1 /*
   2  * Copyright (c) 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 package com.sun.org.apache.xalan.internal.xsltc.compiler;
  22 
  23 import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
  24 import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
  25 import com.sun.org.apache.bcel.internal.generic.InstructionList;
  26 import com.sun.org.apache.bcel.internal.generic.PUSH;
  27 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
  28 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
  29 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringType;
  30 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
  31 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
  32 import java.util.List;
  33 
  34 /**
  35  * @author Morten Jorgensen
  36  * @author Santiago Pericas-Geertsen
  37  * @LastModified: Oct 2017
  38  */
  39 final class KeyCall extends FunctionCall {
  40 
  41     /**
  42      * The name of the key.
  43      */
  44     private Expression _name;
  45 
  46     /**
  47      * The value to look up in the key/index.
  48      */
  49     private Expression _value;
  50 
  51     /**
  52      * The value's data type.
  53      */
  54     private Type _valueType; // The value's data type
  55 
  56     /**
  57      * Expanded qname when name is literal.
  58      */
  59     private QName _resolvedQName = null;
  60 
  61     /**
  62      * Get the parameters passed to function:
  63      *   key(String name, String value)
  64      *   key(String name, NodeSet value)
  65      * The 'arguments' vector should contain two parameters for key() calls,
  66      * one holding the key name and one holding the value(s) to look up. The
  67      * vector has only one parameter for id() calls (the key name is always
  68      * "##id" for id() calls).
  69      *
  70      * @param fname The function name (should be 'key' or 'id')
  71      * @param arguments A vector containing the arguments the the function
  72      */
  73     public KeyCall(QName fname, List<Expression> arguments) {
  74         super(fname, arguments);
  75         switch(argumentCount()) {
  76         case 1:
  77             _name = null;
  78             _value = argument(0);
  79             break;
  80         case 2:
  81             _name = argument(0);
  82             _value = argument(1);
  83             break;
  84         default:
  85             _name = _value = null;
  86             break;
  87         }
  88     }
  89 
  90      /**
  91      * If this call to key() is in a top-level element like  another variable
  92      * or param, add a dependency between that top-level element and the
  93      * referenced key. For example,
  94      *
  95      *   <xsl:key name="x" .../>
  96      *   <xsl:variable name="y" select="key('x', 1)"/>
  97      *
  98      * and assuming this class represents "key('x', 1)", add a reference
  99      * between variable y and key x. Note that if 'x' is unknown statically
 100      * in key('x', 1), there's nothing we can do at this point.
 101      */
 102     public void addParentDependency() {
 103         // If name unknown statically, there's nothing we can do
 104         if (_resolvedQName == null) return;
 105 
 106         SyntaxTreeNode node = this;
 107         while (node != null && node instanceof TopLevelElement == false) {
 108             node = node.getParent();
 109         }
 110 
 111         TopLevelElement parent = (TopLevelElement) node;
 112         if (parent != null) {
 113             parent.addDependency(getSymbolTable().getKey(_resolvedQName));
 114         }
 115     }
 116 
 117    /**
 118      * Type check the parameters for the id() or key() function.
 119      * The index name (for key() call only) must be a string or convertable
 120      * to a string, and the lookup-value must be a string or a node-set.
 121      * @param stable The parser's symbol table
 122      * @throws TypeCheckError When the parameters have illegal type
 123      */
 124     public Type typeCheck(SymbolTable stable) throws TypeCheckError {
 125         final Type returnType = super.typeCheck(stable);
 126 
 127         // Run type check on the key name (first argument) - must be a string,
 128         // and if it is not it must be converted to one using string() rules.
 129         if (_name != null) {
 130             final Type nameType = _name.typeCheck(stable);
 131 
 132             if (_name instanceof LiteralExpr) {
 133                 final LiteralExpr literal = (LiteralExpr) _name;
 134                 _resolvedQName =
 135                     getParser().getQNameIgnoreDefaultNs(literal.getValue());
 136             }
 137             else if (nameType instanceof StringType == false) {
 138                 _name = new CastExpr(_name, Type.String);
 139             }
 140         }
 141 
 142         // Run type check on the value for this key. This value can be of
 143         // any data type, so this should never cause any type-check errors.
 144         // If the value is a reference, then we have to defer the decision
 145         // of how to process it until run-time.
 146         // If the value is known not to be a node-set, then it should be
 147         // converted to a string before the lookup is done. If the value is
 148         // known to be a node-set then this process (convert to string, then
 149         // do lookup) should be applied to every node in the set, and the
 150         // result from all lookups should be added to the resulting node-set.
 151         _valueType = _value.typeCheck(stable);
 152 
 153         if (_valueType != Type.NodeSet
 154                 && _valueType != Type.Reference
 155                 && _valueType != Type.String) {
 156             _value = new CastExpr(_value, Type.String);
 157             _valueType = _value.typeCheck(stable);
 158         }
 159 
 160         // If in a top-level element, create dependency to the referenced key
 161         addParentDependency();
 162 
 163         return returnType;
 164     }
 165 
 166     /**
 167      * This method is called when the constructor is compiled in
 168      * Stylesheet.compileConstructor() and not as the syntax tree is traversed.
 169      * <p>This method will generate byte code that produces an iterator
 170      * for the nodes in the node set for the key or id function call.
 171      * @param classGen The Java class generator
 172      * @param methodGen The method generator
 173      */
 174     public void translate(ClassGenerator classGen,
 175                           MethodGenerator methodGen) {
 176         final ConstantPoolGen cpg = classGen.getConstantPool();
 177         final InstructionList il = methodGen.getInstructionList();
 178 
 179         // Returns the KeyIndex object of a given name
 180         final int getKeyIndex = cpg.addMethodref(TRANSLET_CLASS,
 181                                                  "getKeyIndex",
 182                                                  "(Ljava/lang/String;)"+
 183                                                  KEY_INDEX_SIG);
 184 
 185         // KeyIndex.setDom(Dom, node) => void
 186         final int keyDom = cpg.addMethodref(KEY_INDEX_CLASS,
 187                                             "setDom",
 188                                             "(" + DOM_INTF_SIG + "I)V");
 189 
 190         // Initialises a KeyIndex to return nodes with specific values
 191         final int getKeyIterator =
 192                         cpg.addMethodref(KEY_INDEX_CLASS,
 193                                          "getKeyIndexIterator",
 194                                          "(" + _valueType.toSignature() + "Z)"
 195                                              + KEY_INDEX_ITERATOR_SIG);
 196 
 197         // Initialise the index specified in the first parameter of key()
 198         il.append(classGen.loadTranslet());
 199         if (_name == null) {
 200             il.append(new PUSH(cpg,"##id"));
 201         } else if (_resolvedQName != null) {
 202             il.append(new PUSH(cpg, _resolvedQName.toString()));
 203         } else {
 204             _name.translate(classGen, methodGen);
 205         }
 206 
 207         // Generate following byte code:
 208         //
 209         //   KeyIndex ki = translet.getKeyIndex(_name)
 210         //   ki.setDom(translet.dom);
 211         //   ki.getKeyIndexIterator(_value, true)  - for key()
 212         //        OR
 213         //   ki.getKeyIndexIterator(_value, false)  - for id()
 214         il.append(new INVOKEVIRTUAL(getKeyIndex));
 215         il.append(DUP);
 216         il.append(methodGen.loadDOM());
 217         il.append(methodGen.loadCurrentNode());
 218         il.append(new INVOKEVIRTUAL(keyDom));
 219 
 220         _value.translate(classGen, methodGen);
 221         il.append((_name != null) ? ICONST_1: ICONST_0);
 222         il.append(new INVOKEVIRTUAL(getKeyIterator));
 223     }
 224 }