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