package client.net.sf.saxon.ce.expr;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.expr.sort.SetUtils;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.pattern.*;
import client.net.sf.saxon.ce.trans.XPathException;
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.Set;
/**
* An AxisExpression is always obtained by simplifying a PathExpression.
* It represents a PathExpression that starts at the context node, and uses
* a simple node-test with no filters. For example "*", "title", "./item",
* "@*", or "ancestor::chapter*".
*
* <p>An AxisExpression delivers nodes in axis order (not in document order).
* To get nodes in document order, in the case of a reverse axis, the expression
* should be wrapped in a call on reverse().</p>
*/
public final class AxisExpression extends Expression {
private byte axis;
private NodeTest test;
private ItemType itemType = null;
private ItemType contextItemType = null;
int computedCardinality = -1;
private boolean doneWarnings = false;
/**
* Constructor
* @param axis The axis to be used in this AxisExpression: relevant constants are defined
* in class client.net.sf.saxon.ce.om.Axis.
* @param nodeTest The conditions to be satisfied by selected nodes. May be null,
* indicating that any node on the axis is acceptable
* @see client.net.sf.saxon.ce.om.Axis
*/
public AxisExpression(byte axis, NodeTest nodeTest) {
this.axis = axis;
test = nodeTest;
}
/**
* Simplify an expression
* @return the simplified expression
* @param visitor an expression visitor
*/
public Expression simplify(ExpressionVisitor visitor) {
if (axis == Axis.PARENT && (test==null || test instanceof AnyNodeTest)) {
ParentNodeExpression p = new ParentNodeExpression();
ExpressionTool.copyLocationInfo(this, p);
return p;
}
return this;
}
/**
* Type-check the expression
*/
public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
Configuration config = visitor.getConfiguration();
NamePool namePool = config.getNamePool();
StaticContext env = visitor.getStaticContext();
if (contextItemType == null) {
typeError(visitor, "Axis step " + toString(namePool) +
" cannot be used here: the context item is undefined", "XPDY0002", null);
}
if (contextItemType.isAtomicType()) {
typeError(visitor, "Axis step " + toString(namePool) +
" cannot be used here: the context item is an atomic value", "XPTY0020", null);
}
if (this.contextItemType == contextItemType && doneWarnings) {
return this;
}
this.contextItemType = contextItemType;
doneWarnings = true;
if (contextItemType instanceof NodeTest) {
int origin = contextItemType.getPrimitiveType();
if (origin != Type.NODE) {
if (Axis.isAlwaysEmpty(axis, origin)) {
env.issueWarning("The " + Axis.axisName[axis] + " axis starting at " +
(origin==Type.ELEMENT || origin == Type.ATTRIBUTE ? "an " : "a ") +
NodeKindTest.nodeKindName(origin) + " node will never select anything",
getSourceLocator());
return Literal.makeEmptySequence();
}
}
if (test != null) {
int kind = test.getPrimitiveType();
if (kind != Type.NODE) {
if (!Axis.containsNodeKind(axis, kind)) {
env.issueWarning("The " + Axis.axisName[axis] + " axis will never select any " +
NodeKindTest.nodeKindName(kind) + " nodes",
getSourceLocator());
return Literal.makeEmptySequence();
}
}
if (axis==Axis.SELF && kind!=Type.NODE && origin!=Type.NODE && kind!=origin) {
env.issueWarning("The self axis will never select any " +
NodeKindTest.nodeKindName(kind) +
" nodes when starting at " +
(origin==Type.ELEMENT || origin == Type.ATTRIBUTE ? "an " : "a ") +
NodeKindTest.nodeKindName(origin) + " node", getSourceLocator());
return Literal.makeEmptySequence();
}
if (axis==Axis.SELF) {
itemType = new CombinedNodeTest(test, Token.INTERSECT, (NodeTest)contextItemType);
}
// If the content type of the context item is known, see whether the node test can select anything
if (contextItemType instanceof DocumentNodeTest && kind == Type.ELEMENT) {
NodeTest elementTest = ((DocumentNodeTest)contextItemType).getElementTest();
Set<Integer> outermostElementNames = elementTest.getRequiredNodeNames();
if (outermostElementNames != null) {
Set<Integer> selectedElementNames = test.getRequiredNodeNames();
if (selectedElementNames != null) {
if (axis == Axis.CHILD) {
// check that the name appearing in the step is one of the names allowed by the nodetest
if (SetUtils.intersect(selectedElementNames, outermostElementNames).isEmpty()) {
env.issueWarning("Starting at a document node, the step is selecting an element whose name " +
"is not among the names of child elements permitted for this document node type", getSourceLocator());
return Literal.makeEmptySequence();
}
itemType = elementTest;
return this;
} else if (axis == Axis.DESCENDANT) {
// check that the name appearing in the step is one of the names allowed by the nodetest
boolean canMatchOutermost = !SetUtils.intersect(selectedElementNames, outermostElementNames).isEmpty();
if (!canMatchOutermost) {
// The expression /descendant::x starting at the document node doesn't match the outermost
// element, so replace it by child::*/descendant::x, and check that
PathExpression path = new PathExpression(
new AxisExpression(Axis.CHILD, elementTest),
new AxisExpression(Axis.DESCENDANT, test));
ExpressionTool.copyLocationInfo(this, path);
return path.typeCheck(visitor, contextItemType);
}
}
}
}
}
}
}
return this;
}
/**
* Get the static type of the context item for this AxisExpression. May be null if not known.
* @return the statically-inferred type, or null if not known
*/
public ItemType getContextItemType() {
return contextItemType;
}
/**
* Perform optimisation of an expression and its subexpressions.
* <p/>
* <p>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.</p>
*
* @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 client.net.sf.saxon.ce.type.Type#ITEM_TYPE}
* @return the original expression, rewritten if appropriate to optimize execution
*/
public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) {
return this;
}
/**
* Is this expression the same as another expression?
*/
public boolean equals(Object other) {
if (!(other instanceof AxisExpression)) {
return false;
}
if (axis != ((AxisExpression)other).axis) {
return false;
}
if (test==null) {
return ((AxisExpression)other).test==null;
}
return test.toString().equals(((AxisExpression)other).test.toString());
}
/**
* get HashCode for comparing two expressions
*/
public int hashCode() {
// generate an arbitrary hash code that depends on the axis and the node test
int h = 9375162 + axis<<20;
if (test != null) {
h ^= test.getPrimitiveType()<<16;
h ^= test.getFingerprint();
}
return h;
}
/**
* 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 getIntrinsicDependencies() {
return StaticProperty.DEPENDS_ON_CONTEXT_ITEM;
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
*/
private Expression copy() {
AxisExpression a2 = new AxisExpression(axis, test);
a2.itemType = itemType;
a2.contextItemType = contextItemType;
a2.computedCardinality = computedCardinality;
a2.doneWarnings = false;
return a2;
}
/**
* 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() {
return StaticProperty.CONTEXT_DOCUMENT_NODESET |
StaticProperty.SINGLE_DOCUMENT_NODESET |
StaticProperty.NON_CREATIVE |
(Axis.isForwards[axis] ? StaticProperty.ORDERED_NODESET : StaticProperty.REVERSE_DOCUMENT_ORDER) |
(Axis.isPeerAxis[axis] ? StaticProperty.PEER_NODESET : 0) |
(Axis.isSubtreeAxis[axis] ? StaticProperty.SUBTREE_NODESET : 0) |
((axis==Axis.ATTRIBUTE || axis==Axis.NAMESPACE) ? StaticProperty.ATTRIBUTE_NS_NODESET : 0);
}
/**
* Determine the data type of the items returned by this expression
* @return Type.NODE or a subtype, based on the NodeTest in the axis step, plus
* information about the content type if this is known from schema analysis
* @param th the type hierarchy cache
*/
public final ItemType getItemType(TypeHierarchy th) {
if (itemType != null) {
return itemType;
}
int p = Axis.principalNodeType[axis];
switch (p) {
case Type.ATTRIBUTE:
case Type.NAMESPACE:
return NodeKindTest.makeNodeKindTest(p);
default:
if (test==null) {
return AnyNodeTest.getInstance();
} else {
return test;
//return NodeKindTest.makeNodeKindTest(test.getPrimitiveType());
}
}
}
/**
* Determine the cardinality of the result of this expression
*/
public final int computeCardinality() {
if (computedCardinality != -1) {
// This takes care of the case where cardinality was computed during type checking of the child axis
return computedCardinality;
}
if (axis == Axis.ATTRIBUTE && test instanceof NameTest) {
return StaticProperty.ALLOWS_ZERO_OR_ONE;
} else if (axis == Axis.SELF) {
return StaticProperty.ALLOWS_ZERO_OR_ONE;
} else {
return StaticProperty.ALLOWS_ZERO_OR_MORE;
}
// the parent axis isn't handled by this class
}
/**
* Determine whether the expression can be evaluated without reference to the part of the context
* document outside the subtree rooted at the context node.
* @return true if the expression has no dependencies on the context node, or if the only dependencies
* on the context node are downward selections using the self, child, descendant, attribute, and namespace
* axes.
*/
public boolean isSubtreeExpression() {
return Axis.isSubtreeAxis[axis];
}
/**
* Get the axis
* @return the axis number, for example {@link Axis#CHILD}
*/
public byte getAxis() {
return axis;
}
/**
* Get the NodeTest. Returns null if the AxisExpression can return any node.
* @return the node test, or null if all nodes are returned
*/
public NodeTest getNodeTest() {
return test;
}
/**
* Evaluate the path-expression in a given context to return a NodeSet
* @param context the evaluation context
*/
public SequenceIterator iterate(XPathContext context) throws XPathException {
Item item = context.getContextItem();
try {
if (test==null) {
return ((NodeInfo)item).iterateAxis(axis);
} else {
return ((NodeInfo)item).iterateAxis(axis, test);
}
} catch (Exception exe) {
NamePool pool;
try {
pool = context.getConfiguration().getNamePool();
} catch (Exception err) {
pool = null;
}
String cName = (pool == null) ? toString() : toString(pool);
boolean isCCE = (exe instanceof ClassCastException);
if (exe instanceof NullPointerException || item == null || isCCE) {
String appendText = " is " + ((isCCE)? "not a node" : "undefined");
String code = (isCCE)? "XPTY0020" : "XPDY0002";
XPathException err = new XPathException("The context item for axis step " +
cName + appendText);
err.setErrorCode(code);
err.setXPathContext(context);
err.setLocator(getSourceLocator());
err.setIsTypeError(true);
throw err;
} else { // if (exe instanceof UnsupportedOperationException) {
if (exe.getCause() instanceof XPathException) {
XPathException ec = (XPathException)exe.getCause();
ec.maybeSetLocation(getSourceLocator());
ec.maybeSetContext(context);
throw ec;
} else {
// the namespace axis is not supported for all tree implementations
dynamicError("Axis Expression Error on: " + cName + " " + exe.getMessage(), "XPST0010", context);
return null;
}
}
}
}
/**
* Represent the expression as a string for diagnostics
*/
public String toString() {
return Axis.axisName[axis] +
"::" +
(test==null ? "node()" : test.toString());
}
/**
* Represent the expression as a string for diagnostics
* @param pool the name pool, used for expanding names in the node test
* @return a string representation of the expression
*/
public String toString(NamePool pool) {
return Axis.axisName[axis] +
"::" +
(test==null ? "node()" : test.toString(pool));
}
}
// 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.