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.