/* * Copyright 2001-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: Predicate.java,v 1.34 2004/02/24 02:58:42 zongaro Exp $ */ package com.sun.org.apache.xalan.internal.xsltc.compiler; import java.util.ArrayList; import com.sun.org.apache.bcel.internal.classfile.Field; import com.sun.org.apache.bcel.internal.generic.ASTORE; import com.sun.org.apache.bcel.internal.generic.CHECKCAST; import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen; import com.sun.org.apache.bcel.internal.generic.GETFIELD; import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL; import com.sun.org.apache.bcel.internal.generic.InstructionList; import com.sun.org.apache.bcel.internal.generic.LocalVariableGen; import com.sun.org.apache.bcel.internal.generic.NEW; import com.sun.org.apache.bcel.internal.generic.PUSH; import com.sun.org.apache.bcel.internal.generic.PUTFIELD; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.BooleanType; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.FilterGenerator; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.IntType; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.NumberType; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ReferenceType; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ResultTreeType; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TestGenerator; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util; /** * @author Jacek Ambroziak * @author Santiago Pericas-Geertsen * @author Morten Jorgensen */ final class Predicate extends Expression implements Closure { /** * The predicate's expression. */ private Expression _exp = null; /** * This flag indicates if optimizations are turned on. The * method dontOptimize() can be called to turn * optimizations off. */ private boolean _canOptimize = true; /** * Flag indicatig if the nth position optimization is on. It * is set in typeCheck(). */ private boolean _nthPositionFilter = false; /** * Flag indicatig if the nth position descendant is on. It * is set in typeCheck(). */ private boolean _nthDescendant = false; /** * Cached node type of the expression that owns this predicate. */ int _ptype = -1; /** * Name of the inner class. */ private String _className = null; /** * List of variables in closure. */ private ArrayList _closureVars = null; /** * Reference to parent closure. */ private Closure _parentClosure = null; /** * Cached value of method getCompareValue(). */ private Expression _value = null; /** * Cached value of method getCompareValue(). */ private Step _step = null; /** * Initializes a predicate. */ public Predicate(Expression exp) { _exp = exp; _exp.setParent(this); } /** * Set the parser for this expression. */ public void setParser(Parser parser) { super.setParser(parser); _exp.setParser(parser); } /** * Returns a boolean value indicating if the nth position optimization * is on. Must be call after type checking! */ public boolean isNthPositionFilter() { return _nthPositionFilter; } /** * Returns a boolean value indicating if the nth descendant optimization * is on. Must be call after type checking! */ public boolean isNthDescendant() { return _nthDescendant; } /** * Turns off all optimizations for this predicate. */ public void dontOptimize() { _canOptimize = false; } /** * Returns true if the expression in this predicate contains a call * to position(). */ public boolean hasPositionCall() { return _exp.hasPositionCall(); } /** * Returns true if the expression in this predicate contains a call * to last(). */ public boolean hasLastCall() { return _exp.hasLastCall(); } // -- Begin Closure interface -------------------- /** * Returns true if this closure is compiled in an inner class (i.e. * if this is a real closure). */ public boolean inInnerClass() { return (_className != null); } /** * Returns a reference to its parent closure or null if outermost. */ public Closure getParentClosure() { if (_parentClosure == null) { SyntaxTreeNode node = getParent(); do { if (node instanceof Closure) { _parentClosure = (Closure) node; break; } if (node instanceof TopLevelElement) { break; // way up in the tree } node = node.getParent(); } while (node != null); } return _parentClosure; } /** * Returns the name of the auxiliary class or null if this predicate * is compiled inside the Translet. */ public String getInnerClassName() { return _className; } /** * Add new variable to the closure. */ public void addVariable(VariableRefBase variableRef) { if (_closureVars == null) { _closureVars = new ArrayList(); } // Only one reference per variable if (!_closureVars.contains(variableRef)) { _closureVars.add(variableRef); // Add variable to parent closure as well Closure parentClosure = getParentClosure(); if (parentClosure != null) { parentClosure.addVariable(variableRef); } } } // -- End Closure interface ---------------------- /** * Returns the node type of the expression owning this predicate. The * return value is cached in _ptype. */ public int getPosType() { if (_ptype == -1) { SyntaxTreeNode parent = getParent(); if (parent instanceof StepPattern) { _ptype = ((StepPattern)parent).getNodeType(); } else if (parent instanceof AbsoluteLocationPath) { AbsoluteLocationPath path = (AbsoluteLocationPath)parent; Expression exp = path.getPath(); if (exp instanceof Step) { _ptype = ((Step)exp).getNodeType(); } } else if (parent instanceof VariableRefBase) { final VariableRefBase ref = (VariableRefBase)parent; final VariableBase var = ref.getVariable(); final Expression exp = var.getExpression(); if (exp instanceof Step) { _ptype = ((Step)exp).getNodeType(); } } else if (parent instanceof Step) { _ptype = ((Step)parent).getNodeType(); } } return _ptype; } public boolean parentIsPattern() { return (getParent() instanceof Pattern); } public Expression getExpr() { return _exp; } public String toString() { return "pred(" + _exp + ')'; } /** * Type check a predicate expression. If the type of the expression is * number convert it to boolean by adding a comparison with position(). * Note that if the expression is a parameter, we cannot distinguish * at compile time if its type is number or not. Hence, expressions of * reference type are always converted to booleans. * * This method may be called twice, before and after calling * dontOptimize(). If so, the second time it should honor * the new value of _canOptimize. */ public Type typeCheck(SymbolTable stable) throws TypeCheckError { Type texp = _exp.typeCheck(stable); // We need explicit type information for reference types - no good! if (texp instanceof ReferenceType) { _exp = new CastExpr(_exp, texp = Type.Real); } // A result tree fragment should not be cast directly to a number type, // but rather to a boolean value, and then to a numer (0 or 1). // Ref. section 11.2 of the XSLT 1.0 spec if (texp instanceof ResultTreeType) { _exp = new CastExpr(_exp, Type.Boolean); _exp = new CastExpr(_exp, Type.Real); texp = _exp.typeCheck(stable); } // Numerical types will be converted to a position filter if (texp instanceof NumberType) { // Cast any numerical types to an integer if (texp instanceof IntType == false) { _exp = new CastExpr(_exp, Type.Int); } if (_canOptimize) { // Nth position optimization. Expression must not depend on context _nthPositionFilter = !_exp.hasLastCall() && !_exp.hasPositionCall(); // _nthDescendant optimization - only if _nthPositionFilter is on if (_nthPositionFilter) { SyntaxTreeNode parent = getParent(); _nthDescendant = (parent instanceof Step) && (parent.getParent() instanceof AbsoluteLocationPath); return _type = Type.NodeSet; } } // Reset optimization flags _nthPositionFilter = _nthDescendant = false; // Otherwise, expand [e] to [position() = e] final QName position = getParser().getQNameIgnoreDefaultNs("position"); final PositionCall positionCall = new PositionCall(position); positionCall.setParser(getParser()); positionCall.setParent(this); _exp = new EqualityExpr(EqualityExpr.EQ, positionCall, _exp); if (_exp.typeCheck(stable) != Type.Boolean) { _exp = new CastExpr(_exp, Type.Boolean); } return _type = Type.Boolean; } else { // All other types will be handled as boolean values if (texp instanceof BooleanType == false) { _exp = new CastExpr(_exp, Type.Boolean); } return _type = Type.Boolean; } } /** * Create a new "Filter" class implementing * CurrentNodeListFilter. Allocate registers for local * variables and local parameters passed in the closure to test(). * Notice that local variables need to be "unboxed". */ private void compileFilter(ClassGenerator classGen, MethodGenerator methodGen) { TestGenerator testGen; LocalVariableGen local; FilterGenerator filterGen; _className = getXSLTC().getHelperClassName(); filterGen = new FilterGenerator(_className, "java.lang.Object", toString(), ACC_PUBLIC | ACC_SUPER, new String[] { CURRENT_NODE_LIST_FILTER }, classGen.getStylesheet()); final ConstantPoolGen cpg = filterGen.getConstantPool(); final int length = (_closureVars == null) ? 0 : _closureVars.size(); // Add a new instance variable for each var in closure for (int i = 0; i < length; i++) { VariableBase var = ((VariableRefBase) _closureVars.get(i)).getVariable(); filterGen.addField(new Field(ACC_PUBLIC, cpg.addUtf8(var.getEscapedName()), cpg.addUtf8(var.getType().toSignature()), null, cpg.getConstantPool())); } final InstructionList il = new InstructionList(); testGen = new TestGenerator(ACC_PUBLIC | ACC_FINAL, com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN, new com.sun.org.apache.bcel.internal.generic.Type[] { com.sun.org.apache.bcel.internal.generic.Type.INT, com.sun.org.apache.bcel.internal.generic.Type.INT, com.sun.org.apache.bcel.internal.generic.Type.INT, com.sun.org.apache.bcel.internal.generic.Type.INT, Util.getJCRefType(TRANSLET_SIG), Util.getJCRefType(NODE_ITERATOR_SIG) }, new String[] { "node", "position", "last", "current", "translet", "iterator" }, "test", _className, il, cpg); // Store the dom in a local variable local = testGen.addLocalVariable("document", Util.getJCRefType(DOM_INTF_SIG), null, null); final String className = classGen.getClassName(); il.append(filterGen.loadTranslet()); il.append(new CHECKCAST(cpg.addClass(className))); il.append(new GETFIELD(cpg.addFieldref(className, DOM_FIELD, DOM_INTF_SIG))); il.append(new ASTORE(local.getIndex())); // Store the dom index in the test generator testGen.setDomIndex(local.getIndex()); _exp.translate(filterGen, testGen); il.append(IRETURN); testGen.stripAttributes(true); testGen.setMaxLocals(); testGen.setMaxStack(); testGen.removeNOPs(); filterGen.addEmptyConstructor(ACC_PUBLIC); filterGen.addMethod(testGen.getMethod()); getXSLTC().dumpClass(filterGen.getJavaClass()); } /** * Returns true if the predicate is a test for the existance of an * element or attribute. All we have to do is to get the first node * from the step, check if it is there, and then return true/false. */ public boolean isBooleanTest() { return (_exp instanceof BooleanExpr); } /** * Method to see if we can optimise the predicate by using a specialised * iterator for expressions like '/foo/bar[@attr = $var]', which are * very common in many stylesheets */ public boolean isNodeValueTest() { if (!_canOptimize) return false; return (getStep() != null && getCompareValue() != null); } /** * Returns the step in an expression of the form 'step = value'. * Null is returned if the expression is not of the right form. * Optimization if off if null is returned. */ public Step getStep() { // Returned cached value if called more than once if (_step != null) { return _step; } // Nothing to do if _exp is null if (_exp == null) { return null; } // Ignore if not equality expression if (_exp instanceof EqualityExpr) { EqualityExpr exp = (EqualityExpr)_exp; Expression left = exp.getLeft(); Expression right = exp.getRight(); // Unwrap and set _step if appropriate if (left instanceof CastExpr) { left = ((CastExpr) left).getExpr(); } if (left instanceof Step) { _step = (Step) left; } // Unwrap and set _step if appropriate if (right instanceof CastExpr) { right = ((CastExpr)right).getExpr(); } if (right instanceof Step) { _step = (Step)right; } } return _step; } /** * Returns the value in an expression of the form 'step = value'. * A value may be either a literal string or a variable whose * type is string. Optimization if off if null is returned. */ public Expression getCompareValue() { // Returned cached value if called more than once if (_value != null) { return _value; } // Nothing to to do if _exp is null if (_exp == null) { return null; } // Ignore if not an equality expression if (_exp instanceof EqualityExpr) { EqualityExpr exp = (EqualityExpr) _exp; Expression left = exp.getLeft(); Expression right = exp.getRight(); // Return if left is literal string if (left instanceof LiteralExpr) { _value = left; return _value; } // Return if left is a variable reference of type string if (left instanceof VariableRefBase && left.getType() == Type.String) { _value = left; return _value; } // Return if right is literal string if (right instanceof LiteralExpr) { _value = right; return _value; } // Return if left is a variable reference whose type is string if (right instanceof VariableRefBase && right.getType() == Type.String) { _value = right; return _value; } } return null; } /** * Translate a predicate expression. This translation pushes * two references on the stack: a reference to a newly created * filter object and a reference to the predicate's closure. */ public void translateFilter(ClassGenerator classGen, MethodGenerator methodGen) { final ConstantPoolGen cpg = classGen.getConstantPool(); final InstructionList il = methodGen.getInstructionList(); // Compile auxiliary class for filter compileFilter(classGen, methodGen); // Create new instance of filter il.append(new NEW(cpg.addClass(_className))); il.append(DUP); il.append(new INVOKESPECIAL(cpg.addMethodref(_className, "", "()V"))); // Initialize closure variables final int length = (_closureVars == null) ? 0 : _closureVars.size(); for (int i = 0; i < length; i++) { VariableRefBase varRef = (VariableRefBase) _closureVars.get(i); VariableBase var = varRef.getVariable(); Type varType = var.getType(); il.append(DUP); // Find nearest closure implemented as an inner class Closure variableClosure = _parentClosure; while (variableClosure != null) { if (variableClosure.inInnerClass()) break; variableClosure = variableClosure.getParentClosure(); } // Use getfield if in an inner class if (variableClosure != null) { il.append(ALOAD_0); il.append(new GETFIELD( cpg.addFieldref(variableClosure.getInnerClassName(), var.getEscapedName(), varType.toSignature()))); } else { // Use a load of instruction if in translet class il.append(var.loadInstruction()); } // Store variable in new closure il.append(new PUTFIELD( cpg.addFieldref(_className, var.getEscapedName(), varType.toSignature()))); } } /** * Translate a predicate expression. If non of the optimizations apply * then this translation pushes two references on the stack: a reference * to a newly created filter object and a reference to the predicate's * closure. See class Step for further details. */ public void translate(ClassGenerator classGen, MethodGenerator methodGen) { final ConstantPoolGen cpg = classGen.getConstantPool(); final InstructionList il = methodGen.getInstructionList(); if (_nthPositionFilter || _nthDescendant) { _exp.translate(classGen, methodGen); } else if (isNodeValueTest() && (getParent() instanceof Step)) { _value.translate(classGen, methodGen); il.append(new CHECKCAST(cpg.addClass(STRING_CLASS))); il.append(new PUSH(cpg, ((EqualityExpr)_exp).getOp())); } else { translateFilter(classGen, methodGen); } } }