package client.net.sf.saxon.ce.expr;
import client.net.sf.saxon.ce.expr.instruct.ForEach;
import client.net.sf.saxon.ce.expr.sort.DocumentOrderIterator;
import client.net.sf.saxon.ce.expr.sort.GlobalOrderComparer;
import client.net.sf.saxon.ce.om.Item;
import client.net.sf.saxon.ce.om.NodeInfo;
import client.net.sf.saxon.ce.om.SequenceIterator;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.EmptyIterator;
import client.net.sf.saxon.ce.tree.iter.OneItemGoneIterator;
import client.net.sf.saxon.ce.tree.util.SourceLocator;
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 client.net.sf.saxon.ce.value.AtomicValue;
import client.net.sf.saxon.ce.value.Cardinality;
import client.net.sf.saxon.ce.value.EmptySequence;
import client.net.sf.saxon.ce.value.SequenceType;
import java.util.Arrays;
import java.util.Iterator;
/**
* A slash expression is any expression using the binary slash operator "/". The parser initially generates a slash
* expression for all occurrences of the binary "/" operator. Subsequently, as a result of type inferencing, the
* majority of slash expressions will be rewritten as instances of PathExpression (returning nodes) or
* ForEach instructions (when they return atomic values). However, in the rare case where it is not possible to determine
* statically whether the rh operand returns nodes or atomic values, instances of this class may need to be interpreted
* directly at run time.
*/
public class SlashExpression extends Expression
implements ContextMappingFunction {
Expression start;
Expression step;
/**
* Constructor
* @param start The left hand operand (which must always select a sequence of nodes).
* @param step The step to be followed from each node in the start expression to yield a new
* sequence; this may return either nodes or atomic values (but not a mixture of the two)
*/
public SlashExpression(Expression start, Expression step) {
this.start = start;
this.step = step;
adoptChildExpression(start);
adoptChildExpression(step);
}
protected void setStartExpression(Expression start2) {
if (start != start2) {
start = start2;
adoptChildExpression(start);
}
}
protected void setStepExpression(Expression step2) {
if (step != step2) {
step = step2;
adoptChildExpression(step);
}
}
/**
* Get the start expression (the left-hand operand)
* @return the first operand
*/
public Expression getControllingExpression() {
return start;
}
/**
* Get the step expression (the right-hand operand)
* @return the second operand
*/
public Expression getControlledExpression() {
return step;
}
/**
* Determine whether this expression is capable (as far as static analysis is concerned)
* of returning a mixture of nodes and atomic values. If so, this needs to be prevented
* at run time
* @return true if the static type allows both nodes and atomic values
*/
public boolean isHybrid() {
return true;
}
/**
* Simplify an expression
* @return the simplified expression
* @param visitor the expression visitor
*/
public Expression simplify(ExpressionVisitor visitor) throws XPathException {
setStartExpression(visitor.simplify(start));
setStepExpression(visitor.simplify(step));
// if the start expression is an empty sequence, then the whole PathExpression is empty
if (Literal.isEmptySequence(start)) {
return start;
}
// if the simplified Step is an empty sequence, then the whole PathExpression is empty
if (Literal.isEmptySequence(step)) {
return step;
}
// the expression /.. is sometimes used to represent the empty node-set. Applying this simplification
// now avoids generating warnings for this case.
if (start instanceof RootExpression && step instanceof ParentNodeExpression) {
return Literal.makeEmptySequence();
}
return this;
}
/**
* Determine the data type of the items returned by this exprssion
* @return the type of the step
* @param th the type hierarchy cache
*/
public final ItemType getItemType(TypeHierarchy th) {
return step.getItemType(th);
}
/**
* Type-check the expression
*/
public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
Expression start2 = visitor.typeCheck(start, contextItemType);
// The first operand must be of type node()*
RoleLocator role0 = new RoleLocator(RoleLocator.BINARY_EXPR, "/", 0);
role0.setErrorCode("XPTY0019");
setStartExpression(
TypeChecker.staticTypeCheck(start2, SequenceType.NODE_SEQUENCE, false, role0, visitor));
// Now check the second operand
setStepExpression(visitor.typeCheck(step, start.getItemType(th)));
// We distinguish three cases for the second operand: either it is known statically to deliver
// nodes only (a traditional path expression), or it is known statically to deliver atomic values
// only (a simple mapping expression), or we don't yet know.
ItemType stepType = step.getItemType(th);
if (th.isSubType(stepType, Type.NODE_TYPE)) {
if ((step.getSpecialProperties() & StaticProperty.NON_CREATIVE) != 0) {
// A traditional path expression
// We don't need the operands to be sorted; any sorting that's needed
// will be done at the top level
Optimizer opt = visitor.getConfiguration().getOptimizer();
start2 = ExpressionTool.unsorted(opt, start, false);
Expression step2 = ExpressionTool.unsorted(opt, step, false);
PathExpression path = new PathExpression(start2, step2);
ExpressionTool.copyLocationInfo(this, path);
Expression sortedPath = path.addDocumentSorter();
ExpressionTool.copyLocationInfo(this, sortedPath);
sortedPath = sortedPath.simplify(visitor);
return sortedPath.typeCheck(visitor, contextItemType);
} else {
// We can still use a path expression, but need to retain the sorting of operands
PathExpression path = new PathExpression(start, step);
ExpressionTool.copyLocationInfo(this, path);
Expression sortedPath = path.addDocumentSorter();
ExpressionTool.copyLocationInfo(this, sortedPath);
sortedPath = sortedPath.simplify(visitor);
return sortedPath.typeCheck(visitor, contextItemType);
}
// Decide whether the result needs to be wrapped in a sorting
// expression to deliver the results in document order
// int props = getSpecialProperties();
//
// if ((props & StaticProperty.ORDERED_NODESET) != 0) {
// return this;
// } else if ((props & StaticProperty.REVERSE_DOCUMENT_ORDER) != 0) {
// return SystemFunction.makeSystemFunction("reverse", new Expression[]{this});
// } else {
// return new DocumentSorter(this);
// }
} else if (stepType.isAtomicType()) {
// This is a simple mapping expression: a/b where b returns atomic values
ForEach ame = new ForEach(start, step, false);
ExpressionTool.copyLocationInfo(this, ame);
return visitor.typeCheck(visitor.simplify(ame), contextItemType);
} else {
// This is a hybrid mapping expression, one where we don't know the type of the step
// (and therefore, we don't know whether sorting into document order is required) until run-time
return this;
}
}
public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy();
setStartExpression(visitor.optimize(start, contextItemType));
setStepExpression(step.optimize(visitor, start.getItemType(th)));
if (Literal.isEmptySequence(start) || Literal.isEmptySequence(step)) {
return new Literal(EmptySequence.getInstance());
}
return promoteFocusIndependentSubexpressions(visitor, contextItemType);
}
/**
* If any subexpressions within the step are not dependent on the focus,
* and if they are not "creative" expressions (expressions that can create new nodes), then
* promote them: this causes them to be evaluated once, outside the path expression
* @param visitor the expression visitor
* @param contextItemType the type of the context item for evaluating the start expression
* @return the rewritten expression, or the original expression if no rewrite was possible
*/
protected Expression promoteFocusIndependentSubexpressions(
ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
Optimizer opt = visitor.getConfiguration().getOptimizer();
PromotionOffer offer = new PromotionOffer(opt);
offer.action = PromotionOffer.FOCUS_INDEPENDENT;
offer.promoteDocumentDependent = (start.getSpecialProperties() & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0;
offer.containingExpression = this;
setStepExpression(doPromotion(step, offer));
visitor.resetStaticProperties();
if (offer.containingExpression != this) {
offer.containingExpression =
visitor.optimize(visitor.typeCheck(offer.containingExpression, contextItemType), contextItemType);
return offer.containingExpression;
}
return this;
}
/**
* Promote this expression if possible
*/
public Expression promote(PromotionOffer offer, Expression parent) throws XPathException {
Expression exp = offer.accept(parent, this);
if (exp != null) {
return exp;
} else {
setStartExpression(doPromotion(start, offer));
if (offer.action == PromotionOffer.REPLACE_CURRENT) {
// 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.
setStepExpression(doPromotion(step, offer));
}
return this;
}
}
/**
* Get the immediate subexpressions of this expression
*/
public Iterator<Expression> iterateSubExpressions() {
return Arrays.asList((new Expression[]{start, step})).iterator();
}
/**
* Given an expression that is an immediate child of this expression, test whether
* the evaluation of the parent expression causes the child expression to be
* evaluated repeatedly
* @param child the immediate subexpression
* @return true if the child expression is evaluated repeatedly
*/
public boolean hasLoopingSubexpression(Expression child) {
return child == step;
}
/**
* 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) {
setStartExpression(replacement);
found = true;
}
if (step == original) {
setStepExpression(replacement);
found = true;
}
return found;
}
/**
* 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
*/
public int computeDependencies() {
return start.getDependencies() |
// not all dependencies in the step matter, because the context node, etc,
// are not those of the outer expression
(step.getDependencies() &
(StaticProperty.DEPENDS_ON_XSLT_CONTEXT |
StaticProperty.DEPENDS_ON_LOCAL_VARIABLES |
StaticProperty.DEPENDS_ON_USER_FUNCTIONS));
}
/**
* 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.
*/
public int computeSpecialProperties() {
int p = super.computeSpecialProperties();
if ((start.getSpecialProperties() & step.getSpecialProperties() & StaticProperty.NON_CREATIVE) != 0) {
p |= StaticProperty.NON_CREATIVE;
}
return p;
}
/**
* Determine the static cardinality of the expression
*/
public int computeCardinality() {
int c1 = start.getCardinality();
int c2 = step.getCardinality();
return Cardinality.multiply(c1, c2);
}
/**
* Is this expression the same as another expression?
*/
public boolean equals(Object other) {
if (!(other instanceof SlashExpression)) {
return false;
}
SlashExpression p = (SlashExpression) other;
return (start.equals(p.start) && step.equals(p.step));
}
/**
* get HashCode for comparing two expressions
*/
public int hashCode() {
return "SlashExpression".hashCode() + start.hashCode() + step.hashCode();
}
/**
* Iterate the path-expression in a given context
* @param context the evaluation context
*/
public SequenceIterator iterate(final XPathContext context) throws XPathException {
// This class delivers the result of the path expression in unsorted order,
// without removal of duplicates. If sorting and deduplication are needed,
// this is achieved by wrapping the path expression in a DocumentSorter
SequenceIterator result = start.iterate(context);
XPathContext context2 = context.newMinorContext();
context2.setCurrentIterator(result);
result = new ContextMappingIterator(this, context2);
// Peek at the first item, and depending on its type, check that all the items
// are atomic values or that all are nodes.
final SourceLocator loc = getSourceLocator();
Item first = result.next();
if (first == null) {
return EmptyIterator.getInstance();
} else if (first instanceof AtomicValue) {
ItemMappingFunction atomicValueChecker = new ItemMappingFunction() {
public Item mapItem(Item item) throws XPathException {
if (item instanceof AtomicValue) {
return item;
} else {
throw reportMixedItems(loc, context);
}
}
};
return new ItemMappingIterator(new OneItemGoneIterator(result), atomicValueChecker, true);
} else {
ItemMappingFunction nodeChecker = new ItemMappingFunction() {
public Item mapItem(Item item) throws XPathException {
if (item instanceof NodeInfo) {
return item;
} else {
throw reportMixedItems(loc, context);
}
}
};
return new DocumentOrderIterator(
new ItemMappingIterator(new OneItemGoneIterator(result), nodeChecker, true),
GlobalOrderComparer.getInstance());
}
}
private XPathException reportMixedItems(SourceLocator loc, XPathContext context) {
XPathException err = new XPathException("Cannot mix nodes and atomic values in the result of a path expression");
err.setErrorCode("XPTY0018");
err.setLocator(loc);
err.setXPathContext(context);
return err;
}
/**
* Mapping function, from a node returned by the start iteration, to a sequence
* returned by the child.
*/
public final SequenceIterator map(XPathContext context) throws XPathException {
return step.iterate(context);
}
/**
* 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
* @return a representation of the expression as a string
*/
public String toString() {
return start.toString() + "/" + step.toString();
}
/**
* Get the first step in this expression. A path expression A/B/C is represented as (A/B)/C, but
* the first step is A
* @return the first step in the expression, after expanding any nested path expressions
*/
public Expression getFirstStep() {
if (start instanceof SlashExpression) {
return ((SlashExpression) start).getFirstStep();
} else {
return start;
}
}
/**
* Get all steps after the first.
* This is complicated by the fact that A/B/C is represented as ((A/B)/C; we are required
* to return B/C
* @return a path expression containing all steps in this path expression other than the first,
* after expanding any nested path expressions
*/
public Expression getRemainingSteps() {
if (start instanceof SlashExpression) {
SlashExpression rem =
new SlashExpression(((PathExpression) start).getRemainingSteps(), step);
ExpressionTool.copyLocationInfo(start, rem);
return rem;
} else {
return step;
}
}
/**
* Get the last step of the path expression
* @return the last step in the expression, after expanding any nested path expressions
*/
public Expression getLastStep() {
if (step instanceof SlashExpression) {
return ((SlashExpression)step).getLastStep();
} else {
return step;
}
}
/**
* Get a path expression consisting of all steps except the last
* @return a path expression containing all steps in this path expression other than the last,
* after expanding any nested path expressions
*/
public Expression getLeadingSteps() {
if (step instanceof SlashExpression) {
SlashExpression rem =
new SlashExpression(start, ((SlashExpression) step).getLeadingSteps());
ExpressionTool.copyLocationInfo(start, rem);
return rem;
} else {
return start;
}
}
}
// 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.