Package client.net.sf.saxon.ce.pattern

Source Code of client.net.sf.saxon.ce.pattern.LocationPathPattern

package client.net.sf.saxon.ce.pattern;

import client.net.sf.saxon.ce.expr.*;
import client.net.sf.saxon.ce.expr.instruct.Executable;
import client.net.sf.saxon.ce.expr.instruct.SlotManager;
import client.net.sf.saxon.ce.functions.Last;
import client.net.sf.saxon.ce.functions.Position;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.SingletonIterator;
import client.net.sf.saxon.ce.tree.iter.UnfailingIterator;
import client.net.sf.saxon.ce.type.ItemType;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.type.TypeHierarchy;

import java.util.*;

/**
* A LocationPathPattern represents a path, for example of the form A/B/C... The components are represented
* as a linked list, each component pointing to its predecessor
*/

public final class LocationPathPattern extends Pattern {

    /**
     * Create a LocationPathPattern
     */

    public LocationPathPattern() {}

    // the following public variables are exposed to the ExpressionParser

    private Pattern upperPattern = null;
    private byte upwardsAxis = Axis.PARENT;
    public NodeTest nodeTest = AnyNodeTest.getInstance();
    protected Expression[] filters = EMPTY_FILTER_ARRAY;
    protected Expression equivalentExpr = null;
    protected boolean firstElementPattern = false;
    protected boolean lastElementPattern = false;
    protected boolean specialFilter = false;
    private Expression variableBinding = null;      // local variable to which the current() node is bound
    private NodeTest refinedNodeTest = null;

    /**
     * Set the NodeTest
     * @param test the NodeTest
     */

    public void setNodeTest(NodeTest test) {
        if (test == null) {
            throw new NullPointerException("test");
        }
        this.nodeTest = test;
    }

    /**
     * Set the superior pattern (matching a parent or ancestor node
     * @param axis the axis (parent or ancestor) connecting to the upper pattern
     * @param upper the pattern that a parent or ancestor must match
     */

    public void setUpperPattern(byte axis, Pattern upper) {
        this.upwardsAxis = axis;
        this.upperPattern = upper;
    }

    /**
     * Add a filter to the pattern (while under construction)
     *
     * @param filter The predicate (a boolean expression or numeric expression) to be added
     */

    public void addFilter(Expression filter) {
        // Because the number of filters is small and fixed, we add entries to the array one at a time

        int len = filters.length;
        Expression[] f2 = new Expression[len+1];

        System.arraycopy(filters, 0, f2, 0, len);
        filters = f2;
        filters[len] = filter;
        filter.setContainer(this);
    }

    public void setSystemId(String systemId) {
        super.setSystemId(systemId);
        if (upperPattern != null) {
            upperPattern.setSystemId(systemId);
        }
    }

    /**
     * Set the executable containing this pattern
     *
     * @param executable the executable
     */

    public void setExecutable(Executable executable) {
        super.setExecutable(executable);
        if (upperPattern != null) {
            upperPattern.setExecutable(executable);
        }
    }

    /**
     * Get the filters assocated with the last step in the pattern
     * @return an array of expression holding the filter predicates in order
     */

    public Expression[] getFilters() {
        return filters;
    }

    /**
     * Get the pattern applying to the parent node, if there is one
     * @return the parent pattern, for example if the pattern is a/b[1]/c then the parent
     * pattern is a/b[1]
     */

    public Pattern getUpperPattern() {
        return upperPattern;
    }

    /**
     * Get the upwards axis, that is, the axis by which the upper pattern is reached.
     * Typically Axis.PARENT or Axis.ANCESTOR
     */

    public byte getUpwardsAxis() {
        return upwardsAxis;
    }

    /**
     * Set an expression used to bind the variable that represents the value of the current() function
     * @param exp the expression that binds the variable
     */

    public void setVariableBindingExpression(Expression exp) {
        variableBinding = exp;
    }

    /**
     * Simplify the pattern: perform any context-independent optimisations
     * @param visitor an expression visitor
     */

    public Pattern simplify(ExpressionVisitor visitor) throws XPathException {

        // detect the simple cases: no parent or ancestor pattern, no predicates

        if (upperPattern == null &&
                filters.length == 0 &&
                !firstElementPattern &&
                !lastElementPattern) {
            NodeTestPattern ntp = new NodeTestPattern(nodeTest);
            ntp.setSystemId(getSystemId());
            return ntp;
        }

        // simplify each component of the pattern

        if (upperPattern != null) {
            upperPattern = upperPattern.simplify(visitor);
        }

        for (int i = filters.length - 1; i >= 0; i--) {
            filters[i] = visitor.simplify(filters[i]);
        }

        return this;
    }

    /**
     * Type-check the pattern, performing any type-dependent optimizations.
     * @param visitor an expression visitor
     * @param contextItemType the type of the context item at the point where the pattern appears
     * @return the optimised Pattern
     */

    public Pattern analyze(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {

        // analyze each component of the pattern
        StaticContext env = visitor.getStaticContext();
        final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
        if (upperPattern != null) {
            upperPattern = upperPattern.analyze(visitor, contextItemType);
            if (upwardsAxis == Axis.PARENT) {
                // Check that this step in the pattern makes sense in the context of the parent step
                AxisExpression step;
                if (nodeTest.getPrimitiveType() == Type.ATTRIBUTE) {
                    step = new AxisExpression(Axis.ATTRIBUTE, nodeTest);
                } else {
                    step = new AxisExpression(Axis.CHILD, nodeTest);
                }
                step.setSourceLocator(this);
                step.setContainer(this);
                Expression exp = visitor.typeCheck(step, upperPattern.getNodeTest());
                refinedNodeTest = (NodeTest) exp.getItemType(th);
            }
        }

        int removeEntries = 0;
        for (int i = filters.length - 1; i >= 0; i--) {
            Expression filter = visitor.typeCheck(filters[i], getNodeTest());
            filter = visitor.optimize(filter, getNodeTest());
            filters[i] = filter;
            if (Literal.isConstantBoolean(filter, true)) {
                // mark the filter for removal
                removeEntries++;
            } else if (Literal.isConstantBoolean(filter, false)) {
                // if a filter is constant false, the pattern doesn't match anything
                return new NodeTestPattern(EmptySequenceTest.getInstance());
            }
        }
        if (removeEntries > 0) {
            if (removeEntries == filters.length) {
                // remove all predicates
                filters = EMPTY_FILTER_ARRAY;
            } else {
                Expression[] f2 = new Expression[filters.length - removeEntries];
                int j = 0;
                for (int i=0; i<filters.length; i++) {
                     if (!Literal.isConstantBoolean(filters[i], true)) {
                         f2[j++] = filters[i];
                     }
                }
                filters = f2;
            }
        }

        // see if it's an element pattern with a single positional predicate of [1]

        if (nodeTest.getPrimitiveType() == Type.ELEMENT && filters.length == 1) {
            if (Literal.isConstantOne(filters[0])) {
                firstElementPattern = true;
                specialFilter = true;
                filters = EMPTY_FILTER_ARRAY;
            } else if (filters[0] instanceof ComparisonExpression) {
                ComparisonExpression comp = (ComparisonExpression)filters[0];
                if (comp.getSingletonOperator() == Token.FEQ &&
                        (comp.getOperands()[0] instanceof Position && Literal.isConstantOne(comp.getOperands()[1])) ||
                        (comp.getOperands()[1] instanceof Position && Literal.isConstantOne(comp.getOperands()[0]))) {
                    firstElementPattern = true;
                    specialFilter = true;
                    filters = EMPTY_FILTER_ARRAY;
                }
            }
        }

        // see if it's an element pattern with a single positional predicate
        // of [position()=last()]

        if (nodeTest.getPrimitiveType() == Type.ELEMENT &&
                filters.length == 1 &&
                filters[0] instanceof Last) {
            lastElementPattern = true;
            specialFilter = true;
            filters = EMPTY_FILTER_ARRAY;
        }

        // For a positional pattern, construct the equivalent expression
        if (isPositional(th)) {
            equivalentExpr = makeEquivalentExpression();
            equivalentExpr = visitor.typeCheck(equivalentExpr, contextItemType);
            specialFilter = true;
        }

        return this;

        // TODO:PERF: identify subexpressions within a pattern predicate that could be promoted
        // In the case of match patterns in template rules, these would have to become global variables.
    }

    /**
     * Get the dependencies of the pattern. The only possible dependency for a pattern is
     * on local variables. This is analyzed in those patterns where local variables may appear.
     */

    public int getDependencies() {
        int dependencies = 0;
        if (upperPattern != null) {
            dependencies |= upperPattern.getDependencies();
        }
        for (int i = 0; i < filters.length; i++) {
            dependencies |= filters[i].getDependencies();
        }
        // the only dependency that's interesting is a dependency on local variables
        dependencies &= StaticProperty.DEPENDS_ON_LOCAL_VARIABLES;
        return dependencies;
    }

    /**
     * Iterate over the subexpressions within this pattern
     */

    public Iterator iterateSubExpressions() {
        List<Expression> list = new ArrayList<Expression>();
        if (variableBinding != null) {
            // Note that the variable binding must come first to ensure slots are allocated to the "current"
            // variable before the variable reference is encountered
            list.add(variableBinding);
        }
        list.addAll(Arrays.asList(filters));
        if (upperPattern != null) {
            for (Iterator<Expression> upper = upperPattern.iterateSubExpressions(); upper.hasNext();) {
                list.add(upper.next());
            }
        }
        return list.iterator();
    }

    /**
     * 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;
        for (int i = 0; i < filters.length; i++) {
            if (filters[i] == original) {
                filters[i] = replacement;
                found = true;
            }
        }
        if (upperPattern != null) {
            found |= upperPattern.replaceSubExpression(original, replacement);
        }
        if (variableBinding == original) {
            variableBinding = replacement;
            found = true;
        }
        return found;
    }

    /**
     * Allocate slots to any variables used within the pattern
     * @param env the static context in the XSLT stylesheet
     * @param slotManager
     *@param nextFree the next slot that is free to be allocated @return the next slot that is free to be allocated
     */

    public int allocateSlots(StaticContext env, SlotManager slotManager, int nextFree) {
        // See tests cnfr23, idky239, match54
        // SlotManager slotManager = env.getStyleElement().getContainingSlotManager();
        if (variableBinding != null) {
            nextFree = ExpressionTool.allocateSlots(variableBinding, nextFree, slotManager);
        }
        for (int i = 0; i < filters.length; i++) {
            nextFree = ExpressionTool.allocateSlots(filters[i], nextFree, slotManager);
        }
        if (upperPattern != null) {
            nextFree = upperPattern.allocateSlots(env, slotManager, nextFree);
        }
        return nextFree;
    }

    /**
     * Offer promotion for subexpressions within this pattern. The offer will be accepted if the subexpression
     * is not dependent on the factors (e.g. the context item) identified in the PromotionOffer.
     * By default the offer is not accepted - this is appropriate in the case of simple expressions
     * such as constant values and variable references where promotion would give no performance
     * advantage. This method is always called at compile time.
     * <p/>
     * <p>Unlike the corresponding method on {@link client.net.sf.saxon.ce.expr.Expression}, this method does not return anything:
     * it can make internal changes to the pattern, but cannot return a different pattern. Only certain
     * kinds of promotion are applicable within a pattern: specifically, promotions affecting local
     * variable references within the pattern.
     *
     * @param offer details of the offer, for example the offer to move
     *              expressions that don't depend on the context to an outer level in
     *              the containing expression
     * @param parent
     * @throws client.net.sf.saxon.ce.trans.XPathException
     *          if any error is detected
     */

    public void promote(PromotionOffer offer, Expression parent) throws XPathException {

        if (upperPattern != null) {
            upperPattern.promote(offer, parent);
        }
        Binding[] savedBindingList = offer.bindingList;
        if (variableBinding instanceof Assignation) {
            offer.bindingList = ((Assignation)variableBinding).extendBindingList(offer.bindingList);
        }
        for (int i = 0; i < filters.length; i++) {
            filters[i] = filters[i].promote(offer, parent);
        }
        offer.bindingList = savedBindingList;
    }

    /**
     * For a positional pattern, make an equivalent path expression to evaluate the filters.
     * This expression takes the node being tested as the context node, and returns a set of nodes
     * which will include the context node if and only if it matches the pattern. The expression only
     * tests nodes against the filters, not against any parent or ancestor patterns.
     * @return the equivalent path expression
     */

    private Expression makeEquivalentExpression() {
        byte axis = (nodeTest.getPrimitiveType() == Type.ATTRIBUTE ?
                Axis.ATTRIBUTE :
                Axis.CHILD);
        Expression step = new AxisExpression(axis, nodeTest);
        for (int n = 0; n < filters.length; n++) {
            step = new FilterExpression(step, filters[n]);
        }
        ParentNodeExpression start = new ParentNodeExpression();
        start.setContainer(this);
        PathExpression path = new PathExpression(start, step);
        path.setContainer(this);
        return path;
        // Note, the resulting expression is not required to deliver results in document order
    }

    /**
     * Determine whether the pattern matches a given node.
     *
     * @param node the node to be tested
     * @return true if the pattern matches, else false
     */

    public boolean matches(NodeInfo node, XPathContext context) throws XPathException {
        return matchesBeneathAnchor(node, null, context);
        // matches() and internalMatches() differ in the way they handle the current() function.
        // The variable holding the value of current() is initialized on entry to the top-level
        // LocationPathPattern, but not on entry to its subordinate patterns.
    }

    /**
     * Determine whether this pattern matches a given Node within the subtree rooted at a given
     * anchor node. This method is used when the pattern is used for streaming.
     * @param node    The NodeInfo representing the Element or other node to be tested against the Pattern
     * @param anchor  The anchor node, which must match any AnchorPattern subpattern
     * @param context The dynamic context. Only relevant if the pattern
     *                uses variables, or contains calls on functions such as document() or key().
     * @return true if the node matches the Pattern, false otherwise
     */

    public boolean matchesBeneathAnchor(NodeInfo node, NodeInfo anchor, XPathContext context) throws XPathException {
        // if there is a variable to hold the value of current(), bind it now
        if (variableBinding != null) {
            XPathContext c2 = context;
            Item ci = context.getContextItem();
            if (!(ci instanceof NodeInfo && ((NodeInfo)ci).isSameNodeInfo(node))) {
                c2 = context.newContext();
                UnfailingIterator si = SingletonIterator.makeIterator(node);
                si.next();
                c2.setCurrentIterator(si);
            }
            variableBinding.evaluateItem(c2);
        }
        return internalMatches(node, anchor, context);
    }

    /**
     * Test whether the pattern matches, but without changing the current() node
     */

    protected boolean internalMatches(NodeInfo node, NodeInfo anchor, XPathContext context) throws XPathException {
        // System.err.println("Matching node type and fingerprint");
        if (!nodeTest.matches(node)) {
            return false;
        }
        if (upperPattern != null) {
            switch (upwardsAxis) {
                case Axis.PARENT: {
                    NodeInfo par = node.getParent();
                    if (par == null) {
                        return false;
                    }
                    if (!upperPattern.internalMatches(par, anchor, context)) {
                        return false;
                    }
                    break;
                }
                case Axis.ANCESTOR: {
                    NodeInfo anc = node.getParent();
                    while (true) {
                        if (anc == null) {
                            return false;
                        }
                        if (upperPattern.internalMatches(anc, anchor, context)) {
                            break;
                        }
                        anc = anc.getParent();
                    }
                    break;
                }
                case Axis.ANCESTOR_OR_SELF: {
                    NodeInfo anc = node;
                    while (true) {
                        if (anc == null) {
                            return false;
                        }
                        if (upperPattern.internalMatches(anc, anchor, context)) {
                            break;
                        }
                        anc = anc.getParent();
                    }
                    break;
                }
                default:
                    throw new XPathException("Unsupported axis " + Axis.axisName[upwardsAxis] + " in pattern");
            }
        }

        if (specialFilter) {
            if (firstElementPattern) {
                SequenceIterator iter = node.iterateAxis(Axis.PRECEDING_SIBLING, nodeTest);
                return iter.next() == null;
            }

            if (lastElementPattern) {
                SequenceIterator iter = node.iterateAxis(Axis.FOLLOWING_SIBLING, nodeTest);
                return iter.next() == null;
            }

            if (equivalentExpr != null) {

                // for a positional pattern, we do it the hard way: test whether the
                // node is a member of the nodeset obtained by evaluating the
                // equivalent expression

                // System.err.println("Testing positional pattern against node " + node.generateId());
                XPathContext c2 = context.newMinorContext();
                UnfailingIterator single = SingletonIterator.makeIterator(node);
                single.next();
                c2.setCurrentIterator(single);
                try {
                    SequenceIterator nsv = equivalentExpr.iterate(c2);
                    while (true) {
                        NodeInfo n = (NodeInfo) nsv.next();
                        if (n == null) {
                            return false;
                        }
                        if (n.isSameNodeInfo(node)) {
                            return true;
                        }
                    }
                } catch (XPathException e) {
                    XPathException err = new XPathException("An error occurred matching pattern {" + toString() + "}: ", e);
                    err.setXPathContext(c2);
                    err.setErrorCodeQName(e.getErrorCodeQName());
                    err.setLocator(this);
                    c2.getController().recoverableError(err);
                    return false;
                }
            }
        }

        if (filters.length != 0) {
            XPathContext c2 = context.newMinorContext();
            UnfailingIterator iter = SingletonIterator.makeIterator(node);
            iter.next();
            c2.setCurrentIterator(iter);
            // it's a non-positional filter, so we can handle each node separately

            for (int i = 0; i < filters.length; i++) {
                try {
                    if (!filters[i].effectiveBooleanValue(c2)) {
                        return false;
                    }
                } catch (XPathException e) {
                    if ("XTDE0640".equals(e.getErrorCodeLocalPart())) {
                        // Treat circularity error as fatal (test error213)
                        throw e;
                    }
                    XPathException err = new XPathException("An error occurred matching pattern {" + toString() + "}: ", e);
                    err.setXPathContext(c2);
                    err.setErrorCodeQName(e.getErrorCodeQName());
                    err.setLocator(this);
                    c2.getController().recoverableError(err);
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Determine the types of nodes to which this pattern applies. Used for optimisation.
     * For patterns that match nodes of several types, return Node.NODE
     *
     * @return the type of node matched by this pattern. e.g. Node.ELEMENT or Node.TEXT
     */

    public int getNodeKind() {
        return nodeTest.getPrimitiveType();
    }

    /**
     * Determine the fingerprint of nodes to which this pattern applies.
     * Used for optimisation.
     *
     * @return the fingerprint of nodes matched by this pattern.
     */

    public int getFingerprint() {
        return nodeTest.getFingerprint();
    }

    /**
     * Get a NodeTest that all the nodes matching this pattern must satisfy
     */

    public NodeTest getNodeTest() {
        if (refinedNodeTest != null) {
            return refinedNodeTest;
        } else {
            return nodeTest;
        }
    }

    /**
     * Determine if the pattern uses positional filters
     * @param th the type hierarchy cache
     * @return true if there is a numeric filter in the pattern, or one that uses the position()
     *         or last() functions
     */

    public boolean isPositional(TypeHierarchy th) {
        for (int i = 0; i < filters.length; i++) {
            int type = filters[i].getItemType(th).getPrimitiveType();
            if (type == StandardNames.XS_DOUBLE || type == StandardNames.XS_DECIMAL ||
                    type == StandardNames.XS_INTEGER || type == StandardNames.XS_FLOAT || type == StandardNames.XS_ANY_ATOMIC_TYPE) {
                return true;
            }
            if ((filters[i].getDependencies() &
                    (StaticProperty.DEPENDS_ON_POSITION | StaticProperty.DEPENDS_ON_LAST)) != 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * If the pattern contains any calls on current(), this method is called to modify such calls
     * to become variable references to a variable declared in a specially-allocated local variable
     *
     * @param let   the expression that assigns the local variable. This returns a dummy result, and is executed
     *              just before evaluating the pattern, to get the value of the context item into the variable.
     * @param offer A PromotionOffer used to process the expressions and change the call on current() into
     *              a variable reference
     * @param topLevel
     * @throws XPathException
     */

    public void resolveCurrent(LetExpression let, PromotionOffer offer, boolean topLevel) throws XPathException {
        for (int i = 0; i < filters.length; i++) {
            filters[i] = filters[i].promote(offer, let);
        }
        if (upperPattern instanceof LocationPathPattern) {
            upperPattern.resolveCurrent(let, offer, false);
        }
        if (topLevel) {
            variableBinding = let;
        }
    }

    /**
     * Determine whether this pattern is the same as another pattern
     * @param other the other object
     */

    public boolean equals(Object other) {
        if (other instanceof LocationPathPattern) {
            LocationPathPattern lpp = (LocationPathPattern)other;
            if (!Arrays.equals(filters, lpp.filters)) {
                return false;
            }
            if (!nodeTest.equals(lpp.nodeTest)) {
                return false;
            }
            if (upwardsAxis != lpp.upwardsAxis) {
                return false;
            }
            if (upperPattern == null) {
                if (lpp.upperPattern != null) {
                    return false;
                }
            } else {
                if (!upperPattern.equals(lpp.upperPattern)) {
                    return false;
                }
            }
        } else {
            return false;
        }
        return true;
    }

    /**
     * hashcode supporting equals()
     */

    public int hashCode() {
        int h = 88267;
        for (int i=0; i<filters.length; i++) {
            h ^= filters[i].hashCode();
        }
        h ^= nodeTest.hashCode();
        if (upperPattern != null) {
            h ^= upperPattern.hashCode();
        }
        h ^= (upwardsAxis<<22);
        return h;
    }

    private static Expression[] EMPTY_FILTER_ARRAY = new Expression[0];


}

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

Related Classes of client.net.sf.saxon.ce.pattern.LocationPathPattern

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.