All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.FilterExpression Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// 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.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.expr;





import net.sf.saxon.Configuration;
import net.sf.saxon.expr.instruct.Choose;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.*;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.EmptyIterator;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;

import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;



/**
 * 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 implements ContextSwitchingExpression {

    /*@NotNull*/
    private Expression start;
    /*@NotNull*/
    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. (It may depend on last()).
    public static final int FILTERED = 10000;

    //private int isIndexable = 0;

    /**
     * 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(/*@NotNull*/ Expression start, /*@NotNull*/ Expression filter) {
        this.start = start;
        this.filter = filter;
        adoptChildExpression(start);
        adoptChildExpression(filter);
        start.setFiltered(true);
    }

    /**
     * Get a name identifying the kind of expression, in terms meaningful to a user.
     * @return a name identifying the kind of expression, in terms meaningful to a user.
     *         The name will always be in the form of a lexical XML QName, and should match the name used
     *         in explain() output displaying the expression.
     */

    public String getExpressionName() {
        return "filter";
    }

    /**
     * Get the data type of the items returned
     *
     * @param th the type hierarchy cache
     * @return an integer representing the data type
     */

    /*@NotNull*/
    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;
    }
    
    /**
     * @see com.saxonica.bytecode.FilterExpressionCompiler
     * @return the boolean of filter is positional
     * */
    public boolean isFilterIsPositional(){
    	return filterIsPositional;
    }

    /**
     * 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);
    }

    /**
     * Test if the filter always returns a singleton boolean
     *
     * @return true if the filter is a simple boolean expression
     */

    public boolean isSimpleBooleanFilter() {
        return filterIsSingletonBoolean;
    }

    /**
     * Determine whether the filter is a simple independent numeric, that is, an expression
     * that satisfies the following conditions: (a) its value is numeric;
     * (b) the value does not depend on the context item or position;
     * (c) the cardinality is zero or one.
     *
     * @return true if the filter is a numeric value that does not depend on the context item or position
     */

    public boolean isIndependentNumericFilter() {
        return filterIsIndependentNumeric;
    }

    /**
     * Simplify an expression
     *
     * @param visitor the expression visitor
     * @return the simplified expression
     * @throws XPathException if any failure occurs
     */

    /*@NotNull*/
    public Expression simplify(ExpressionVisitor visitor) throws XPathException {

        start = visitor.simplify(start);
        filter = visitor.simplify(filter);

        // ignore the filter if the base expression is an empty sequence
        if (Literal.isEmptySequence(start)) {
            return start;
        }

        // check whether the filter is a constant true() or false()
        if (filter instanceof Literal && !(((Literal)filter).getValue() instanceof NumericValue)) {
            try {
                if (filter.effectiveBooleanValue(visitor.getStaticContext().makeEarlyEvaluationContext())) {
                    return start;
                } else {
                    return Literal.makeEmptySequence();
                }
            } catch (XPathException e) {
                e.maybeSetLocation(this);
                throw e;
            }
        }

        // check whether the filter is [last()] (note, position()=last() is handled elsewhere)

        if (filter instanceof Last) {
            filter = new IsLastExpression(true);
            adoptChildExpression(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)
     */

    /*@NotNull*/
    public Expression typeCheck(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException {
        final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
        Expression start2 = visitor.typeCheck(start, contextItemType);
        if (start2 != start) {
            start = start2;
            adoptChildExpression(start2);
        }
        start.setFiltered(true);
        if (Literal.isEmptySequence(start)) {
            return start;
        }

        Expression filter2 = visitor.typeCheck(filter, new ExpressionVisitor.ContextItemType(start.getItemType(th), false));
        if (filter2 != filter) {
            filter = filter2;
            adoptChildExpression(filter2);
        }

        // The filter expression usually doesn't need to be sorted

        filter2 = ExpressionTool.unsortedIfHomogeneous(visitor.getConfiguration().obtainOptimizer(), filter);
        if (filter2 != filter) {
            filter = filter2;
            adoptChildExpression(filter2);
        }

        // detect head expressions (E[1]) and treat them specially

        if (Literal.isConstantOne(filter)) {
            Expression fie = FirstItemExpression.makeFirstItemExpression(start);
            ExpressionTool.copyLocationInfo(this, fie);
            return fie;
        }

        // determine whether the filter might depend on position
        filterIsPositional = isPositionalFilter(filter, th);

        // 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 (it may depend on last())
        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.
     * 

*

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.

* * @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 net.sf.saxon.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) */ /*@NotNull*/ public Expression optimize(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException { final StaticContext env = visitor.getStaticContext(); final Configuration config = visitor.getConfiguration(); final Optimizer opt = config.obtainOptimizer(); final boolean debug = config.getBooleanProperty(FeatureKeys.TRACE_OPTIMIZER_DECISIONS); final TypeHierarchy th = config.getTypeHierarchy(); Expression start2 = visitor.optimize(start, contextItemType); if (start2 != start) { start = start2; adoptChildExpression(start2); } start.setFiltered(true); Expression originalFilter; try { originalFilter = filter.copy(); } catch (UnsupportedOperationException err) { originalFilter = null; } ExpressionVisitor.ContextItemType baseItemType = new ExpressionVisitor.ContextItemType(start.getItemType(th), false); Expression filter2 = filter.optimize(visitor, baseItemType); if (filter2 != filter) { filter = filter2; adoptChildExpression(filter2); } // The filter expression usually doesn't need to be sorted filter2 = ExpressionTool.unsortedIfHomogeneous(opt, filter); if (filter2 != filter) { filter = filter2; adoptChildExpression(filter2); } // if the result of evaluating the filter cannot include numeric values, then we can use // its effective boolean value ItemType filterType = filter.getItemType(th); if (!th.isSubType(filterType, BuiltInAtomicType.BOOLEAN) && th.relationship(filterType, BuiltInAtomicType.NUMERIC) == TypeHierarchy.DISJOINT) { BooleanFn f = (BooleanFn) SystemFunctionCall.makeSystemFunction("boolean", new Expression[]{filter}); filter = visitor.optimize(f, baseItemType); } // 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()) { if (debug) { opt.trace("Redundant filter removed", start); } return start; } else { if (debug) { opt.trace("Filter expression eliminated because predicate is always false", Literal.makeEmptySequence()); } return Literal.makeEmptySequence(); } } // determine whether the filter might depend on position filterIsPositional = isPositionalFilter(filter, th); filterIsSingletonBoolean = filter.getCardinality() == StaticProperty.EXACTLY_ONE && filter.getItemType(th).equals(BuiltInAtomicType.BOOLEAN); // determine whether the filter is indexable if (!filterIsPositional && !visitor.isOptimizeForStreaming()) { int isIndexable = opt.isIndexableFilter(filter); if (isIndexable == 0 && filter != originalFilter && originalFilter != null) { // perhaps the original filter was indexable; if so, revert to the original // this happens when [@a = 1] is rewritten as [some $x in @a satisfies $x eq 1] // TODO: this rollback mechanism is very unsatisfactory. Better: make the some expression indexable! int origIndexable = opt.isIndexableFilter(originalFilter); if (origIndexable != 0) { isIndexable = origIndexable; filter = originalFilter; adoptChildExpression(originalFilter); } } // If the filter is indexable consider creating a key, or an indexed filter expression // (This happens in Saxon-EE only) if (isIndexable != 0) { boolean contextIsDoc = contextItemType != null && contextItemType.itemType != ErrorType.getInstance() && th.isSubType(contextItemType.itemType, NodeKindTest.DOCUMENT); Expression f = opt.tryIndexedFilter(this, visitor, isIndexable > 0, contextIsDoc); if (f != this) { return f.typeCheck(visitor, contextItemType).optimize(visitor, contextItemType); } } } // if the filter is positional, try changing f[a and b] to f[a][b] to increase // the chances of finishing early. if (filterIsPositional && filter instanceof BooleanExpression && ((BooleanExpression)filter).operator == Token.AND) { BooleanExpression bf = (BooleanExpression)filter; if (isExplicitlyPositional(bf.operand0) && !isExplicitlyPositional(bf.operand1)) { Expression p0 = forceToBoolean(bf.operand0, env.getConfiguration()); Expression p1 = forceToBoolean(bf.operand1, env.getConfiguration()); FilterExpression f1 = new FilterExpression(start, p0); ExpressionTool.copyLocationInfo(this, f1); FilterExpression f2 = new FilterExpression(f1, p1); ExpressionTool.copyLocationInfo(this, f2); if (debug) { opt.trace("Composite filter replaced by nested filter expressions", f2); } return visitor.optimize(f2, contextItemType); } if (isExplicitlyPositional(bf.operand1) && !isExplicitlyPositional(bf.operand0)) { Expression p0 = forceToBoolean(bf.operand0, env.getConfiguration()); Expression p1 = forceToBoolean(bf.operand1, env.getConfiguration()); FilterExpression f1 = new FilterExpression(start, p1); ExpressionTool.copyLocationInfo(this, f1); FilterExpression f2 = new FilterExpression(f1, p0); ExpressionTool.copyLocationInfo(this, f2); if (debug) { opt.trace("Composite filter replaced by nested filter expressions", f2); } return visitor.optimize(f2, contextItemType); } } if (filter instanceof IsLastExpression && ((IsLastExpression)filter).getCondition()) { if (start instanceof Literal) { filter = Literal.makeLiteral(new Int64Value(((Literal)start).getValue().getLength())); } else { return new LastItemExpression(start); } } Expression subsequence = tryToRewritePositionalFilter(visitor); if (subsequence != null) { if (debug) { opt.trace("Rewrote Filter Expression as:", subsequence); } 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) { if (debug) { opt.trace("Subexpression extracted from filter because independent of context", offer.containingExpression); } offer.containingExpression = visitor.optimize(offer.containingExpression, contextItemType); } Expression result = offer.containingExpression; if (result instanceof FilterExpression) { final Sequence sequence = ((FilterExpression) result).tryEarlyEvaluation(visitor); if (sequence != null) { GroundedValue value = SequenceTool.toGroundedValue(sequence); return Literal.makeLiteral(value); } } return result; } /** * For an expression that returns an integer or a sequence of integers, get * a lower and upper bound on the values of the integers that may be returned, from * static analysis. The default implementation returns null, meaning "unknown" or * "not applicable". Other implementations return an array of two IntegerValue objects, * representing the lower and upper bounds respectively. The values * UNBOUNDED_LOWER and UNBOUNDED_UPPER are used by convention to indicate that * the value may be arbitrarily large. The values MAX_STRING_LENGTH and MAX_SEQUENCE_LENGTH * are used to indicate values limited by the size of a string or the size of a sequence. * * @return the lower and upper bounds of integer values in the result, or null to indicate * unknown or not applicable. */ @Override public IntegerValue[] getIntegerBounds() { return start.getIntegerBounds(); } private Sequence 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 && !ExpressionTool.refersToVariableOrFunction(filter) && (filter.getDependencies()&~StaticProperty.DEPENDS_ON_FOCUS) == 0) { XPathContext context = visitor.getStaticContext().makeEarlyEvaluationContext(); return 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; } /** * Add a representation of this expression to a PathMap. The PathMap captures a map of the nodes visited * by an expression in a source tree. * * @param pathMap the PathMap to which the expression should be added * @param pathMapNodeSet the PathMapNodeSet to which the paths embodied in this expression should be added * @return the pathMapNode representing the focus established by this expression, in the case where this * expression is the first operand of a path expression or filter expression */ public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) { PathMap.PathMapNodeSet target = start.addToPathMap(pathMap, pathMapNodeSet); filter.addToPathMap(pathMap, target); return target; } /** * Construct an expression that obtains the effective boolean value of a given expression, * by wrapping it in a call of the boolean() function * * @param in the given expression * @param config the Saxon configuration * @return an expression that wraps the given expression in a call to the fn:boolean() function */ private static Expression forceToBoolean(Expression in, Configuration config) { final TypeHierarchy th = config.getTypeHierarchy(); if (in.getItemType(th).getPrimitiveType() == StandardNames.XS_BOOLEAN) { return in; } return SystemFunctionCall.makeSystemFunction("boolean", new Expression[]{in}); } /** * 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, or saxon:itemAt * @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 (visitor.isOptimizeForStreaming()) { // TODO: we're suppressing these optimizations because they generate expressions that // can't currently be streamed. But in principle the optimizations are still worth doing. return null; } TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); if (filter instanceof Literal) { GroundedValue val = ((Literal)filter).getValue(); if (val instanceof NumericValue) { if (((NumericValue)val).isWholeNumber()) { long lvalue = ((NumericValue)val).longValue(); if (lvalue <= 0) { return Literal.makeEmptySequence(); } else if (lvalue == 1) { return FirstItemExpression.makeFirstItemExpression(start); } else { return new SubscriptExpression(start, filter); } } else { return Literal.makeEmptySequence(); } } else { return (ExpressionTool.effectiveBooleanValue(val.iterate()) ? start : Literal.makeEmptySequence()); } } if (th.isSubType(filter.getItemType(th), BuiltInAtomicType.NUMERIC) && filter.getCardinality() == StaticProperty.EXACTLY_ONE && (filter.getDependencies() & StaticProperty.DEPENDS_ON_FOCUS) == 0) { return new SubscriptExpression(start, filter); } if (filter instanceof ComparisonExpression) { VendorFunctionLibrary lib = visitor.getConfiguration().getVendorFunctionLibrary(); StaticContext env = visitor.getStaticContext(); 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) SystemFunctionCall.makeSystemFunction("exists", new Expression[]{existsArg}); Expression rewrite = tryToRewritePositionalFilterSupport(start, comparand, operator, th, lib, env); if (rewrite == null) { return this; } Expression choice = Choose.makeConditional(exists, rewrite); let.setAction(choice); return let; } else { return tryToRewritePositionalFilterSupport(start, comparand, operator, th, lib, env); } } 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 SystemFunctionCall.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, Literal.makeLiteral(Int64Value.makeIntegerValue(1))); Expression length = new ArithmeticExpression(max, Token.MINUS, minMinusOne); Subsequence subs = (Subsequence) SystemFunctionCall.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, VendorFunctionLibrary lib, StaticContext env) throws XPathException { if (th.isSubType(comparand.getItemType(th), BuiltInAtomicType.INTEGER)) { switch (operator) { case Token.FEQ: { if (Literal.isConstantOne(comparand)) { return FirstItemExpression.makeFirstItemExpression(start); } else if (comparand instanceof Literal && ((IntegerValue)((Literal)comparand).getValue()).asBigInteger().compareTo(BigInteger.ZERO) <= 0) { return Literal.makeEmptySequence(); } else { return new SubscriptExpression(start, comparand); } } case Token.FLT: { Expression[] args = new Expression[3]; args[0] = start; args[1] = Literal.makeLiteral(Int64Value.makeIntegerValue(1)); if (Literal.isAtomic(comparand)) { long n = ((NumericValue)((Literal)comparand).getValue()).longValue(); args[2] = Literal.makeLiteral(Int64Value.makeIntegerValue(n - 1)); } else { args[2] = new ArithmeticExpression( comparand, Token.MINUS, Literal.makeLiteral(Int64Value.makeIntegerValue(1))); } return SystemFunctionCall.makeSystemFunction("subsequence", args); } case Token.FLE: { Expression[] args = new Expression[3]; args[0] = start; args[1] = Literal.makeLiteral(Int64Value.makeIntegerValue(1)); args[2] = comparand; return SystemFunctionCall.makeSystemFunction("subsequence", args); } case Token.FNE: { return SystemFunctionCall.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()).longValue(); args[1] = Literal.makeLiteral(Int64Value.makeIntegerValue(n + 1)); } else { args[1] = new ArithmeticExpression( comparand, Token.PLUS, Literal.makeLiteral(Int64Value.makeIntegerValue(1))); } return SystemFunctionCall.makeSystemFunction("subsequence", args); } case Token.FGE: { return SystemFunctionCall.makeSystemFunction("subsequence", new Expression[]{start, comparand}); } default: throw new IllegalArgumentException("operator"); } } else { // the comparand is not known statically to be an integer switch (operator) { case Token.FEQ: { return new SubscriptExpression(start, comparand); } case Token.FLT: { // rewrite SEQ[position() lt V] as // let $N := V return subsequence(SEQ, 1, if (is-whole-number($N)) then $N-1 else floor($N))) LetExpression let = new LetExpression(); let.setRequiredType(SequenceType.makeSequenceType( comparand.getItemType(th), StaticProperty.ALLOWS_ONE)); let.setVariableQName(new StructuredQName("pp", NamespaceConstant.SAXON, "pp" + let.hashCode())); let.setSequence(comparand); LocalVariableReference isWholeArg = new LocalVariableReference(let); LocalVariableReference arithArg = new LocalVariableReference(let); LocalVariableReference floorArg = new LocalVariableReference(let); Expression isWhole = lib.makeSaxonFunction( "is-whole-number", new Expression[] {isWholeArg}, env, start.getContainer()); Expression minusOne = new ArithmeticExpression( arithArg, Token.MINUS, Literal.makeLiteral(Int64Value.makeIntegerValue(1))); Floor floor = (Floor) SystemFunctionCall.makeSystemFunction( "floor", new Expression[]{floorArg}); Expression choice = Choose.makeConditional(isWhole, minusOne, floor); Subsequence subs = (Subsequence) SystemFunctionCall.makeSystemFunction( "subsequence", new Expression[]{start, Literal.makeLiteral(Int64Value.makeIntegerValue(1)), choice}); let.setAction(subs); //decl.fixupReferences(let); return let; } case Token.FLE: { Floor floor = (Floor) SystemFunctionCall.makeSystemFunction( "floor", new Expression[]{comparand}); return SystemFunctionCall.makeSystemFunction( "subsequence", new Expression[]{start, Literal.makeLiteral(Int64Value.makeIntegerValue(1)), floor}); } case Token.FNE: { // rewrite SEQ[position() ne V] as // let $N := V return remove(SEQ, if (is-whole-number($N)) then xs:integer($N) else 0) LetExpression let = new LetExpression(); ExpressionTool.copyLocationInfo(start, let); let.setRequiredType(SequenceType.makeSequenceType( comparand.getItemType(th), StaticProperty.ALLOWS_ONE)); let.setVariableQName(new StructuredQName("pp", NamespaceConstant.SAXON, "pp" + let.hashCode())); let.setSequence(comparand); LocalVariableReference isWholeArg = new LocalVariableReference(let); LocalVariableReference castArg = new LocalVariableReference(let); Expression isWhole = lib.makeSaxonFunction( "is-whole-number", new Expression[] {isWholeArg}, env, start.getContainer()); ExpressionTool.copyLocationInfo(start, isWhole); Expression cast = new CastExpression(castArg, BuiltInAtomicType.INTEGER, false); ExpressionTool.copyLocationInfo(start, cast); Expression choice = Choose.makeConditional( isWhole, cast, Literal.makeLiteral(Int64Value.makeIntegerValue(0))); Remove rem = (Remove) SystemFunctionCall.makeSystemFunction( "remove", new Expression[]{start, choice}); let.setAction(rem); return let; } case Token.FGT: { // rewrite SEQ[position() gt V] as // let $N := V return subsequence(SEQ, if (is-whole-number($N)) then $N+1 else ceiling($N))) LetExpression let = new LetExpression(); let.setRequiredType(SequenceType.makeSequenceType( comparand.getItemType(th), StaticProperty.ALLOWS_ONE)); let.setVariableQName(new StructuredQName("pp", NamespaceConstant.SAXON, "pp" + let.hashCode())); let.setSequence(comparand); LocalVariableReference isWholeArg = new LocalVariableReference(let); LocalVariableReference arithArg = new LocalVariableReference(let); LocalVariableReference ceilingArg = new LocalVariableReference(let); Expression isWhole = lib.makeSaxonFunction( "is-whole-number", new Expression[] {isWholeArg}, env, start.getContainer()); Expression plusOne = new ArithmeticExpression( arithArg, Token.PLUS, Literal.makeLiteral(Int64Value.makeIntegerValue(1))); Ceiling ceiling = (Ceiling) SystemFunctionCall.makeSystemFunction( "ceiling", new Expression[]{ceilingArg}); Expression choice = Choose.makeConditional(isWhole, plusOne, ceiling); Subsequence subs = (Subsequence) SystemFunctionCall.makeSystemFunction( "subsequence", new Expression[]{start, choice}); let.setAction(subs); return let; } case Token.FGE: { // rewrite SEQ[position() ge V] => subsequence(SEQ, ceiling(V)) Ceiling ceiling = (Ceiling) SystemFunctionCall.makeSystemFunction( "ceiling", new Expression[]{comparand}); return SystemFunctionCall.makeSystemFunction( "subsequence", new Expression[]{start, ceiling}); } default: throw new IllegalArgumentException("operator"); } } } /** * 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.RANGE_INDEPENDENT && start instanceof FilterExpression) { // Try to handle the situation where we have (EXP[PRED1])[PRED2], and // EXP[PRED2] does not depend on the range variable, but PRED1 does TypeHierarchy th = offer.getOptimizer().getConfiguration().getTypeHierarchy(); FilterExpression newfe = promoteIndependentPredicates(offer.bindingList, offer.getOptimizer(), th); if (newfe != this) { return newfe.promote(offer, parent); } } if (!(offer.action == PromotionOffer.UNORDERED && filterIsPositional)) { start = doPromotion(start, offer); } if (offer.action == PromotionOffer.REPLACE_CURRENT) { filter = doPromotion(filter, offer); } else if (offer.action == PromotionOffer.INLINE_VARIABLE_REFERENCES) { 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; } } /** * Replace this expression by an expression that returns the same result but without * regard to order * * @param retainAllNodes true if all nodes in the result must be retained; false * if duplicates can be eliminated */ @Override public Expression unordered(boolean retainAllNodes) throws XPathException { if (!filterIsPositional) { start = start.unordered(retainAllNodes); } return this; } /** * Rearrange a filter expression so that predicates that are independent of a given * set of range variables come first, allowing them to be promoted along with the base * expression * * @param bindings the given set of range variables * @param th the type hierarchy cache * @return the expression after promoting independent predicates */ private FilterExpression promoteIndependentPredicates(Binding[] bindings, Optimizer opt, TypeHierarchy th) { if (!ExpressionTool.dependsOnVariable(start, bindings)) { return this; } if (isPositional(th)) { return this; } if (start instanceof FilterExpression) { FilterExpression fe = (FilterExpression)start; if (fe.isPositional(th)) { return this; } if (!ExpressionTool.dependsOnVariable(fe.filter, bindings)) { return this; } if (!ExpressionTool.dependsOnVariable(filter, bindings)) { FilterExpression result = new FilterExpression( new FilterExpression(fe.start, filter).promoteIndependentPredicates(bindings, opt, th), fe.filter); opt.trace("Reordered filter predicates:", result); return result; } } 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 */ public 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 */ /*@NotNull*/ public Iterator iterateSubExpressions() { return new PairIterator(start, filter); } /** * Get the immediate sub-expressions of this expression, with information about the relationship * of each expression to its parent expression. Default implementation * returns a zero-length array, appropriate for an expression that has no * sub-expressions. * * @return an iterator containing the sub-expressions of this expression */ @Override public Iterator iterateSubExpressionInfo() { return new PairIterator( new SubExpressionInfo(start, true, false, INHERITED_CONTEXT), new SubExpressionInfo(filter, false, true, INSPECTION_CONTEXT) ); } /** * 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 (filter instanceof IsLastExpression && ((IsLastExpression)filter).getCondition()) { return start.getCardinality() & ~StaticProperty.ALLOWS_MANY; } 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(); } /** * Convert this expression to an equivalent XSLT pattern * * @param config the Saxon configuration * @param is30 true if this is XSLT 3.0 * @return the equivalent pattern * @throws net.sf.saxon.trans.XPathException * if conversion is not possible */ @Override public Pattern toPattern(Configuration config, boolean is30) throws XPathException { Expression base = getControllingExpression(); Expression filter = getFilter(); TypeHierarchy th = config.getTypeHierarchy(); Pattern basePattern = base.toPattern(config, is30); if (!isPositional(th)) { return new PatternWithPredicate(basePattern, filter); } else if (basePattern instanceof ItemTypePattern && basePattern.getItemType() instanceof NodeTest && filterIsPositional && (filter.getDependencies()&StaticProperty.DEPENDS_ON_LAST) == 0) { return new GeneralPositionalPattern((NodeTest)basePattern.getItemType(), filter); } if (base.getItemType(th) instanceof NodeTest) { return new GeneralNodePattern(this, (NodeTest)base.getItemType(th)); } else { throw new XPathException("The filtered expression in an XSLT 2.0 pattern must be a simple step"); } } /** * 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 */ /*@NotNull*/ public SequenceIterator iterate(XPathContext context) throws XPathException { // Fast path where both operands are constants, or simple variable references Expression startExp = start; Sequence startValue = null; if (startExp instanceof Literal) { startValue = ((Literal)startExp).getValue(); } else if (startExp instanceof VariableReference) { startValue = ((VariableReference)startExp).evaluateVariable(context); if (startValue instanceof GroundedValue) { startExp = Literal.makeLiteral((GroundedValue)startValue); } } if (startValue instanceof EmptySequence) { return EmptyIterator.getInstance(); } Sequence 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) { filterValue = SequenceTool.toGroundedValue(filterValue); if (filterValue instanceof NumericValue) { // Filter is a constant number if (((NumericValue)filterValue).isWholeNumber()) { int pos = (int)(((NumericValue)filterValue).longValue()); if (startValue != null) { // if sequence is a value, use direct indexing Item item = SequenceTool.itemAt(startValue, pos - 1); return (item == null ? EmptyIterator.getInstance() : item.iterate()); } if (pos > 0) { 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 = ((GroundedValue)filterValue).effectiveBooleanValue(); } catch (XPathException err) { err.maybeSetLocation(this); throw err; } if (b) { return start.iterate(context); } else { return EmptyIterator.getInstance(); } } // 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))); } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression */ /*@NotNull*/ public Expression copy() { FilterExpression fe = new FilterExpression(start.copy(), filter.copy()); fe.filterIsIndependentNumeric = filterIsIndependentNumeric; fe.filterIsPositional = filterIsPositional; fe.filterIsSingletonBoolean = filterIsSingletonBoolean; return fe; } /** * 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 ExpressionTool.parenthesize(start) + "[" + filter.toString() + "]"; } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. * * @param out the ExpressionPresenter to be used */ public void explain(ExpressionPresenter out) { out.startElement("filterExpression"); start.explain(out); filter.explain(out); out.endElement(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy