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

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

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.Block;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.DocumentSorter;
import net.sf.saxon.expr.sort.GlobalOrderComparer;
import net.sf.saxon.functions.CurrentGroupCall;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.AxisInfo;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.pattern.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.SequenceType;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * An expression representing a nodeset that is a union, difference, or
 * intersection of two other NodeSets
 */

public class VennExpression extends BinaryExpression {

    /**
     * Constructor
     *
     * @param p1 the left-hand operand
     * @param op the operator (union, intersection, or difference)
     * @param p2 the right-hand operand
     */

    public VennExpression(final Expression p1, final int op, final Expression p2) {
        super(p1, op, p2);
    }

    /**
     * Simplify an expression. This performs any static optimization (by rewriting the expression
     * as a different expression). The default implementation simplifies its operands.
     *
     * @return the simplified expression (or the original if unchanged, or if modified in-situ)
     * @throws net.sf.saxon.trans.XPathException if an error is discovered during expression
     *                                           rewriting
     */
    @Override
    public Expression simplify() throws XPathException {
        // Force both operands to be sorted in document order. If this turns out to be unnecessary, it will
        // get optimized away
        if (!(getLhsExpression() instanceof DocumentSorter)) {
            setLhsExpression(new DocumentSorter(getLhsExpression()));
        }
        if (!(getRhsExpression() instanceof DocumentSorter)) {
            setRhsExpression(new DocumentSorter(getRhsExpression()));
        }
        super.simplify();
        return this;
    }

    /**
     * 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.
     */

    @Override
    public String getExpressionName() {
        switch (operator) {
            case Token.UNION:
                return "union";
            case Token.INTERSECT:
                return "intersect";
            case Token.EXCEPT:
                return "except";
            default:
                return "unknown";
        }
    }

    /**
     * Determine the data type of the items returned by this expression
     *
     * @return the data type
     */

    /*@NotNull*/
    @Override
    public final ItemType getItemType() {
        final ItemType t1 = getLhsExpression().getItemType();
        if (operator == Token.UNION) {
            ItemType t2 = getRhsExpression().getItemType();
            TypeHierarchy th = getConfiguration().getTypeHierarchy();
            return Type.getCommonSuperType(t1, t2, th);
        } else {
            return t1;
        }
    }

    /**
     * Get the static type of the expression as a UType, following precisely the type
     * inference rules defined in the XSLT 3.0 specification.
     *
     * @return the static item type of the expression according to the XSLT 3.0 defined rules
     * @param contextItemType the static type of the context item
     */
    @Override
    public UType getStaticUType(UType contextItemType) {
        switch (operator) {
            case Token.UNION:
                return getLhsExpression().getStaticUType(contextItemType).union(getRhsExpression().getStaticUType(contextItemType));
            case Token.INTERSECT:
                return getLhsExpression().getStaticUType(contextItemType).intersection(getRhsExpression().getStaticUType(contextItemType));
            case Token.EXCEPT:
            default:
                return getLhsExpression().getStaticUType(contextItemType);
        }
    }


    /**
     * Determine the static cardinality of the expression
     */

    @Override
    protected final int computeCardinality() {
        final int c1 = getLhsExpression().getCardinality();
        final int c2 = getRhsExpression().getCardinality();
        switch (operator) {
            case Token.UNION:
                if (Literal.isEmptySequence(getLhsExpression())) {
                    return c2;
                }
                if (Literal.isEmptySequence(getRhsExpression())) {
                    return c1;
                }
                return c1 | c2 | StaticProperty.ALLOWS_ONE | StaticProperty.ALLOWS_MANY;
            // allows ZERO only if one operand allows ZERO
            case Token.INTERSECT:
                if (Literal.isEmptySequence(getLhsExpression())) {
                    return StaticProperty.EMPTY;
                }
                if (Literal.isEmptySequence(getRhsExpression())) {
                    return StaticProperty.EMPTY;
                }
                return (c1 & c2) | StaticProperty.ALLOWS_ZERO | StaticProperty.ALLOWS_ONE;
            // allows MANY only if both operands allow MANY
            case Token.EXCEPT:
                if (Literal.isEmptySequence(getLhsExpression())) {
                    return StaticProperty.EMPTY;
                }
                if (Literal.isEmptySequence(getRhsExpression())) {
                    return c1;
                }
                return c1 | StaticProperty.ALLOWS_ZERO | StaticProperty.ALLOWS_ONE;
            // allows MANY only if first operand allows MANY
        }
        return StaticProperty.ALLOWS_ZERO_OR_MORE;
    }

    /**
     * Get the static properties of this expression (other than its type). The result is
     * bit-signficant. 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.
     */

    @Override
    protected int computeSpecialProperties() {
        final int prop0 = getLhsExpression().getSpecialProperties();
        final int prop1 = getRhsExpression().getSpecialProperties();
        int props = StaticProperty.ORDERED_NODESET;
        if (testContextDocumentNodeSet(prop0, prop1)) {
            props |= StaticProperty.CONTEXT_DOCUMENT_NODESET;
        }
        if (testSubTree(prop0, prop1)) {
            props |= StaticProperty.SUBTREE_NODESET;
        }
        if (createsNoNewNodes(prop0, prop1)) {
            props |= StaticProperty.NO_NODES_NEWLY_CREATED;
        }
        return props;
    }

    /**
     * Determine whether all the nodes in the node-set are guaranteed to
     * come from the same document as the context node. Used for optimization.
     *
     * @param prop0 contains the Context Document Nodeset property of the first operand
     * @param prop1 contains the Context Document Nodeset property of the second operand
     * @return true if all the nodes come from the context document
     */

    private boolean testContextDocumentNodeSet(final int prop0, final int prop1) {
        switch (operator) {
            case Token.UNION:
                return (prop0 & prop1 & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0;
            case Token.INTERSECT:
                return ((prop0 | prop1) & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0;
            case Token.EXCEPT:
                return (prop0 & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0;
        }
        return false;
    }

    /**
     * Gather the component operands of a union or intersect expression
     *
     * @param operator union or intersect
     * @param set      the set into which the components are to be gathered. If the operator
     *                 is union, this follows the tree gathering all operands of union expressions. Ditto,
     *                 mutatis mutandis, for intersect expressions.
     */

    public void gatherComponents(int operator, Set set) {
        if (getLhsExpression() instanceof VennExpression && ((VennExpression) getLhsExpression()).operator == operator) {
            ((VennExpression) getLhsExpression()).gatherComponents(operator, set);
        } else {
            set.add(getLhsExpression());
        }
        if (getRhsExpression() instanceof VennExpression && ((VennExpression) getRhsExpression()).operator == operator) {
            ((VennExpression) getRhsExpression()).gatherComponents(operator, set);
        } else {
            set.add(getRhsExpression());
        }
    }

    /**
     * Determine whether all the nodes in the node-set are guaranteed to
     * come from a subtree rooted at the context node. Used for optimization.
     *
     * @param prop0 contains the SubTree property of the first operand
     * @param prop1 contains the SubTree property of the second operand
     * @return true if all the nodes come from the tree rooted at the context node
     */

    private boolean testSubTree(final int prop0, final int prop1) {
        switch (operator) {
            case Token.UNION:
                return (prop0 & prop1 & StaticProperty.SUBTREE_NODESET) != 0;
            case Token.INTERSECT:
                return ((prop0 | prop1) & StaticProperty.SUBTREE_NODESET) != 0;
            case Token.EXCEPT:
                return (prop0 & StaticProperty.SUBTREE_NODESET) != 0;
        }
        return false;
    }

    /**
     * Determine whether the expression can create new nodes
     *
     * @param prop0 contains the noncreative property of the first operand
     * @param prop1 contains the noncreative property of the second operand
     * @return true if the expression can create new nodes
     */

    private boolean createsNoNewNodes(final int prop0, final int prop1) {
        return (prop0 & StaticProperty.NO_NODES_NEWLY_CREATED) != 0 &&
                (prop1 & StaticProperty.NO_NODES_NEWLY_CREATED) != 0;
    }

    /**
     * Type-check the expression
     */

    /*@NotNull*/
    @Override
    public Expression typeCheck(ExpressionVisitor visitor, final ContextItemStaticInfo contextInfo) throws XPathException {

        Configuration config = visitor.getConfiguration();
        TypeChecker tc = config.getTypeChecker(false);
        getLhs().typeCheck(visitor, contextInfo);
        getRhs().typeCheck(visitor, contextInfo);

        if (!(getLhsExpression() instanceof Pattern)) {
            final RoleDiagnostic role0 = new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 0);
            setLhsExpression(tc.staticTypeCheck(getLhsExpression(), SequenceType.NODE_SEQUENCE, role0, visitor));
        }

        if (!(getRhsExpression() instanceof Pattern)) {
            final RoleDiagnostic role1 = new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 1);
            setRhsExpression(tc.staticTypeCheck(getRhsExpression(), SequenceType.NODE_SEQUENCE, role1, visitor));
        }

        // For the intersect and except operators, if the types are disjoint then we can simplify
        if (operator != Token.UNION) {
            TypeHierarchy th = config.getTypeHierarchy();
            ItemType t0 = getLhsExpression().getItemType();
            ItemType t1 = getRhsExpression().getItemType();
            if (th.relationship(t0, t1) == Affinity.DISJOINT) {
                if (operator == Token.INTERSECT) {
                    return Literal.makeEmptySequence();
                } else {
                    if (getLhsExpression().hasSpecialProperty(StaticProperty.ORDERED_NODESET)) {
                        return getLhsExpression();
                    } else {
                        return new DocumentSorter(getLhsExpression());
                    }
                }
            }
        }

        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 net.sf.saxon.trans.XPathException * if an error is discovered during this phase * (typically a type error) */ /*@NotNull*/ @Override public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException { Expression e = super.optimize(visitor, contextItemType); if (e != this) { return e; } final Configuration config = visitor.getConfiguration(); final TypeHierarchy th = config.getTypeHierarchy(); // If either operand is an empty sequence, simplify the expression. This can happen // after reduction with constructs of the form //a[condition] | //b[not(condition)], // common in XPath 1.0 because there were no conditional expressions. Expression lhs = getLhsExpression(); Expression rhs = getRhsExpression(); switch (operator) { case Token.UNION: if (Literal.isEmptySequence(lhs) && (rhs.getSpecialProperties() & StaticProperty.ORDERED_NODESET) != 0) { return rhs; } if (Literal.isEmptySequence(rhs) && (lhs.getSpecialProperties() & StaticProperty.ORDERED_NODESET) != 0) { return lhs; } if (contextItemWithCurrentGroup(lhs, rhs)) { return rhs; } if (contextItemWithCurrentGroup(rhs, lhs)) { return lhs; } break; case Token.INTERSECT: if (Literal.isEmptySequence(lhs)) { return lhs; } if (Literal.isEmptySequence(rhs)) { return rhs; } if (contextItemWithCurrentGroup(lhs, rhs)) { return lhs; } if (contextItemWithCurrentGroup(rhs, lhs)) { return rhs; } break; case Token.EXCEPT: if (Literal.isEmptySequence(lhs)) { return lhs; } if (Literal.isEmptySequence(rhs) && (lhs.getSpecialProperties() & StaticProperty.ORDERED_NODESET) != 0) { return lhs; } if (contextItemWithCurrentGroup(lhs, rhs)) { return Literal.makeEmptySequence(); } if (contextItemWithCurrentGroup(rhs, lhs)) { // Test case si-group-055. // The streaming code has problems with (current-group() except .) so we // optimize it away. This is a bit of a hack, because the difficulty may // affect other expressions as well. The problem arises because part of the // pattern needs to be evaluated with each node in the group as anchor node, // and part with only the first node in the group as anchor node. return new TailExpression(lhs, 2); } break; } // If both are axis expressions on the same axis, merge them // ie. rewrite (axis::test1 | axis::test2) as axis::(test1 | test2) if (lhs instanceof AxisExpression && rhs instanceof AxisExpression) { final AxisExpression a1 = (AxisExpression) lhs; final AxisExpression a2 = (AxisExpression) rhs; if (a1.getAxis() == a2.getAxis()) { if (a1.getNodeTest().equals(a2.getNodeTest())) { if (operator == Token.EXCEPT) { return Literal.makeEmptySequence(); } else { return a1; } } else { AxisExpression ax = new AxisExpression(a1.getAxis(), new CombinedNodeTest(a1.getNodeTest(), operator, a2.getNodeTest())); ExpressionTool.copyLocationInfo(this, ax); return ax; } } } // If both are path expressions starting the same way, merge them // i.e. rewrite (/X | /Y) as /(X|Y). This applies recursively, so that // /A/B/C | /A/B/D becomes /A/B/child::(C|D) // This optimization was previously done for all three operators. However, it's not safe for "except": // A//B except A//C//B cannot be rewritten as A/descendant-or-self::node()/(B except C//B). As a quick // fix, the optimization has been retained for "union" but dropped for "intersect" and "except". Need to // do a more rigorous analysis of the conditions under which it is safe. // TODO: generalize this code to handle all distributive operators, and expressions involving multiple // unions (p/x | p/y | p/z) if (lhs instanceof SlashExpression && rhs instanceof SlashExpression && operator == Token.UNION) { final SlashExpression path1 = (SlashExpression) lhs; final SlashExpression path2 = (SlashExpression) rhs; if (path1.getFirstStep().isEqual(path2.getFirstStep())) { final VennExpression venn = new VennExpression( path1.getRemainingSteps(), operator, path2.getRemainingSteps()); ExpressionTool.copyLocationInfo(this, venn); final Expression path = ExpressionTool.makePathExpression(path1.getFirstStep(), venn); ExpressionTool.copyLocationInfo(this, path); return path.optimize(visitor, contextItemType); } } // Try merging two non-positional filter expressions: // A[exp0] | A[exp1] becomes A[exp0 or exp1] if (lhs instanceof FilterExpression && rhs instanceof FilterExpression) { final FilterExpression exp0 = (FilterExpression) lhs; final FilterExpression exp1 = (FilterExpression) rhs; if (!exp0.isPositional(th) && !exp1.isPositional(th) && exp0.getSelectExpression().isEqual(exp1.getSelectExpression())) { final Expression filter; switch (operator) { case Token.UNION: filter = new OrExpression(exp0.getFilter(), exp1.getFilter()); break; case Token.INTERSECT: filter = new AndExpression(exp0.getFilter(), exp1.getFilter()); break; case Token.EXCEPT: Expression negate2 = SystemFunction.makeCall("not", getRetainedStaticContext(), exp1.getFilter()); filter = new AndExpression(exp0.getFilter(), negate2); break; default: throw new AssertionError("Unknown operator " + operator); } ExpressionTool.copyLocationInfo(this, filter); FilterExpression f = new FilterExpression(exp0.getSelectExpression(), filter); ExpressionTool.copyLocationInfo(this, f); return f.simplify().typeCheck(visitor, contextItemType).optimize(visitor, contextItemType); } } // Convert @*|node() into @*,node() to eliminate the sorted merge operation // Avoid doing this when streaming because xsl:value-of select="@*,node()" is not currently streamable if (!visitor.isOptimizeForStreaming() && operator == Token.UNION && lhs instanceof AxisExpression && rhs instanceof AxisExpression) { AxisExpression a0 = (AxisExpression) lhs; AxisExpression a1 = (AxisExpression) rhs; if (a0.getAxis() == AxisInfo.ATTRIBUTE && a1.getAxis() == AxisInfo.CHILD) { return new Block(new Expression[]{lhs, rhs}); } else if (a1.getAxis() == AxisInfo.ATTRIBUTE && a0.getAxis() == AxisInfo.CHILD) { return new Block(new Expression[]{rhs, lhs}); } } // Convert (A intersect B) to use a serial search where one operand is a singleton if (operator == Token.INTERSECT && !Cardinality.allowsMany(lhs.getCardinality())) { return new SingletonIntersectExpression(lhs, operator, rhs.unordered(false, false)); } if (operator == Token.INTERSECT && !Cardinality.allowsMany(rhs.getCardinality())) { return new SingletonIntersectExpression(rhs, operator, lhs.unordered(false, false)); } // If the types of the operands are disjoint, simplify "intersect" and "except" if (operandsAreDisjoint(th)) { if (operator == Token.INTERSECT) { return Literal.makeEmptySequence(); } else if (operator == Token.EXCEPT) { if ((lhs.getSpecialProperties() & StaticProperty.ORDERED_NODESET) != 0) { return lhs; } else { return new DocumentSorter(lhs); } } } return this; } private boolean operandsAreDisjoint(TypeHierarchy th) { return th.relationship(getLhsExpression().getItemType(), getRhsExpression().getItemType()) == Affinity.DISJOINT; } /** * Return true if the operands are, respectively, "." and "current-group()", and * if the context-setting scope for both operands is the same. This implies that the context * item must necessarily be a member of the current group. * * @param lhs the left-hand operand * @param rhs the right-hand operand * @return true if the LHS is "." and the RHS is "current-group()" and they the focus * setting container is the containing xsl:for-each-group instruction */ private boolean contextItemWithCurrentGroup(Expression lhs, Expression rhs) { if (lhs instanceof ContextItemExpression && rhs instanceof CurrentGroupCall) { Expression focusSetter = ExpressionTool.getFocusSettingContainer(lhs); Expression forEachGroup = ((CurrentGroupCall) rhs).getControllingInstruction(); return forEachGroup != null && focusSetter == forEachGroup; } return false; } /** * 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 * @param forStreaming set to true if optimizing for streaming */ @Override public Expression unordered(boolean retainAllNodes, boolean forStreaming) { if (operator == Token.UNION && !forStreaming && operandsAreDisjoint(getConfiguration().getTypeHierarchy())) { // replace union operator by comma operator to avoid cost of sorting into document order. See XMark q7 Block block = new Block(new Expression[]{getLhsExpression(), getRhsExpression()}); ExpressionTool.copyLocationInfo(this, block); return block; } return this; } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression * @param rebindings variables that need to be rebound */ /*@NotNull*/ @Override public Expression copy(RebindingMap rebindings) { VennExpression exp = new VennExpression(getLhsExpression().copy(rebindings), operator, getRhsExpression().copy(rebindings)); ExpressionTool.copyLocationInfo(this, exp); return exp; } /** * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process(). * This method indicates which of these methods is provided directly. The other methods will always be available * indirectly, using an implementation that relies on one of the other methods. * * @return the implementation method, for example {@link #ITERATE_METHOD} or {@link #EVALUATE_METHOD} or * {@link #PROCESS_METHOD} */ @Override public int getImplementationMethod() { return ITERATE_METHOD; } /** * Get the operand role (applies to both operands) * @return the operand role * @param arg which argument: 0 for the lhs, 1 for the rhs */ @Override protected OperandRole getOperandRole(int arg) { return OperandRole.SAME_FOCUS_ACTION; } /** * Is this expression the same as another expression? */ public boolean equals(Object other) { // NOTE: it's possible that the method in the superclass is already adequate for this if (other instanceof VennExpression) { VennExpression b = (VennExpression) other; if (operator != b.operator) { return false; } if (getLhsExpression().isEqual(b.getLhsExpression()) && getRhsExpression().isEqual(b.getRhsExpression())) { return true; } if (operator == Token.UNION || operator == Token.INTERSECT) { // These are commutative and associative, so for example (A|B)|C equals B|(A|C) Set s0 = new HashSet<>(10); gatherComponents(operator, s0); Set s1 = new HashSet<>(10); ((VennExpression) other).gatherComponents(operator, s1); return s0.equals(s1); } } return false; } @Override protected int computeHashCode() { return getLhsExpression().hashCode() ^ getRhsExpression().hashCode(); } /** * Convert this expression to an equivalent XSLT pattern * * @param config the Saxon configuration * @return the equivalent pattern * @throws net.sf.saxon.trans.XPathException * if conversion is not possible */ @Override public Pattern toPattern(Configuration config) throws XPathException { if (isPredicatePattern(getLhsExpression()) || isPredicatePattern(getRhsExpression())) { throw new XPathException( "Cannot use a predicate pattern as an operand of a union, intersect, or except operator", "XTSE0340"); } if (operator == Token.UNION) { return new UnionPattern( getLhsExpression().toPattern(config), getRhsExpression().toPattern(config)); } else { // Bug #5368 means it's dangerous to assume that the expression (A except B) can be translated // into a pattern that matches a node if A matches and B does not. We can only do this in special // cases, in particular (a) where both operands use the attribute or child axis, and (b) where // one of the patterns is anchored at the root of the tree (for example //xxx/yyy or $var/xxx or id('x')/xxx) int commonAxis = ExpressionTool.getAxisNavigation(this); if (commonAxis == AxisInfo.CHILD || commonAxis == AxisInfo.ATTRIBUTE || independentOfContextItem(getLhsExpression()) || independentOfContextItem(getRhsExpression())) { if (operator == Token.EXCEPT) { return new ExceptPattern( getLhsExpression().toPattern(config), getRhsExpression().toPattern(config)); } else { return new IntersectPattern( getLhsExpression().toPattern(config), getRhsExpression().toPattern(config)); } } return new GeneralNodePattern(this, (NodeTest)getItemType()); } } private boolean independentOfContextItem(Expression exp) { return (exp.getDependencies() & StaticProperty.DEPENDS_ON_CONTEXT_ITEM) == 0; } private boolean isPredicatePattern(Expression exp) { if (exp instanceof ItemChecker) { exp = ((ItemChecker)exp).getBaseExpression(); } return exp instanceof FilterExpression && (((FilterExpression)exp).getSelectExpression() instanceof ContextItemExpression); } /** * Get the element name used to identify this expression in exported expression format * * @return the element name used to identify this expression */ @Override protected String tag() { if (operator == Token.UNION) { return "union"; } return Token.tokens[operator]; } /** * Iterate over the value of the expression. The result will always be sorted in document order, * with duplicates eliminated * * @param c The context for evaluation * @return a SequenceIterator representing the union of the two operands */ /*@NotNull*/ @Override public SequenceIterator iterate(final XPathContext c) throws XPathException { switch (operator) { case Token.UNION: { // If either of the operands is a union expression, then we merge its component // iterators into a single multi-way union iterator List operands = new ArrayList<>(); gatherUnionLeafIterators(operands, c); return new UnionIterator(operands, GlobalOrderComparer.getInstance()); } case Token.INTERSECT: { SequenceIterator i1 = getLhsExpression().iterate(c); SequenceIterator i2 = getRhsExpression().iterate(c); return new IntersectionEnumeration(i1, i2, GlobalOrderComparer.getInstance()); } case Token.EXCEPT: { SequenceIterator i1 = getLhsExpression().iterate(c); SequenceIterator i2 = getRhsExpression().iterate(c); return new DifferenceEnumeration(i1, i2, GlobalOrderComparer.getInstance()); } } throw new UnsupportedOperationException("Unknown operator in Venn Expression"); } private void gatherUnionLeafIterators(List leafIterators, XPathContext context) throws XPathException { Expression e1 = getLhsExpression(); if (e1 instanceof VennExpression && ((VennExpression)e1).operator == Token.UNION) { ((VennExpression)e1).gatherUnionLeafIterators(leafIterators, context); } else { leafIterators.add(e1.iterate(context)); } Expression e2 = getRhsExpression(); if (e2 instanceof VennExpression && ((VennExpression) e2).operator == Token.UNION) { ((VennExpression) e2).gatherUnionLeafIterators(leafIterators, context); } else { leafIterators.add(e2.iterate(context)); } } /** * Get the effective boolean value. In the case of a union expression, this * is reduced to an OR expression, for efficiency */ @Override public boolean effectiveBooleanValue(final XPathContext context) throws XPathException { if (operator == Token.UNION) { // NOTE: this optimization was probably already done statically return getLhsExpression().effectiveBooleanValue(context) || getRhsExpression().effectiveBooleanValue(context); } else { return super.effectiveBooleanValue(context); } } /** * Get the (partial) name of a class that supports streaming of this kind of expression * * @return the partial name of a class that can be instantiated to provide streaming support in Saxon-EE, * or null if there is no such class */ @Override public String getStreamerName() { return "VennExpression"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy