1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20/*
21 * $Id: Key.java,v 1.6 2006/04/25 02:25:08 jeffsuttor Exp $
22 */
23
24package com.sun.org.apache.xalan.internal.xsltc.compiler;
25
26import java.util.Vector;
27
28import com.sun.org.apache.bcel.internal.generic.BranchHandle;
29import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
30import com.sun.org.apache.bcel.internal.generic.GOTO;
31import com.sun.org.apache.bcel.internal.generic.IFEQ;
32import com.sun.org.apache.bcel.internal.generic.IFGE;
33import com.sun.org.apache.bcel.internal.generic.IFGT;
34import com.sun.org.apache.bcel.internal.generic.ILOAD;
35import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
36import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
37import com.sun.org.apache.bcel.internal.generic.ISTORE;
38import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
39import com.sun.org.apache.bcel.internal.generic.InstructionList;
40import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
41import com.sun.org.apache.bcel.internal.generic.PUSH;
42import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
43import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
44import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
45import com.sun.org.apache.xalan.internal.xsltc.compiler.util.NodeSetType;
46import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringType;
47import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
48import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
49import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
50import com.sun.org.apache.xml.internal.dtm.Axis;
51import com.sun.org.apache.xml.internal.utils.XML11Char;
52
53/**
54 * @author Morten Jorgensen
55 * @author Santiago Pericas-Geertsen
56 */
57final class Key extends TopLevelElement {
58
59    /**
60     * The name of this key as defined in xsl:key.
61     */
62    private QName _name;
63
64    /**
65     * The pattern to match starting at the root node.
66     */
67    private Pattern _match;
68
69    /**
70     * The expression that generates the values for this key.
71     */
72    private Expression _use;
73
74    /**
75     * The type of the _use expression.
76     */
77    private Type _useType;
78
79    /**
80     * Parse the <xsl:key> element and attributes
81     * @param parser A reference to the stylesheet parser
82     */
83    public void parseContents(Parser parser) {
84
85        // Get the required attributes and parser XPath expressions
86        final String name = getAttribute("name");
87        if (!XML11Char.isXML11ValidQName(name)){
88            ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, name, this);
89            parser.reportError(Constants.ERROR, err);
90        }
91
92        // Parse key name and add to symbol table
93        _name = parser.getQNameIgnoreDefaultNs(name);
94        getSymbolTable().addKey(_name, this);
95
96        _match = parser.parsePattern(this, "match", null);
97        _use = parser.parseExpression(this, "use", null);
98
99        // Make sure required attribute(s) have been set
100        if (_name == null) {
101            reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "name");
102            return;
103        }
104        if (_match.isDummy()) {
105            reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "match");
106            return;
107        }
108        if (_use.isDummy()) {
109            reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "use");
110            return;
111        }
112    }
113
114    /**
115     * Returns a String-representation of this key's name
116     * @return The key's name (from the <xsl:key> elements 'name' attribute).
117     */
118    public String getName() {
119        return _name.toString();
120    }
121
122    public Type typeCheck(SymbolTable stable) throws TypeCheckError {
123        // Type check match pattern
124        _match.typeCheck(stable);
125
126        // Cast node values to string values (except for nodesets)
127        _useType = _use.typeCheck(stable);
128        if (_useType instanceof StringType == false &&
129            _useType instanceof NodeSetType == false)
130        {
131            _use = new CastExpr(_use, Type.String);
132        }
133
134        return Type.Void;
135    }
136
137    /**
138     * This method is called if the "use" attribute of the key contains a
139     * node set. In this case we must traverse all nodes in the set and
140     * create one entry in this key's index for each node in the set.
141     */
142    public void traverseNodeSet(ClassGenerator classGen,
143                                MethodGenerator methodGen,
144                                int buildKeyIndex) {
145        final ConstantPoolGen cpg = classGen.getConstantPool();
146        final InstructionList il = methodGen.getInstructionList();
147
148        // DOM.getStringValueX(nodeIndex) => String
149        final int getNodeValue = cpg.addInterfaceMethodref(DOM_INTF,
150                                                           GET_NODE_VALUE,
151                                                           "(I)"+STRING_SIG);
152
153        final int getNodeIdent = cpg.addInterfaceMethodref(DOM_INTF,
154                                                           "getNodeIdent",
155                                                           "(I)"+NODE_SIG);
156
157        // AbstractTranslet.SetKeyIndexDom(name, Dom) => void
158        final int keyDom = cpg.addMethodref(TRANSLET_CLASS,
159                                         "setKeyIndexDom",
160                                         "("+STRING_SIG+DOM_INTF_SIG+")V");
161
162
163        // This variable holds the id of the node we found with the "match"
164        // attribute of xsl:key. This is the id we store, with the value we
165        // get from the nodes we find here, in the index for this key.
166        final LocalVariableGen parentNode =
167            methodGen.addLocalVariable("parentNode",
168                                       Util.getJCRefType("I"),
169                                       null, null);
170
171        // Get the 'parameter' from the stack and store it in a local var.
172        parentNode.setStart(il.append(new ISTORE(parentNode.getIndex())));
173
174        // Save current node and current iterator on the stack
175        il.append(methodGen.loadCurrentNode());
176        il.append(methodGen.loadIterator());
177
178        // Overwrite current iterator with one that gives us only what we want
179        _use.translate(classGen, methodGen);
180        _use.startIterator(classGen, methodGen);
181        il.append(methodGen.storeIterator());
182
183        final BranchHandle nextNode = il.append(new GOTO(null));
184        final InstructionHandle loop = il.append(NOP);
185
186        // Prepare to call buildKeyIndex(String name, int node, String value);
187        il.append(classGen.loadTranslet());
188        il.append(new PUSH(cpg, _name.toString()));
189        parentNode.setEnd(il.append(new ILOAD(parentNode.getIndex())));
190
191        // Now get the node value and push it on the parameter stack
192        il.append(methodGen.loadDOM());
193        il.append(methodGen.loadCurrentNode());
194        il.append(new INVOKEINTERFACE(getNodeValue, 2));
195
196        // Finally do the call to add an entry in the index for this key.
197        il.append(new INVOKEVIRTUAL(buildKeyIndex));
198
199        il.append(classGen.loadTranslet());
200        il.append(new PUSH(cpg, getName()));
201        il.append(methodGen.loadDOM());
202        il.append(new INVOKEVIRTUAL(keyDom));
203
204        nextNode.setTarget(il.append(methodGen.loadIterator()));
205        il.append(methodGen.nextNode());
206
207        il.append(DUP);
208        il.append(methodGen.storeCurrentNode());
209        il.append(new IFGE(loop)); // Go on to next matching node....
210
211        // Restore current node and current iterator from the stack
212        il.append(methodGen.storeIterator());
213        il.append(methodGen.storeCurrentNode());
214    }
215
216    /**
217     * Gather all nodes that match the expression in the attribute "match"
218     * and add one (or more) entries in this key's index.
219     */
220    public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
221
222        final ConstantPoolGen cpg = classGen.getConstantPool();
223        final InstructionList il = methodGen.getInstructionList();
224        final int current = methodGen.getLocalIndex("current");
225
226        // AbstractTranslet.buildKeyIndex(name,node_id,value) => void
227        final int key = cpg.addMethodref(TRANSLET_CLASS,
228                                         "buildKeyIndex",
229                                         "("+STRING_SIG+"I"+STRING_SIG+")V");
230
231        // AbstractTranslet.SetKeyIndexDom(name, Dom) => void
232        final int keyDom = cpg.addMethodref(TRANSLET_CLASS,
233                                         "setKeyIndexDom",
234                                         "("+STRING_SIG+DOM_INTF_SIG+")V");
235
236        final int getNodeIdent = cpg.addInterfaceMethodref(DOM_INTF,
237                                                           "getNodeIdent",
238                                                           "(I)"+NODE_SIG);
239
240        // DOM.getAxisIterator(root) => NodeIterator
241        final int git = cpg.addInterfaceMethodref(DOM_INTF,
242                                                  "getAxisIterator",
243                                                  "(I)"+NODE_ITERATOR_SIG);
244
245        il.append(methodGen.loadCurrentNode());
246        il.append(methodGen.loadIterator());
247
248        // Get an iterator for all nodes in the DOM
249        il.append(methodGen.loadDOM());
250        il.append(new PUSH(cpg,Axis.DESCENDANT));
251        il.append(new INVOKEINTERFACE(git, 2));
252
253        // Reset the iterator to start with the root node
254        il.append(methodGen.loadCurrentNode());
255        il.append(methodGen.setStartNode());
256        il.append(methodGen.storeIterator());
257
258        // Loop for traversing all nodes in the DOM
259        final BranchHandle nextNode = il.append(new GOTO(null));
260        final InstructionHandle loop = il.append(NOP);
261
262        // Check if the current node matches the pattern in "match"
263        il.append(methodGen.loadCurrentNode());
264        _match.translate(classGen, methodGen);
265        _match.synthesize(classGen, methodGen); // Leaves 0 or 1 on stack
266        final BranchHandle skipNode = il.append(new IFEQ(null));
267
268        // If this is a node-set we must go through each node in the set
269        if (_useType instanceof NodeSetType) {
270            // Pass current node as parameter (we're indexing on that node)
271            il.append(methodGen.loadCurrentNode());
272            traverseNodeSet(classGen, methodGen, key);
273        }
274        else {
275            il.append(classGen.loadTranslet());
276            il.append(DUP);
277            il.append(new PUSH(cpg, _name.toString()));
278            il.append(DUP_X1);
279            il.append(methodGen.loadCurrentNode());
280            _use.translate(classGen, methodGen);
281            il.append(new INVOKEVIRTUAL(key));
282
283            il.append(methodGen.loadDOM());
284            il.append(new INVOKEVIRTUAL(keyDom));
285        }
286
287        // Get the next node from the iterator and do loop again...
288        final InstructionHandle skip = il.append(NOP);
289
290        il.append(methodGen.loadIterator());
291        il.append(methodGen.nextNode());
292        il.append(DUP);
293        il.append(methodGen.storeCurrentNode());
294        il.append(new IFGT(loop));
295
296        // Restore current node and current iterator from the stack
297        il.append(methodGen.storeIterator());
298        il.append(methodGen.storeCurrentNode());
299
300        nextNode.setTarget(skip);
301        skipNode.setTarget(skip);
302    }
303}
304