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: Predicate.java 468650 2006-10-28 07:03:30Z minchau $ 020 */ 021 022 package org.apache.xalan.xsltc.compiler; 023 024 import java.util.ArrayList; 025 026 import org.apache.bcel.classfile.Field; 027 import org.apache.bcel.generic.ASTORE; 028 import org.apache.bcel.generic.CHECKCAST; 029 import org.apache.bcel.generic.ConstantPoolGen; 030 import org.apache.bcel.generic.GETFIELD; 031 import org.apache.bcel.generic.INVOKESPECIAL; 032 import org.apache.bcel.generic.InstructionList; 033 import org.apache.bcel.generic.LocalVariableGen; 034 import org.apache.bcel.generic.NEW; 035 import org.apache.bcel.generic.PUSH; 036 import org.apache.bcel.generic.PUTFIELD; 037 import org.apache.xalan.xsltc.compiler.util.BooleanType; 038 import org.apache.xalan.xsltc.compiler.util.ClassGenerator; 039 import org.apache.xalan.xsltc.compiler.util.FilterGenerator; 040 import org.apache.xalan.xsltc.compiler.util.IntType; 041 import org.apache.xalan.xsltc.compiler.util.MethodGenerator; 042 import org.apache.xalan.xsltc.compiler.util.NumberType; 043 import org.apache.xalan.xsltc.compiler.util.ReferenceType; 044 import org.apache.xalan.xsltc.compiler.util.ResultTreeType; 045 import org.apache.xalan.xsltc.compiler.util.TestGenerator; 046 import org.apache.xalan.xsltc.compiler.util.Type; 047 import org.apache.xalan.xsltc.compiler.util.TypeCheckError; 048 import org.apache.xalan.xsltc.compiler.util.Util; 049 import org.apache.xalan.xsltc.runtime.Operators; 050 051 /** 052 * @author Jacek Ambroziak 053 * @author Santiago Pericas-Geertsen 054 * @author Morten Jorgensen 055 */ 056 final class Predicate extends Expression implements Closure { 057 058 /** 059 * The predicate's expression. 060 */ 061 private Expression _exp = null; 062 063 /** 064 * This flag indicates if optimizations are turned on. The 065 * method <code>dontOptimize()</code> can be called to turn 066 * optimizations off. 067 */ 068 private boolean _canOptimize = true; 069 070 /** 071 * Flag indicatig if the nth position optimization is on. It 072 * is set in <code>typeCheck()</code>. 073 */ 074 private boolean _nthPositionFilter = false; 075 076 /** 077 * Flag indicatig if the nth position descendant is on. It 078 * is set in <code>typeCheck()</code>. 079 */ 080 private boolean _nthDescendant = false; 081 082 /** 083 * Cached node type of the expression that owns this predicate. 084 */ 085 int _ptype = -1; 086 087 /** 088 * Name of the inner class. 089 */ 090 private String _className = null; 091 092 /** 093 * List of variables in closure. 094 */ 095 private ArrayList _closureVars = null; 096 097 /** 098 * Reference to parent closure. 099 */ 100 private Closure _parentClosure = null; 101 102 /** 103 * Cached value of method <code>getCompareValue()</code>. 104 */ 105 private Expression _value = null; 106 107 /** 108 * Cached value of method <code>getCompareValue()</code>. 109 */ 110 private Step _step = null; 111 112 /** 113 * Initializes a predicate. 114 */ 115 public Predicate(Expression exp) { 116 _exp = exp; 117 _exp.setParent(this); 118 119 } 120 121 /** 122 * Set the parser for this expression. 123 */ 124 public void setParser(Parser parser) { 125 super.setParser(parser); 126 _exp.setParser(parser); 127 } 128 129 /** 130 * Returns a boolean value indicating if the nth position optimization 131 * is on. Must be call after type checking! 132 */ 133 public boolean isNthPositionFilter() { 134 return _nthPositionFilter; 135 } 136 137 /** 138 * Returns a boolean value indicating if the nth descendant optimization 139 * is on. Must be call after type checking! 140 */ 141 public boolean isNthDescendant() { 142 return _nthDescendant; 143 } 144 145 /** 146 * Turns off all optimizations for this predicate. 147 */ 148 public void dontOptimize() { 149 _canOptimize = false; 150 } 151 152 /** 153 * Returns true if the expression in this predicate contains a call 154 * to position(). 155 */ 156 public boolean hasPositionCall() { 157 return _exp.hasPositionCall(); 158 } 159 160 /** 161 * Returns true if the expression in this predicate contains a call 162 * to last(). 163 */ 164 public boolean hasLastCall() { 165 return _exp.hasLastCall(); 166 } 167 168 // -- Begin Closure interface -------------------- 169 170 /** 171 * Returns true if this closure is compiled in an inner class (i.e. 172 * if this is a real closure). 173 */ 174 public boolean inInnerClass() { 175 return (_className != null); 176 } 177 178 /** 179 * Returns a reference to its parent closure or null if outermost. 180 */ 181 public Closure getParentClosure() { 182 if (_parentClosure == null) { 183 SyntaxTreeNode node = getParent(); 184 do { 185 if (node instanceof Closure) { 186 _parentClosure = (Closure) node; 187 break; 188 } 189 if (node instanceof TopLevelElement) { 190 break; // way up in the tree 191 } 192 node = node.getParent(); 193 } while (node != null); 194 } 195 return _parentClosure; 196 } 197 198 /** 199 * Returns the name of the auxiliary class or null if this predicate 200 * is compiled inside the Translet. 201 */ 202 public String getInnerClassName() { 203 return _className; 204 } 205 206 /** 207 * Add new variable to the closure. 208 */ 209 public void addVariable(VariableRefBase variableRef) { 210 if (_closureVars == null) { 211 _closureVars = new ArrayList(); 212 } 213 214 // Only one reference per variable 215 if (!_closureVars.contains(variableRef)) { 216 _closureVars.add(variableRef); 217 218 // Add variable to parent closure as well 219 Closure parentClosure = getParentClosure(); 220 if (parentClosure != null) { 221 parentClosure.addVariable(variableRef); 222 } 223 } 224 } 225 226 // -- End Closure interface ---------------------- 227 228 /** 229 * Returns the node type of the expression owning this predicate. The 230 * return value is cached in <code>_ptype</code>. 231 */ 232 public int getPosType() { 233 if (_ptype == -1) { 234 SyntaxTreeNode parent = getParent(); 235 if (parent instanceof StepPattern) { 236 _ptype = ((StepPattern)parent).getNodeType(); 237 } 238 else if (parent instanceof AbsoluteLocationPath) { 239 AbsoluteLocationPath path = (AbsoluteLocationPath)parent; 240 Expression exp = path.getPath(); 241 if (exp instanceof Step) { 242 _ptype = ((Step)exp).getNodeType(); 243 } 244 } 245 else if (parent instanceof VariableRefBase) { 246 final VariableRefBase ref = (VariableRefBase)parent; 247 final VariableBase var = ref.getVariable(); 248 final Expression exp = var.getExpression(); 249 if (exp instanceof Step) { 250 _ptype = ((Step)exp).getNodeType(); 251 } 252 } 253 else if (parent instanceof Step) { 254 _ptype = ((Step)parent).getNodeType(); 255 } 256 } 257 return _ptype; 258 } 259 260 public boolean parentIsPattern() { 261 return (getParent() instanceof Pattern); 262 } 263 264 public Expression getExpr() { 265 return _exp; 266 } 267 268 public String toString() { 269 return "pred(" + _exp + ')'; 270 } 271 272 /** 273 * Type check a predicate expression. If the type of the expression is 274 * number convert it to boolean by adding a comparison with position(). 275 * Note that if the expression is a parameter, we cannot distinguish 276 * at compile time if its type is number or not. Hence, expressions of 277 * reference type are always converted to booleans. 278 * 279 * This method may be called twice, before and after calling 280 * <code>dontOptimize()</code>. If so, the second time it should honor 281 * the new value of <code>_canOptimize</code>. 282 */ 283 public Type typeCheck(SymbolTable stable) throws TypeCheckError { 284 Type texp = _exp.typeCheck(stable); 285 286 // We need explicit type information for reference types - no good! 287 if (texp instanceof ReferenceType) { 288 _exp = new CastExpr(_exp, texp = Type.Real); 289 } 290 291 // A result tree fragment should not be cast directly to a number type, 292 // but rather to a boolean value, and then to a numer (0 or 1). 293 // Ref. section 11.2 of the XSLT 1.0 spec 294 if (texp instanceof ResultTreeType) { 295 _exp = new CastExpr(_exp, Type.Boolean); 296 _exp = new CastExpr(_exp, Type.Real); 297 texp = _exp.typeCheck(stable); 298 } 299 300 // Numerical types will be converted to a position filter 301 if (texp instanceof NumberType) { 302 // Cast any numerical types to an integer 303 if (texp instanceof IntType == false) { 304 _exp = new CastExpr(_exp, Type.Int); 305 } 306 307 if (_canOptimize) { 308 // Nth position optimization. Expression must not depend on context 309 _nthPositionFilter = 310 !_exp.hasLastCall() && !_exp.hasPositionCall(); 311 312 // _nthDescendant optimization - only if _nthPositionFilter is on 313 if (_nthPositionFilter) { 314 SyntaxTreeNode parent = getParent(); 315 _nthDescendant = (parent instanceof Step) && 316 (parent.getParent() instanceof AbsoluteLocationPath); 317 return _type = Type.NodeSet; 318 } 319 } 320 321 // Reset optimization flags 322 _nthPositionFilter = _nthDescendant = false; 323 324 // Otherwise, expand [e] to [position() = e] 325 final QName position = 326 getParser().getQNameIgnoreDefaultNs("position"); 327 final PositionCall positionCall = 328 new PositionCall(position); 329 positionCall.setParser(getParser()); 330 positionCall.setParent(this); 331 332 _exp = new EqualityExpr(Operators.EQ, positionCall, 333 _exp); 334 if (_exp.typeCheck(stable) != Type.Boolean) { 335 _exp = new CastExpr(_exp, Type.Boolean); 336 } 337 return _type = Type.Boolean; 338 } 339 else { 340 // All other types will be handled as boolean values 341 if (texp instanceof BooleanType == false) { 342 _exp = new CastExpr(_exp, Type.Boolean); 343 } 344 return _type = Type.Boolean; 345 } 346 } 347 348 /** 349 * Create a new "Filter" class implementing 350 * <code>CurrentNodeListFilter</code>. Allocate registers for local 351 * variables and local parameters passed in the closure to test(). 352 * Notice that local variables need to be "unboxed". 353 */ 354 private void compileFilter(ClassGenerator classGen, 355 MethodGenerator methodGen) { 356 TestGenerator testGen; 357 LocalVariableGen local; 358 FilterGenerator filterGen; 359 360 _className = getXSLTC().getHelperClassName(); 361 filterGen = new FilterGenerator(_className, 362 "java.lang.Object", 363 toString(), 364 ACC_PUBLIC | ACC_SUPER, 365 new String[] { 366 CURRENT_NODE_LIST_FILTER 367 }, 368 classGen.getStylesheet()); 369 370 final ConstantPoolGen cpg = filterGen.getConstantPool(); 371 final int length = (_closureVars == null) ? 0 : _closureVars.size(); 372 373 // Add a new instance variable for each var in closure 374 for (int i = 0; i < length; i++) { 375 VariableBase var = ((VariableRefBase) _closureVars.get(i)).getVariable(); 376 377 filterGen.addField(new Field(ACC_PUBLIC, 378 cpg.addUtf8(var.getEscapedName()), 379 cpg.addUtf8(var.getType().toSignature()), 380 null, cpg.getConstantPool())); 381 } 382 383 final InstructionList il = new InstructionList(); 384 testGen = new TestGenerator(ACC_PUBLIC | ACC_FINAL, 385 org.apache.bcel.generic.Type.BOOLEAN, 386 new org.apache.bcel.generic.Type[] { 387 org.apache.bcel.generic.Type.INT, 388 org.apache.bcel.generic.Type.INT, 389 org.apache.bcel.generic.Type.INT, 390 org.apache.bcel.generic.Type.INT, 391 Util.getJCRefType(TRANSLET_SIG), 392 Util.getJCRefType(NODE_ITERATOR_SIG) 393 }, 394 new String[] { 395 "node", 396 "position", 397 "last", 398 "current", 399 "translet", 400 "iterator" 401 }, 402 "test", _className, il, cpg); 403 404 // Store the dom in a local variable 405 local = testGen.addLocalVariable("document", 406 Util.getJCRefType(DOM_INTF_SIG), 407 null, null); 408 final String className = classGen.getClassName(); 409 il.append(filterGen.loadTranslet()); 410 il.append(new CHECKCAST(cpg.addClass(className))); 411 il.append(new GETFIELD(cpg.addFieldref(className, 412 DOM_FIELD, DOM_INTF_SIG))); 413 local.setStart(il.append(new ASTORE(local.getIndex()))); 414 415 // Store the dom index in the test generator 416 testGen.setDomIndex(local.getIndex()); 417 418 _exp.translate(filterGen, testGen); 419 il.append(IRETURN); 420 421 filterGen.addEmptyConstructor(ACC_PUBLIC); 422 filterGen.addMethod(testGen); 423 424 getXSLTC().dumpClass(filterGen.getJavaClass()); 425 } 426 427 /** 428 * Returns true if the predicate is a test for the existance of an 429 * element or attribute. All we have to do is to get the first node 430 * from the step, check if it is there, and then return true/false. 431 */ 432 public boolean isBooleanTest() { 433 return (_exp instanceof BooleanExpr); 434 } 435 436 /** 437 * Method to see if we can optimise the predicate by using a specialised 438 * iterator for expressions like '/foo/bar[@attr = $var]', which are 439 * very common in many stylesheets 440 */ 441 public boolean isNodeValueTest() { 442 if (!_canOptimize) return false; 443 return (getStep() != null && getCompareValue() != null); 444 } 445 446 /** 447 * Returns the step in an expression of the form 'step = value'. 448 * Null is returned if the expression is not of the right form. 449 * Optimization if off if null is returned. 450 */ 451 public Step getStep() { 452 // Returned cached value if called more than once 453 if (_step != null) { 454 return _step; 455 } 456 457 // Nothing to do if _exp is null 458 if (_exp == null) { 459 return null; 460 } 461 462 // Ignore if not equality expression 463 if (_exp instanceof EqualityExpr) { 464 EqualityExpr exp = (EqualityExpr)_exp; 465 Expression left = exp.getLeft(); 466 Expression right = exp.getRight(); 467 468 // Unwrap and set _step if appropriate 469 if (left instanceof CastExpr) { 470 left = ((CastExpr) left).getExpr(); 471 } 472 if (left instanceof Step) { 473 _step = (Step) left; 474 } 475 476 // Unwrap and set _step if appropriate 477 if (right instanceof CastExpr) { 478 right = ((CastExpr)right).getExpr(); 479 } 480 if (right instanceof Step) { 481 _step = (Step)right; 482 } 483 } 484 return _step; 485 } 486 487 /** 488 * Returns the value in an expression of the form 'step = value'. 489 * A value may be either a literal string or a variable whose 490 * type is string. Optimization if off if null is returned. 491 */ 492 public Expression getCompareValue() { 493 // Returned cached value if called more than once 494 if (_value != null) { 495 return _value; 496 } 497 498 // Nothing to to do if _exp is null 499 if (_exp == null) { 500 return null; 501 } 502 503 // Ignore if not an equality expression 504 if (_exp instanceof EqualityExpr) { 505 EqualityExpr exp = (EqualityExpr) _exp; 506 Expression left = exp.getLeft(); 507 Expression right = exp.getRight(); 508 509 // Return if left is literal string 510 if (left instanceof LiteralExpr) { 511 _value = left; 512 return _value; 513 } 514 // Return if left is a variable reference of type string 515 if (left instanceof VariableRefBase && 516 left.getType() == Type.String) 517 { 518 _value = left; 519 return _value; 520 } 521 522 // Return if right is literal string 523 if (right instanceof LiteralExpr) { 524 _value = right; 525 return _value; 526 } 527 // Return if left is a variable reference whose type is string 528 if (right instanceof VariableRefBase && 529 right.getType() == Type.String) 530 { 531 _value = right; 532 return _value; 533 } 534 } 535 return null; 536 } 537 538 /** 539 * Translate a predicate expression. This translation pushes 540 * two references on the stack: a reference to a newly created 541 * filter object and a reference to the predicate's closure. 542 */ 543 public void translateFilter(ClassGenerator classGen, 544 MethodGenerator methodGen) 545 { 546 final ConstantPoolGen cpg = classGen.getConstantPool(); 547 final InstructionList il = methodGen.getInstructionList(); 548 549 // Compile auxiliary class for filter 550 compileFilter(classGen, methodGen); 551 552 // Create new instance of filter 553 il.append(new NEW(cpg.addClass(_className))); 554 il.append(DUP); 555 il.append(new INVOKESPECIAL(cpg.addMethodref(_className, 556 "<init>", "()V"))); 557 558 // Initialize closure variables 559 final int length = (_closureVars == null) ? 0 : _closureVars.size(); 560 561 for (int i = 0; i < length; i++) { 562 VariableRefBase varRef = (VariableRefBase) _closureVars.get(i); 563 VariableBase var = varRef.getVariable(); 564 Type varType = var.getType(); 565 566 il.append(DUP); 567 568 // Find nearest closure implemented as an inner class 569 Closure variableClosure = _parentClosure; 570 while (variableClosure != null) { 571 if (variableClosure.inInnerClass()) break; 572 variableClosure = variableClosure.getParentClosure(); 573 } 574 575 // Use getfield if in an inner class 576 if (variableClosure != null) { 577 il.append(ALOAD_0); 578 il.append(new GETFIELD( 579 cpg.addFieldref(variableClosure.getInnerClassName(), 580 var.getEscapedName(), varType.toSignature()))); 581 } 582 else { 583 // Use a load of instruction if in translet class 584 il.append(var.loadInstruction()); 585 } 586 587 // Store variable in new closure 588 il.append(new PUTFIELD( 589 cpg.addFieldref(_className, var.getEscapedName(), 590 varType.toSignature()))); 591 } 592 } 593 594 /** 595 * Translate a predicate expression. If non of the optimizations apply 596 * then this translation pushes two references on the stack: a reference 597 * to a newly created filter object and a reference to the predicate's 598 * closure. See class <code>Step</code> for further details. 599 */ 600 public void translate(ClassGenerator classGen, MethodGenerator methodGen) { 601 602 final ConstantPoolGen cpg = classGen.getConstantPool(); 603 final InstructionList il = methodGen.getInstructionList(); 604 605 if (_nthPositionFilter || _nthDescendant) { 606 _exp.translate(classGen, methodGen); 607 } 608 else if (isNodeValueTest() && (getParent() instanceof Step)) { 609 _value.translate(classGen, methodGen); 610 il.append(new CHECKCAST(cpg.addClass(STRING_CLASS))); 611 il.append(new PUSH(cpg, ((EqualityExpr)_exp).getOp())); 612 } 613 else { 614 translateFilter(classGen, methodGen); 615 } 616 } 617 }