package client.net.sf.saxon.ce.expr; import client.net.sf.saxon.ce.expr.instruct.Choose; import client.net.sf.saxon.ce.functions.*; import client.net.sf.saxon.ce.lib.NamespaceConstant; import client.net.sf.saxon.ce.om.NodeInfo; import client.net.sf.saxon.ce.om.SequenceIterator; import client.net.sf.saxon.ce.om.StructuredQName; import client.net.sf.saxon.ce.om.ValueRepresentation; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.tree.iter.EmptyIterator; import client.net.sf.saxon.ce.tree.iter.SingletonIterator; import client.net.sf.saxon.ce.type.AnyItemType; import client.net.sf.saxon.ce.type.BuiltInAtomicType; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.type.TypeHierarchy; import client.net.sf.saxon.ce.value.*; import java.math.BigDecimal; import java.util.Arrays; import java.util.Iterator; /** * A FilterExpression contains a base expression and a filter predicate, which may be an * integer expression (positional filter), or a boolean expression (qualifier) */ public final class FilterExpression extends Expression { private Expression start; private Expression filter; private boolean filterIsPositional; // true if the value of the filter might depend on // the context position private boolean filterIsSingletonBoolean; // true if the filter expression always returns a single boolean private boolean filterIsIndependentNumeric; // true if the filter expression returns a number that doesn't // depend on the context item or position /** * Constructor * * @param start A node-set expression denoting the absolute or relative set of nodes from which the * navigation path should start. * @param filter An expression defining the filter predicate */ public FilterExpression(Expression start, Expression filter) { this.start = start; this.filter = filter; adoptChildExpression(start); adoptChildExpression(filter); } /** * Get the data type of the items returned * * @param th the type hierarchy cache * @return an integer representing the data type */ public ItemType getItemType(TypeHierarchy th) { // special case the filter [. instance of x] if (filter instanceof InstanceOfExpression && ((InstanceOfExpression)filter).getBaseExpression() instanceof ContextItemExpression) { return ((InstanceOfExpression)filter).getRequiredItemType(); } return start.getItemType(th); } /** * Get the underlying expression * * @return the expression being filtered */ public Expression getControllingExpression() { return start; } /** * Get the subexpression that is evaluated in the new context * @return the subexpression evaluated in the context set by the controlling expression */ public Expression getControlledExpression() { return filter; } /** * Get the filter expression * * @return the expression acting as the filter predicate */ public Expression getFilter() { return filter; } /** * Determine if the filter is positional * * @param th the Type Hierarchy (for cached access to type information) * @return true if the value of the filter depends on the position of the item against * which it is evaluated */ public boolean isPositional(TypeHierarchy th) { return isPositionalFilter(filter, th); } /** * Simplify an expression * * @param visitor the expression visitor * @return the simplified expression * @throws XPathException if any failure occurs */ public Expression simplify(ExpressionVisitor visitor) throws XPathException { start = visitor.simplify(start); filter = visitor.simplify(filter); return this; } /** * Type-check the expression * * @param visitor the expression visitor * @param contextItemType the type of the context item for this expression * @return the expression after type-checking (potentially modified to add run-time * checks and/or conversions) */ public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); start = visitor.typeCheck(start, contextItemType); adoptChildExpression(start); Expression filter2 = visitor.typeCheck(filter, start.getItemType(th)); if (filter2 != filter) { filter = filter2; adoptChildExpression(filter2); } if (Literal.isConstantOne(filter)) { return new FirstItemExpression(start); } if (filter instanceof Last) { return new LastItemExpression(start); } // determine whether the filter always evaluates to a single boolean filterIsSingletonBoolean = filter.getCardinality() == StaticProperty.EXACTLY_ONE && filter.getItemType(th).equals(BuiltInAtomicType.BOOLEAN); // determine whether the filter evaluates to a single number, where the number will be the same for // all values in the sequence filterIsIndependentNumeric = th.isSubType(filter.getItemType(th), BuiltInAtomicType.NUMERIC) && (filter.getDependencies() & (StaticProperty.DEPENDS_ON_CONTEXT_ITEM | StaticProperty.DEPENDS_ON_POSITION)) == 0 && !Cardinality.allowsMany(filter.getCardinality()); visitor.resetStaticProperties(); return this; } /** * Perform optimisation of an expression and its subexpressions. * <p/> * <p>This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable, and after all type checking has been done.</p> * * @param visitor an expression visitor * @param contextItemType the static type of "." at the point where this expression is invoked. * The parameter is set to null if it is known statically that the context item will be undefined. * If the type of the context item is not known statically, the argument is set to * {@link client.net.sf.saxon.ce.type.Type#ITEM_TYPE} * @return the original expression, rewritten if appropriate to optimize execution * @throws XPathException if an error is discovered during this phase * (typically a type error) */ public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { final Optimizer opt = visitor.getConfiguration().getOptimizer(); final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); Expression start2 = visitor.optimize(start, contextItemType); if (start2 != start) { start = start2; adoptChildExpression(start2); } Expression filter2 = filter.optimize(visitor, start.getItemType(th)); if (filter2 != filter) { filter = filter2; adoptChildExpression(filter2); } // the filter expression may have been reduced to a constant boolean by previous optimizations if (filter instanceof Literal && ((Literal)filter).getValue() instanceof BooleanValue) { if (((BooleanValue)((Literal)filter).getValue()).getBooleanValue()) { return start; } else { return new Literal(EmptySequence.getInstance()); } } // determine whether the filter might depend on position filterIsPositional = isPositionalFilter(filter, th); filterIsSingletonBoolean = filter.getCardinality() == StaticProperty.EXACTLY_ONE && filter.getItemType(th).equals(BuiltInAtomicType.BOOLEAN); Expression subsequence = tryToRewritePositionalFilter(visitor); if (subsequence != null) { ExpressionTool.copyLocationInfo(this, subsequence); return subsequence.simplify(visitor) .typeCheck(visitor, contextItemType) .optimize(visitor, contextItemType); } // If any subexpressions within the filter are not dependent on the focus, // promote them: this causes them to be evaluated once, outside the filter // expression. Note: we do this even if the filter is numeric, because it ensures that // the subscript is pre-evaluated, allowing direct indexing into the sequence. PromotionOffer offer = new PromotionOffer(opt); offer.action = PromotionOffer.FOCUS_INDEPENDENT; offer.promoteDocumentDependent = (start.getSpecialProperties() & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0; offer.containingExpression = this; filter2 = doPromotion(filter, offer); if (filter2 != filter) { filter = filter2; adoptChildExpression(filter2); } if (offer.containingExpression instanceof LetExpression) { offer.containingExpression = visitor.optimize(offer.containingExpression, contextItemType); } Expression result = offer.containingExpression; if (result instanceof FilterExpression) { Value value = ((FilterExpression)result).tryEarlyEvaluation(visitor); if (value != null) { return new Literal(value); } } return result; } private Value tryEarlyEvaluation(ExpressionVisitor visitor) throws XPathException { // Attempt early evaluation of a filter expression if the base sequence is constant and the // filter depends only on the context. (This can't be done if, for example, the predicate uses // local variables, even variables declared within the predicate) try { if (start instanceof Literal && (filter.getDependencies()&~StaticProperty.DEPENDS_ON_FOCUS) == 0) { XPathContext context = visitor.getStaticContext().makeEarlyEvaluationContext(); return (Value)SequenceExtent.makeSequenceExtent(iterate(context)); } } catch (Exception e) { // can happen for a variety of reasons, for example the filter references a global parameter, // references the doc() function, etc. return null; } return null; } /** * Attempt to rewrite a filter expression whose predicate is a test of the form * [position() op expr] as a call on functions such as subsequence, remove * @param visitor the current expression visitor * @return the rewritten expression if a rewrite was possible, or null otherwise */ private Expression tryToRewritePositionalFilter(ExpressionVisitor visitor) throws XPathException { if (filter instanceof Literal) { Value val = ((Literal)filter).getValue(); if (val instanceof NumericValue) { if (((NumericValue)val).isWholeNumber()) { try { int lvalue = ((NumericValue)val).intValue(); if (lvalue <= 0) { return Literal.makeEmptySequence(); } else if (lvalue == 1) { return new FirstItemExpression(start); } else { return SystemFunction.makeSystemFunction("subsequence", new Expression[]{start, filter, new Literal(IntegerValue.PLUS_ONE)}); } } catch (XPathException err) { // integer out of range return null; } } else { return Literal.makeEmptySequence(); } } else { return (val.effectiveBooleanValue() ? start : Literal.makeEmptySequence()); } } if (filter instanceof ComparisonExpression) { TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); Expression[] operands = ((ComparisonExpression)filter).getOperands(); int operator = ((ComparisonExpression)filter).getSingletonOperator(); Expression comparand; if (operands[0] instanceof Position && th.isSubType(operands[1].getItemType(th), BuiltInAtomicType.NUMERIC)) { comparand = operands[1]; } else if (operands[1] instanceof Position && th.isSubType(operands[0].getItemType(th), BuiltInAtomicType.NUMERIC)) { comparand = operands[0]; operator = Token.inverse(operator); } else { return null; } if (ExpressionTool.dependsOnFocus(comparand)) { return null; } int card = comparand.getCardinality(); if (Cardinality.allowsMany(card)) { return null; } // If the comparand might be an empty sequence, do the base rewrite and then wrap the // rewritten expression EXP in "let $n := comparand if exists($n) then EXP else () if (Cardinality.allowsZero(card)) { LetExpression let = new LetExpression(); let.setRequiredType(SequenceType.makeSequenceType(comparand.getItemType(th), card)); let.setVariableQName(new StructuredQName("pp", NamespaceConstant.SAXON, "pp" + let.hashCode())); let.setSequence(comparand); comparand = new LocalVariableReference(let); LocalVariableReference existsArg = new LocalVariableReference(let); Exists exists = (Exists)SystemFunction.makeSystemFunction("exists", new Expression[]{existsArg}); Expression rewrite = tryToRewritePositionalFilterSupport(start, comparand, operator, th); if (rewrite == null) { return null; } Expression choice = Choose.makeConditional(exists, rewrite); let.setAction(choice); return let; } else { return tryToRewritePositionalFilterSupport(start, comparand, operator, th); } } else if (filter instanceof IntegerRangeTest) { // rewrite SEQ[position() = N to M] // => let $n := N return subsequence(SEQ, $n, (M - ($n - 1)) // (precise form is optimized for the case where $n is a literal, especially N = 1) Expression val = ((IntegerRangeTest)filter).getValueExpression(); if (!(val instanceof Position)) { return null; } Expression min = ((IntegerRangeTest)filter).getMinValueExpression(); Expression max = ((IntegerRangeTest)filter).getMaxValueExpression(); if (ExpressionTool.dependsOnFocus(min)) { return null; } if (ExpressionTool.dependsOnFocus(max)) { if (max instanceof Last) { return SystemFunction.makeSystemFunction("subsequence", new Expression[]{start, min}); } else { return null; } } LetExpression let = new LetExpression(); let.setRequiredType(SequenceType.SINGLE_INTEGER); let.setVariableQName(new StructuredQName("nn", NamespaceConstant.SAXON, "nn" + let.hashCode())); let.setSequence(min); min = new LocalVariableReference(let); LocalVariableReference min2 = new LocalVariableReference(let); Expression minMinusOne = new ArithmeticExpression( min2, Token.MINUS, new Literal(IntegerValue.makeIntegerValue(1))); Expression length = new ArithmeticExpression(max, Token.MINUS, minMinusOne); Subsequence subs = (Subsequence)SystemFunction.makeSystemFunction( "subsequence", new Expression[]{start, min, length}); let.setAction(subs); return let; } else { return null; } } private static Expression tryToRewritePositionalFilterSupport( Expression start, Expression comparand, int operator, TypeHierarchy th) throws XPathException { if (th.isSubType(comparand.getItemType(th), BuiltInAtomicType.INTEGER)) { switch (operator) { case Token.FEQ: { if (Literal.isConstantOne(comparand)) { return new FirstItemExpression(start); } else { return SystemFunction.makeSystemFunction("subsequence", new Expression[]{start, comparand, new Literal(IntegerValue.PLUS_ONE)}); } } case Token.FLT: { Expression[] args = new Expression[3]; args[0] = start; args[1] = new Literal(IntegerValue.makeIntegerValue(1)); if (Literal.isAtomic(comparand)) { long n = ((NumericValue)((Literal)comparand).getValue()).intValue(); args[2] = new Literal(new IntegerValue(new BigDecimal(n - 1))); } else { args[2] = new ArithmeticExpression( comparand, Token.MINUS, new Literal(IntegerValue.makeIntegerValue(1))); } return SystemFunction.makeSystemFunction("subsequence", args); } case Token.FLE: { Expression[] args = new Expression[3]; args[0] = start; args[1] = new Literal(IntegerValue.makeIntegerValue(1)); args[2] = comparand; return SystemFunction.makeSystemFunction("subsequence", args); } case Token.FNE: { return SystemFunction.makeSystemFunction("remove", new Expression[]{start, comparand}); } case Token.FGT: { Expression[] args = new Expression[2]; args[0] = start; if (Literal.isAtomic(comparand)) { long n = ((NumericValue)((Literal)comparand).getValue()).intValue(); args[1] = new Literal(new IntegerValue(new BigDecimal(n + 1))); } else { args[1] = new ArithmeticExpression( comparand, Token.PLUS, new Literal(IntegerValue.makeIntegerValue(1))); } return SystemFunction.makeSystemFunction("subsequence", args); } case Token.FGE: { return SystemFunction.makeSystemFunction("subsequence", new Expression[]{start, comparand}); } default: throw new IllegalArgumentException("operator"); } } else { return null; } } /** * Promote this expression if possible * * @param offer details of the promotion that is possible * @param parent * @return the promoted expression (or the original expression, unchanged) */ public Expression promote(PromotionOffer offer, Expression parent) throws XPathException { Expression exp = offer.accept(parent, this); if (exp != null) { return exp; } else { if (!(offer.action == PromotionOffer.UNORDERED && filterIsPositional)) { start = doPromotion(start, offer); } if (offer.action == PromotionOffer.REPLACE_CURRENT) { filter = doPromotion(filter, offer); } else { // Don't pass on other requests. We could pass them on, but only after augmenting // them to say we are interested in subexpressions that don't depend on either the // outer context or the inner context. } return this; } } /** * Determine whether an expression, when used as a filter, is potentially positional; * that is, where it either contains a call on position() or last(), or where it is capable of returning * a numeric result. * * @param exp the expression to be examined * @param th the type hierarchy cache * @return true if the expression depends on position() or last() explicitly or implicitly */ private static boolean isPositionalFilter(Expression exp, TypeHierarchy th) { ItemType type = exp.getItemType(th); if (type.equals(BuiltInAtomicType.BOOLEAN)) { // common case, get it out of the way quickly return isExplicitlyPositional(exp); } return (type.equals(BuiltInAtomicType.ANY_ATOMIC) || type instanceof AnyItemType || type.equals(BuiltInAtomicType.INTEGER) || type.equals(BuiltInAtomicType.NUMERIC) || th.isSubType(type, BuiltInAtomicType.NUMERIC) || isExplicitlyPositional(exp)); } /** * Determine whether an expression, when used as a filter, has an explicit dependency on position() or last() * * @param exp the expression being tested * @return true if the expression is explicitly positional, that is, if it contains an explicit call on * position() or last() */ private static boolean isExplicitlyPositional(Expression exp) { return (exp.getDependencies() & (StaticProperty.DEPENDS_ON_POSITION | StaticProperty.DEPENDS_ON_LAST)) != 0; } /** * Get the immediate subexpressions of this expression * * @return the subexpressions, as an array */ public Iterator<Expression> iterateSubExpressions() { return Arrays.asList((new Expression[]{start, filter})).iterator(); } /** * Given an expression that is an immediate child of this expression, test whether * the evaluation of the parent expression causes the child expression to be * evaluated repeatedly * * @param child the immediate subexpression * @return true if the child expression is evaluated repeatedly */ public boolean hasLoopingSubexpression(Expression child) { return child == filter; } /** * Replace one subexpression by a replacement subexpression * * @param original the original subexpression * @param replacement the replacement subexpression * @return true if the original subexpression is found */ public boolean replaceSubExpression(Expression original, Expression replacement) { boolean found = false; if (start == original) { start = replacement; found = true; } if (filter == original) { filter = replacement; found = true; } return found; } /** * Get the static cardinality of this expression * * @return the cardinality. The method attempts to determine the case where the * filter predicate is guaranteed to select at most one item from the sequence being filtered */ public int computeCardinality() { if (filter instanceof Literal && ((Literal)filter).getValue() instanceof NumericValue) { if (((NumericValue)((Literal)filter).getValue()).compareTo(1) == 0 && !Cardinality.allowsZero(start.getCardinality())) { return StaticProperty.ALLOWS_ONE; } else { return StaticProperty.ALLOWS_ZERO_OR_ONE; } } if (filterIsIndependentNumeric) { return StaticProperty.ALLOWS_ZERO_OR_ONE; } if (!Cardinality.allowsMany(start.getCardinality())) { return StaticProperty.ALLOWS_ZERO_OR_ONE; } return StaticProperty.ALLOWS_ZERO_OR_MORE; } /** * Get the static properties of this expression (other than its type). The result is * bit-significant. These properties are used for optimizations. In general, if * property bit is set, it is true, but if it is unset, the value is unknown. * * @return the static properties of the expression, as a bit-significant value */ public int computeSpecialProperties() { return start.getSpecialProperties(); } /** * Is this expression the same as another expression? * * @param other the expression to be compared with this one * @return true if the two expressions are statically equivalent */ public boolean equals(Object other) { if (other instanceof FilterExpression) { FilterExpression f = (FilterExpression)other; return (start.equals(f.start) && filter.equals(f.filter)); } return false; } /** * get HashCode for comparing two expressions * * @return the hash code */ public int hashCode() { return "FilterExpression".hashCode() + start.hashCode() + filter.hashCode(); } /** * Iterate over the results, returning them in the correct order * * @param context the dynamic context for the evaluation * @return an iterator over the expression results * @throws XPathException if any dynamic error occurs */ public SequenceIterator iterate(XPathContext context) throws XPathException { // Fast path where both operands are constants, or simple variable references Expression startExp = start; Value startValue = null; if (startExp instanceof Literal) { startValue = ((Literal)startExp).getValue(); } else if (startExp instanceof VariableReference) { startValue = Value.asValue(((VariableReference)startExp).evaluateVariable(context)); startExp = new Literal(startValue); } if (startValue instanceof EmptySequence) { return EmptyIterator.getInstance(); } ValueRepresentation filterValue = null; if (filter instanceof Literal) { filterValue = ((Literal)filter).getValue(); } else if (filter instanceof VariableReference) { filterValue = ((VariableReference)filter).evaluateVariable(context); } // Handle the case where the filter is a value. Because of earlier static rewriting, this covers // all cases where the filter expression is independent of the context, that is, where the // value of the filter expression is the same for all items in the sequence being filtered. if (filterValue != null) { if (filterValue instanceof Value) { filterValue = ((Value)filterValue).reduce(); if (filterValue instanceof NumericValue) { // Filter is a constant number if (((NumericValue)filterValue).isWholeNumber()) { int pos = (int)(((NumericValue)filterValue).intValue()); if (startValue != null) { // if sequence is a value, use direct indexing - unless its a Closure! return SingletonIterator.makeIterator(startValue.itemAt(pos - 1)); } if (pos >= 1) { SequenceIterator base = startExp.iterate(context); return SubsequenceIterator.make(base, pos, pos); } else { // index is less than one, no items will be selected return EmptyIterator.getInstance(); } } else { // a non-integer value will never be equal to position() return EmptyIterator.getInstance(); } } // Filter is a value that we can treat as boolean boolean b; try { b = ((Value)filterValue).effectiveBooleanValue(); } catch (XPathException err) { err.maybeSetLocation(getSourceLocator()); throw err; } if (b) { return start.iterate(context); } else { return EmptyIterator.getInstance(); } } else if (filterValue instanceof NodeInfo) { return start.iterate(context); } } // get an iterator over the base nodes SequenceIterator base = startExp.iterate(context); // quick exit for an empty sequence if (base instanceof EmptyIterator) { return base; } if (filterIsPositional && !filterIsSingletonBoolean) { return new FilterIterator(base, filter, context); } else { return new FilterIterator.NonNumeric(base, filter, context); } } /** * Determine which aspects of the context the expression depends on. The result is * a bitwise-or'ed value composed from constants such as XPathContext.VARIABLES and * XPathContext.CURRENT_NODE * * @return the dependencies */ public int computeDependencies() { // not all dependencies in the filter expression matter, because the context node, // position, and size are not dependent on the outer context. return (start.getDependencies() | (filter.getDependencies() & (StaticProperty.DEPENDS_ON_XSLT_CONTEXT | StaticProperty.DEPENDS_ON_LOCAL_VARIABLES | StaticProperty.DEPENDS_ON_USER_FUNCTIONS))); } /** * The toString() method for an expression attempts to give a representation of the expression * in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath. * In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax */ public String toString() { return "(" + start.toString() + "[" + filter.toString() + "])"; } } // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.