001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: Key.java 1225842 2011-12-30 15:14:35Z mrglavas $
020     */
021    
022    package org.apache.xalan.xsltc.compiler;
023    
024    import org.apache.bcel.generic.BranchHandle;
025    import org.apache.bcel.generic.ConstantPoolGen;
026    import org.apache.bcel.generic.GOTO;
027    import org.apache.bcel.generic.IFEQ;
028    import org.apache.bcel.generic.IFGE;
029    import org.apache.bcel.generic.IFGT;
030    import org.apache.bcel.generic.ILOAD;
031    import org.apache.bcel.generic.INVOKEINTERFACE;
032    import org.apache.bcel.generic.INVOKEVIRTUAL;
033    import org.apache.bcel.generic.ISTORE;
034    import org.apache.bcel.generic.InstructionHandle;
035    import org.apache.bcel.generic.InstructionList;
036    import org.apache.bcel.generic.LocalVariableGen;
037    import org.apache.bcel.generic.PUSH;
038    import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
039    import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
040    import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
041    import org.apache.xalan.xsltc.compiler.util.NodeSetType;
042    import org.apache.xalan.xsltc.compiler.util.StringType;
043    import org.apache.xalan.xsltc.compiler.util.Type;
044    import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
045    import org.apache.xalan.xsltc.compiler.util.Util;
046    import org.apache.xml.dtm.Axis;
047    import org.apache.xml.utils.XML11Char;
048    
049    /**
050     * @author Morten Jorgensen
051     * @author Santiago Pericas-Geertsen
052     */
053    final class Key extends TopLevelElement {
054    
055        /**
056         * The name of this key as defined in xsl:key.
057         */
058        private QName _name;
059    
060        /**
061         * The pattern to match starting at the root node.
062         */
063        private Pattern _match; 
064    
065        /**
066         * The expression that generates the values for this key.
067         */
068        private Expression _use;
069    
070        /**
071         * The type of the _use expression.
072         */
073        private Type _useType;
074            
075        /**
076         * Parse the <xsl:key> element and attributes
077         * @param parser A reference to the stylesheet parser
078         */
079        public void parseContents(Parser parser) {
080    
081            // Get the required attributes and parser XPath expressions
082            final String name = getAttribute("name");
083            if (!XML11Char.isXML11ValidQName(name)){
084                ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, name, this);
085                parser.reportError(Constants.ERROR, err);           
086            }
087            
088            // Parse key name and add to symbol table
089            _name = parser.getQNameIgnoreDefaultNs(name);
090            getSymbolTable().addKey(_name, this);
091            
092            _match = parser.parsePattern(this, "match", null);
093            _use = parser.parseExpression(this, "use", null);
094    
095            // Make sure required attribute(s) have been set
096            if (_name == null) {
097                reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "name");
098                return;
099            }
100            if (_match.isDummy()) {
101                reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "match");
102                return;
103            }
104            if (_use.isDummy()) {
105                reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "use");
106                return;
107            }
108        }
109    
110        /**
111         * Returns a String-representation of this key's name
112         * @return The key's name (from the <xsl:key> elements 'name' attribute).
113         */
114        public String getName() {
115            return _name.toString();
116        }
117    
118        public Type typeCheck(SymbolTable stable) throws TypeCheckError {
119            // Type check match pattern
120            _match.typeCheck(stable);
121    
122            // Cast node values to string values (except for nodesets)
123            _useType = _use.typeCheck(stable);
124            if (_useType instanceof StringType == false &&
125                _useType instanceof NodeSetType == false) 
126            {
127                _use = new CastExpr(_use, Type.String);
128            }
129    
130            return Type.Void;
131        }
132    
133        /**
134         * This method is called if the "use" attribute of the key contains a
135         * node set. In this case we must traverse all nodes in the set and
136         * create one entry in this key's index for each node in the set.
137         */
138        public void traverseNodeSet(ClassGenerator classGen,
139                                    MethodGenerator methodGen,
140                                    int buildKeyIndex) {
141            final ConstantPoolGen cpg = classGen.getConstantPool();
142            final InstructionList il = methodGen.getInstructionList();
143    
144            // DOM.getStringValueX(nodeIndex) => String
145            final int getNodeValue = cpg.addInterfaceMethodref(DOM_INTF,
146                                                               GET_NODE_VALUE,
147                                                               "(I)"+STRING_SIG);
148                                                               
149            final int getNodeIdent = cpg.addInterfaceMethodref(DOM_INTF,
150                                                               "getNodeIdent",
151                                                               "(I)"+NODE_SIG);     
152                                                               
153            // AbstractTranslet.SetKeyIndexDom(name, Dom) => void
154            final int keyDom = cpg.addMethodref(TRANSLET_CLASS,
155                                             "setKeyIndexDom",
156                                             "("+STRING_SIG+DOM_INTF_SIG+")V");                              
157                                                                                               
158    
159            // This variable holds the id of the node we found with the "match"
160            // attribute of xsl:key. This is the id we store, with the value we
161            // get from the nodes we find here, in the index for this key.
162            final LocalVariableGen parentNode =
163                methodGen.addLocalVariable("parentNode",
164                                           Util.getJCRefType("I"),
165                                           null, null);
166    
167            // Get the 'parameter' from the stack and store it in a local var.
168            parentNode.setStart(il.append(new ISTORE(parentNode.getIndex())));      
169    
170            // Save current node and current iterator on the stack
171            il.append(methodGen.loadCurrentNode());
172            il.append(methodGen.loadIterator());
173    
174            // Overwrite current iterator with one that gives us only what we want
175            _use.translate(classGen, methodGen);
176            _use.startIterator(classGen, methodGen);
177            il.append(methodGen.storeIterator());
178    
179            final BranchHandle nextNode = il.append(new GOTO(null));
180            final InstructionHandle loop = il.append(NOP);
181    
182            // Prepare to call buildKeyIndex(String name, int node, String value);
183            il.append(classGen.loadTranslet());
184            il.append(new PUSH(cpg, _name.toString()));
185            parentNode.setEnd(il.append(new ILOAD(parentNode.getIndex())));
186    
187            // Now get the node value and push it on the parameter stack
188            il.append(methodGen.loadDOM());
189            il.append(methodGen.loadCurrentNode());
190            il.append(new INVOKEINTERFACE(getNodeValue, 2));                
191    
192            // Finally do the call to add an entry in the index for this key.
193            il.append(new INVOKEVIRTUAL(buildKeyIndex));
194            
195            il.append(classGen.loadTranslet());
196            il.append(new PUSH(cpg, getName()));
197            il.append(methodGen.loadDOM());
198            il.append(new INVOKEVIRTUAL(keyDom));
199    
200            nextNode.setTarget(il.append(methodGen.loadIterator()));
201            il.append(methodGen.nextNode());        
202    
203            il.append(DUP);
204            il.append(methodGen.storeCurrentNode());
205            il.append(new IFGE(loop)); // Go on to next matching node....
206    
207            // Restore current node and current iterator from the stack
208            il.append(methodGen.storeIterator());
209            il.append(methodGen.storeCurrentNode());
210        }
211    
212        /**
213         * Gather all nodes that match the expression in the attribute "match"
214         * and add one (or more) entries in this key's index.
215         */
216        public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
217    
218            final ConstantPoolGen cpg = classGen.getConstantPool();
219            final InstructionList il = methodGen.getInstructionList();
220            final int current = methodGen.getLocalIndex("current");
221    
222            // AbstractTranslet.buildKeyIndex(name,node_id,value) => void
223            final int key = cpg.addMethodref(TRANSLET_CLASS,
224                                             "buildKeyIndex",
225                                             "("+STRING_SIG+"I"+OBJECT_SIG+")V");
226                                             
227            // AbstractTranslet.SetKeyIndexDom(name, Dom) => void
228            final int keyDom = cpg.addMethodref(TRANSLET_CLASS,
229                                             "setKeyIndexDom",
230                                             "("+STRING_SIG+DOM_INTF_SIG+")V");
231                                             
232            final int getNodeIdent = cpg.addInterfaceMethodref(DOM_INTF,
233                                                               "getNodeIdent",
234                                                               "(I)"+NODE_SIG);                                                                      
235    
236            // DOM.getAxisIterator(root) => NodeIterator
237            final int git = cpg.addInterfaceMethodref(DOM_INTF,
238                                                      "getAxisIterator",
239                                                      "(I)"+NODE_ITERATOR_SIG);
240    
241            il.append(methodGen.loadCurrentNode());
242            il.append(methodGen.loadIterator());
243    
244            // Get an iterator for all nodes in the DOM
245            il.append(methodGen.loadDOM()); 
246            il.append(new PUSH(cpg,Axis.DESCENDANT));
247            il.append(new INVOKEINTERFACE(git, 2));
248    
249            // Reset the iterator to start with the root node
250            il.append(methodGen.loadCurrentNode());
251            il.append(methodGen.setStartNode());
252            il.append(methodGen.storeIterator());
253    
254            // Loop for traversing all nodes in the DOM
255            final BranchHandle nextNode = il.append(new GOTO(null));
256            final InstructionHandle loop = il.append(NOP);
257    
258            // Check if the current node matches the pattern in "match"
259            il.append(methodGen.loadCurrentNode());
260            _match.translate(classGen, methodGen);
261            _match.synthesize(classGen, methodGen); // Leaves 0 or 1 on stack
262            final BranchHandle skipNode = il.append(new IFEQ(null));
263            
264            // If this is a node-set we must go through each node in the set
265            if (_useType instanceof NodeSetType) {
266                // Pass current node as parameter (we're indexing on that node)
267                il.append(methodGen.loadCurrentNode());
268                traverseNodeSet(classGen, methodGen, key);
269            }
270            else {
271                il.append(classGen.loadTranslet());
272                il.append(DUP);
273                il.append(new PUSH(cpg, _name.toString()));
274                il.append(DUP_X1);
275                il.append(methodGen.loadCurrentNode());
276                _use.translate(classGen, methodGen);
277                il.append(new INVOKEVIRTUAL(key));
278                
279                il.append(methodGen.loadDOM());
280                il.append(new INVOKEVIRTUAL(keyDom));
281            }
282            
283            // Get the next node from the iterator and do loop again...
284            final InstructionHandle skip = il.append(NOP);
285            
286            il.append(methodGen.loadIterator());
287            il.append(methodGen.nextNode());
288            il.append(DUP);
289            il.append(methodGen.storeCurrentNode());
290            il.append(new IFGT(loop));
291    
292            // Restore current node and current iterator from the stack
293            il.append(methodGen.storeIterator());
294            il.append(methodGen.storeCurrentNode());
295            
296            nextNode.setTarget(skip);
297            skipNode.setTarget(skip);
298        }
299    }