1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: KeyCall.java,v 1.15 2004/02/16 22:24:29 minchau Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.xsltc.compiler;
  20. import java.util.Vector;
  21. import com.sun.org.apache.bcel.internal.generic.ALOAD;
  22. import com.sun.org.apache.bcel.internal.generic.ASTORE;
  23. import com.sun.org.apache.bcel.internal.generic.BranchHandle;
  24. import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
  25. import com.sun.org.apache.bcel.internal.generic.GOTO;
  26. import com.sun.org.apache.bcel.internal.generic.IFGT;
  27. import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
  28. import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
  29. import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
  30. import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
  31. import com.sun.org.apache.bcel.internal.generic.InstructionList;
  32. import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
  33. import com.sun.org.apache.bcel.internal.generic.NEW;
  34. import com.sun.org.apache.bcel.internal.generic.PUSH;
  35. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
  36. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
  37. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringType;
  38. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
  39. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
  40. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
  41. /**
  42. * @author Morten Jorgensen
  43. * @author Santiago Pericas-Geertsen
  44. */
  45. final class KeyCall extends FunctionCall {
  46. /**
  47. * The name of the key.
  48. */
  49. private Expression _name;
  50. /**
  51. * The value to look up in the key/index.
  52. */
  53. private Expression _value;
  54. /**
  55. * The value's data type.
  56. */
  57. private Type _valueType; // The value's data type
  58. /**
  59. * Expanded qname when name is literal.
  60. */
  61. private QName _resolvedQName = null;
  62. /**
  63. * Get the parameters passed to function:
  64. * key(String name, String value)
  65. * key(String name, NodeSet value)
  66. * The 'arguments' vector should contain two parameters for key() calls,
  67. * one holding the key name and one holding the value(s) to look up. The
  68. * vector has only one parameter for id() calls (the key name is always
  69. * "##id" for id() calls).
  70. *
  71. * @param fname The function name (should be 'key' or 'id')
  72. * @param arguments A vector containing the arguments the the function
  73. */
  74. public KeyCall(QName fname, Vector arguments) {
  75. super(fname, arguments);
  76. switch(argumentCount()) {
  77. case 1:
  78. _name = null;
  79. _value = argument(0);
  80. break;
  81. case 2:
  82. _name = argument(0);
  83. _value = argument(1);
  84. break;
  85. default:
  86. _name = _value = null;
  87. break;
  88. }
  89. }
  90. /**
  91. * Type check the parameters for the id() or key() function.
  92. * The index name (for key() call only) must be a string or convertable
  93. * to a string, and the lookup-value must be a string or a node-set.
  94. * @param stable The parser's symbol table
  95. * @throws TypeCheckError When the parameters have illegal type
  96. */
  97. public Type typeCheck(SymbolTable stable) throws TypeCheckError {
  98. final Type returnType = super.typeCheck(stable);
  99. // Run type check on the key name (first argument) - must be a string,
  100. // and if it is not it must be converted to one using string() rules.
  101. if (_name != null) {
  102. final Type nameType = _name.typeCheck(stable);
  103. if (_name instanceof LiteralExpr) {
  104. final LiteralExpr literal = (LiteralExpr) _name;
  105. _resolvedQName =
  106. getParser().getQNameIgnoreDefaultNs(literal.getValue());
  107. }
  108. else if (nameType instanceof StringType == false) {
  109. _name = new CastExpr(_name, Type.String);
  110. }
  111. }
  112. // Run type check on the value for this key. This value can be of
  113. // any data type, so this should never cause any type-check errors.
  114. // If the value is not a node-set then it should be converted to a
  115. // string before the lookup is done. If the value is a node-set then
  116. // this process (convert to string, then do lookup) should be applied
  117. // to every node in the set, and the result from all lookups should
  118. // be added to the resulting node-set.
  119. _valueType = _value.typeCheck(stable);
  120. if (_valueType != Type.NodeSet && _valueType != Type.String)
  121. {
  122. _value = new CastExpr(_value, Type.String);
  123. }
  124. return returnType;
  125. }
  126. /**
  127. * This method is called when the constructor is compiled in
  128. * Stylesheet.compileConstructor() and not as the syntax tree is traversed.
  129. * This method is a wrapper for the real translation method, which is
  130. * the private method translateCall() below. All this method does is to
  131. * wrap the KeyIndex that this function returns inside a duplicate filter.
  132. * The duplicate filter is used both to eliminate duplicates and to
  133. * cache the nodes in the index.
  134. * @param classGen The Java class generator
  135. * @param methodGen The method generator
  136. */
  137. public void translate(ClassGenerator classGen,
  138. MethodGenerator methodGen) {
  139. final ConstantPoolGen cpg = classGen.getConstantPool();
  140. final InstructionList il = methodGen.getInstructionList();
  141. final int getNodeHandle = cpg.addInterfaceMethodref(DOM_INTF,
  142. "getNodeHandle",
  143. "(I)"+NODE_SIG);
  144. // Wrap the KeyIndex (iterator) inside a duplicate filter iterator
  145. // to pre-read the indexed nodes and cache them.
  146. final int dupInit = cpg.addMethodref(DUP_FILTERED_ITERATOR,
  147. "<init>",
  148. "("+NODE_ITERATOR_SIG+")V");
  149. il.append(new NEW(cpg.addClass(DUP_FILTERED_ITERATOR)));
  150. il.append(DUP);
  151. translateCall(classGen, methodGen);
  152. il.append(new INVOKESPECIAL(dupInit));
  153. }
  154. /**
  155. * Translate the actual index lookup - leaves KeyIndex (iterator) on stack
  156. * @param classGen The Java class generator
  157. * @param methodGen The method generator
  158. */
  159. private void translateCall(ClassGenerator classGen,
  160. MethodGenerator methodGen) {
  161. final ConstantPoolGen cpg = classGen.getConstantPool();
  162. final InstructionList il = methodGen.getInstructionList();
  163. // Returns the string value for a node in the DOM
  164. final int getNodeValue = cpg.addInterfaceMethodref(DOM_INTF,
  165. GET_NODE_VALUE,
  166. "(I)"+STRING_SIG);
  167. // Returns the KeyIndex object of a given name
  168. final int getKeyIndex = cpg.addMethodref(TRANSLET_CLASS,
  169. "getKeyIndex",
  170. "(Ljava/lang/String;)"+
  171. KEY_INDEX_SIG);
  172. // Initialises a KeyIndex to return nodes with specific values
  173. final int lookupId = cpg.addMethodref(KEY_INDEX_CLASS,
  174. "lookupId",
  175. "(Ljava/lang/Object;)V");
  176. final int lookupKey = cpg.addMethodref(KEY_INDEX_CLASS,
  177. "lookupKey",
  178. "(Ljava/lang/Object;)V");
  179. // Merges the nodes in two KeyIndex objects
  180. final int merge = cpg.addMethodref(KEY_INDEX_CLASS,
  181. "merge",
  182. "("+KEY_INDEX_SIG+")V");
  183. // Constructor for KeyIndex class
  184. final int indexConstructor = cpg.addMethodref(TRANSLET_CLASS,
  185. "createKeyIndex",
  186. "()"+KEY_INDEX_SIG);
  187. // KeyIndex.setDom(Dom) => void
  188. final int keyDom = cpg.addMethodref(XSLT_PACKAGE + ".dom.KeyIndex",
  189. "setDom",
  190. "("+DOM_INTF_SIG+")V");
  191. // This local variable holds the index/iterator we will return
  192. final LocalVariableGen returnIndex =
  193. methodGen.addLocalVariable("returnIndex",
  194. Util.getJCRefType(KEY_INDEX_SIG),
  195. il.getEnd(), null);
  196. // This local variable holds the index we're using for search
  197. final LocalVariableGen searchIndex =
  198. methodGen.addLocalVariable("searchIndex",
  199. Util.getJCRefType(KEY_INDEX_SIG),
  200. il.getEnd(), null);
  201. // If the second paramter is a node-set we need to go through each
  202. // node in the set, convert each one to a string and do a look up in
  203. // the named index, and then merge all the resulting node sets.
  204. if (_valueType == Type.NodeSet) {
  205. // Save current node and current iterator on the stack
  206. il.append(methodGen.loadCurrentNode());
  207. il.append(methodGen.loadIterator());
  208. // Get new iterator from 2nd parameter node-set & store in variable
  209. _value.translate(classGen, methodGen);
  210. _value.startIterator(classGen, methodGen);
  211. il.append(methodGen.storeIterator());
  212. // Create the KeyIndex object (the iterator) we'll return
  213. il.append(classGen.loadTranslet());
  214. il.append(new INVOKEVIRTUAL(indexConstructor));
  215. il.append(DUP);
  216. il.append(methodGen.loadDOM());
  217. il.append(new INVOKEVIRTUAL(keyDom));
  218. il.append(new ASTORE(returnIndex.getIndex()));
  219. // Initialise the index specified in the first parameter of key()
  220. il.append(classGen.loadTranslet());
  221. if (_name == null) {
  222. il.append(new PUSH(cpg,"##id"));
  223. }
  224. else if (_resolvedQName != null) {
  225. il.append(new PUSH(cpg, _resolvedQName.toString()));
  226. }
  227. else {
  228. _name.translate(classGen, methodGen);
  229. }
  230. il.append(new INVOKEVIRTUAL(getKeyIndex));
  231. il.append(new ASTORE(searchIndex.getIndex()));
  232. // LOOP STARTS HERE
  233. // Now we're ready to start traversing the node-set given in
  234. // the key() function's second argument....
  235. final BranchHandle nextNode = il.append(new GOTO(null));
  236. final InstructionHandle loop = il.append(NOP);
  237. // Push returnIndex on stack to prepare for call to merge()
  238. il.append(new ALOAD(returnIndex.getIndex()));
  239. // Lookup index using the string value from the current node
  240. il.append(new ALOAD(searchIndex.getIndex()));
  241. il.append(DUP);
  242. il.append(methodGen.loadDOM());
  243. il.append(methodGen.loadCurrentNode());
  244. il.append(new INVOKEINTERFACE(getNodeValue, 2));
  245. if (_name == null) {
  246. il.append(new INVOKEVIRTUAL(lookupId));
  247. }
  248. else {
  249. il.append(new INVOKEVIRTUAL(lookupKey));
  250. }
  251. // Call to returnIndex.merge(searchIndex);
  252. il.append(new INVOKEVIRTUAL(merge));
  253. // Go on with next node in the 2nd parameter node-set
  254. nextNode.setTarget(il.append(methodGen.loadIterator()));
  255. il.append(methodGen.nextNode());
  256. il.append(DUP);
  257. il.append(methodGen.storeCurrentNode());
  258. il.append(new IFGT(loop));
  259. // LOOP ENDS HERE
  260. // Restore current node and current iterator from the stack
  261. il.append(methodGen.storeIterator());
  262. il.append(methodGen.storeCurrentNode());
  263. // Return with the an iterator for all resulting nodes
  264. il.append(new ALOAD(returnIndex.getIndex()));
  265. }
  266. // If the second parameter is a single value we just lookup the named
  267. // index and initialise the iterator to return nodes with this value.
  268. else {
  269. // Call getKeyIndex in AbstractTranslet with the name of the key
  270. // to get the index for this key (which is also a node iterator).
  271. il.append(classGen.loadTranslet());
  272. if (_name == null) {
  273. il.append(new PUSH(cpg,"##id"));
  274. }
  275. else if (_resolvedQName != null) {
  276. il.append(new PUSH(cpg, _resolvedQName.toString()));
  277. }
  278. else {
  279. _name.translate(classGen, methodGen);
  280. }
  281. il.append(new INVOKEVIRTUAL(getKeyIndex));
  282. // Now use the value in the second argument to determine what nodes
  283. // the iterator should return.
  284. il.append(DUP);
  285. _value.translate(classGen, methodGen);
  286. if (_name == null) {
  287. il.append(new INVOKEVIRTUAL(lookupId));
  288. }
  289. else {
  290. il.append(new INVOKEVIRTUAL(lookupKey));
  291. }
  292. }
  293. }
  294. }