net.sf.saxon.expr.ValueComparison Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 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.parser.*;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.CodepointCollatingComparer;
import net.sf.saxon.expr.sort.CodepointCollator;
import net.sf.saxon.expr.sort.GenericAtomicComparer;
import net.sf.saxon.functions.*;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.NoDynamicContextException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import java.util.Map;
/**
* ValueComparison: a boolean expression that compares two atomic values
* for equals, not-equals, greater-than or less-than. Implements the operators
* eq, ne, lt, le, gt, ge
*/
public final class ValueComparison extends BinaryExpression implements ComparisonExpression, Negatable {
private AtomicComparer comparer;
/*@Nullable*/ private BooleanValue resultWhenEmpty = null;
private boolean needsRuntimeCheck;
/**
* Create a comparison expression identifying the two operands and the operator
*
* @param p1 the left-hand operand
* @param op the operator, as a token returned by the Tokenizer (e.g. Token.LT)
* @param p2 the right-hand operand
*/
public ValueComparison(Expression p1, int op, Expression p2) {
super(p1, op, p2);
}
/**
* 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.
*/
public String getExpressionName() {
return "ValueComparison";
}
/**
* Set the AtomicComparer used to compare atomic values
*
* @param comparer the AtomicComparer
*/
public void setAtomicComparer(AtomicComparer comparer) {
this.comparer = comparer;
}
/**
* Get the AtomicComparer used to compare atomic values. This encapsulates any collation that is used.
* Note that the comparer is always known at compile time.
*/
public AtomicComparer getAtomicComparer() {
return comparer;
}
/**
* Get the primitive (singleton) operator used: one of Token.FEQ, Token.FNE, Token.FLT, Token.FGT,
* Token.FLE, Token.FGE
*/
public int getSingletonOperator() {
return operator;
}
/**
* Determine whether untyped atomic values should be converted to the type of the other operand
*
* @return true if untyped values should be converted to the type of the other operand, false if they
* should be converted to strings.
*/
public boolean convertsUntypedToOther() {
return false;
}
/**
* Set the result to be returned if one of the operands is an empty sequence
*
* @param value the result to be returned if an operand is empty. Supply null to mean the empty sequence.
*/
public void setResultWhenEmpty(BooleanValue value) {
resultWhenEmpty = value;
}
/**
* Get the result to be returned if one of the operands is an empty sequence
*
* @return BooleanValue.TRUE, BooleanValue.FALSE, or null (meaning the empty sequence)
*/
public BooleanValue getResultWhenEmpty() {
return resultWhenEmpty;
}
/**
* Determine whether a run-time check is needed to check that the types of the arguments
* are comparable
*
* @return true if a run-time check is needed
*/
public boolean needsRuntimeComparabilityCheck() {
return needsRuntimeCheck;
}
/**
* Type-check the expression
*/
/*@NotNull*/
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
resetLocalStaticProperties();
typeCheckChildren(visitor, contextInfo);
Configuration config = getConfiguration();
StaticContext env = visitor.getStaticContext();
if (Literal.isEmptySequence(getLhsExpression())) {
return resultWhenEmpty == null ? getLhsExpression() : Literal.makeLiteral(resultWhenEmpty);
}
if (Literal.isEmptySequence(getRhsExpression())) {
return resultWhenEmpty == null ? getRhsExpression() : Literal.makeLiteral(resultWhenEmpty);
}
final SequenceType optionalAtomic = SequenceType.OPTIONAL_ATOMIC;
RoleDiagnostic role0 = new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 0);
setLhsExpression(TypeChecker.staticTypeCheck(getLhsExpression(), optionalAtomic, false, role0, visitor));
RoleDiagnostic role1 = new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 1);
setRhsExpression(TypeChecker.staticTypeCheck(getRhsExpression(), optionalAtomic, false, role1, visitor));
PlainType t0 = getLhsExpression().getItemType().getAtomizedItemType();
PlainType t1 = getRhsExpression().getItemType().getAtomizedItemType();
if (t0.isExternalType() || t1.isExternalType()) {
XPathException err = new XPathException("Cannot perform comparisons involving external objects");
err.setIsTypeError(true);
err.setErrorCode("XPTY0004");
err.setLocation(getLocation());
throw err;
}
BuiltInAtomicType p0 = (BuiltInAtomicType) t0.getPrimitiveItemType();
if (p0.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
p0 = BuiltInAtomicType.STRING;
}
BuiltInAtomicType p1 = (BuiltInAtomicType) t1.getPrimitiveItemType();
if (p1.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
p1 = BuiltInAtomicType.STRING;
}
needsRuntimeCheck =
p0.equals(BuiltInAtomicType.ANY_ATOMIC) || p1.equals(BuiltInAtomicType.ANY_ATOMIC);
if (!needsRuntimeCheck && !Type.isPossiblyComparable(p0, p1, Token.isOrderedOperator(operator))) {
boolean opt0 = Cardinality.allowsZero(getLhsExpression().getCardinality());
boolean opt1 = Cardinality.allowsZero(getRhsExpression().getCardinality());
if (opt0 || opt1) {
// This is a comparison such as (xs:integer? eq xs:date?). This is almost
// certainly an error, but we need to let it through because it will work if
// one of the operands is an empty sequence.
String which = null;
if (opt0) {
which = "the first operand is";
}
if (opt1) {
which = "the second operand is";
}
if (opt0 && opt1) {
which = "one or both operands are";
}
visitor.getStaticContext().issueWarning("Comparison of " + t0.toString() +
(opt0 ? "?" : "") + " to " + t1.toString() +
(opt1 ? "?" : "") + " will fail unless " + which + " empty", getLocation());
needsRuntimeCheck = true;
} else {
XPathException err = new XPathException("Cannot compare " + t0.toString() +
" to " + t1.toString());
err.setIsTypeError(true);
err.setErrorCode("XPTY0004");
err.setLocation(getLocation());
throw err;
}
}
if (!(operator == Token.FEQ || operator == Token.FNE)) {
if (!p0.isOrdered(true)) {
XPathException err = new XPathException("Type " + t0.toString() + " is not an ordered type");
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
err.setLocation(getLocation());
throw err;
}
if (!p1.isOrdered(true)) {
XPathException err = new XPathException("Type " + t1.toString() + " is not an ordered type");
err.setErrorCode("XPTY0004");
err.setIsTypeError(true);
err.setLocation(getLocation());
throw err;
}
}
if (comparer == null) {
// In XSLT, only do this the first time through, otherwise the default-collation attribute may be missed
final String defaultCollationName = env.getDefaultCollationName();
StringCollator comp = config.getCollation(defaultCollationName);
if (comp == null) {
comp = CodepointCollator.getInstance();
}
comparer = GenericAtomicComparer.makeAtomicComparer(
p0, p1, comp, env.getConfiguration().getConversionContext());
}
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 contextInfo 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 XPathException if an error is discovered during this phase
* (typically a type error)
*/
/*@NotNull*/
public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
TypeHierarchy th = getConfiguration().getTypeHierarchy();
optimizeChildren(visitor, contextInfo);
Sequence value0 = null;
Sequence value1 = null;
if (getLhsExpression() instanceof Literal) {
value0 = ((Literal) getLhsExpression()).getValue();
}
if (getRhsExpression() instanceof Literal) {
value1 = ((Literal) getRhsExpression()).getValue();
}
// evaluate the expression now if both arguments are constant
if ((value0 != null) && (value1 != null)) {
try {
AtomicValue r = evaluateItem(visitor.getStaticContext().makeEarlyEvaluationContext());
return Literal.makeLiteral(r == null ? EmptySequence.getInstance() : r);
} catch (NoDynamicContextException e) {
// early evaluation failed, typically because the implicit context isn't available.
// Try again at run-time
return this;
}
}
// optimise count(x) eq N (or gt N, ne N, eq N, etc)
// TODO: extend this to cases where N is not a literal
if (getLhsExpression().isCallOn(Count.class) && Literal.isAtomic(getRhsExpression())) {
Expression e2 = optimizeCount(visitor, false);
if (e2 != null) {
return e2.optimize(visitor, contextInfo);
}
} else if (getRhsExpression().isCallOn(Count.class) && Literal.isAtomic(getLhsExpression())) {
Expression e2 = optimizeCount(visitor, true);
if (e2 != null) {
return e2.optimize(visitor, contextInfo);
}
}
// optimise string-length(x) = 0, >0, !=0 etc
if (getLhsExpression().isCallOn(StringLength_1.class) &&
isZero(value1)) {
Expression arg = ((SystemFunctionCall) getLhsExpression()).getArg(0);
switch (operator) {
case Token.FEQ:
case Token.FLE:
return SystemFunction.makeCall("not", getRetainedStaticContext(), arg);
case Token.FNE:
case Token.FGT:
return SystemFunction.makeCall("boolean", getRetainedStaticContext(), arg);
case Token.FGE:
return Literal.makeLiteral(BooleanValue.TRUE);
case Token.FLT:
return Literal.makeLiteral(BooleanValue.FALSE);
}
}
// optimise (0 = string-length(x)), etc
if (getRhsExpression().isCallOn(StringLength_1.class) &&
isZero(value0)) {
Expression arg = ((SystemFunctionCall) getRhsExpression()).getArg(0);
switch (operator) {
case Token.FEQ:
case Token.FGE:
return SystemFunction.makeCall("not", getRetainedStaticContext(), arg);
case Token.FNE:
case Token.FLT:
return SystemFunction.makeCall("boolean", getRetainedStaticContext(), arg);
case Token.FLE:
return Literal.makeLiteral(BooleanValue.TRUE);
case Token.FGT:
return Literal.makeLiteral(BooleanValue.FALSE);
}
}
// optimise string="" etc
// Note we can change S!="" to boolean(S) for cardinality zero-or-one, but we can only
// change S="" to not(S) for cardinality exactly-one.
int p0 = getLhsExpression().getItemType().getPrimitiveType();
if ((p0 == StandardNames.XS_STRING ||
p0 == StandardNames.XS_ANY_URI ||
p0 == StandardNames.XS_UNTYPED_ATOMIC) &&
getRhsExpression() instanceof Literal &&
((Literal) getRhsExpression()).getValue() instanceof StringValue &&
((StringValue) ((Literal) getRhsExpression()).getValue()).isZeroLength() &&
comparer instanceof CodepointCollatingComparer) {
switch (operator) {
case Token.FNE:
case Token.FGT:
return SystemFunction.makeCall("boolean", getRetainedStaticContext(), getLhsExpression());
case Token.FEQ:
case Token.FLE:
if (getLhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE) {
return SystemFunction.makeCall("not", getRetainedStaticContext(), getLhsExpression());
}
}
}
// optimize "" = string etc
int p1 = getRhsExpression().getItemType().getPrimitiveType();
if ((p1 == StandardNames.XS_STRING ||
p1 == StandardNames.XS_ANY_URI ||
p1 == StandardNames.XS_UNTYPED_ATOMIC) &&
getLhsExpression() instanceof Literal &&
((Literal) getLhsExpression()).getValue() instanceof StringValue &&
((StringValue) ((Literal) getLhsExpression()).getValue()).isZeroLength() &&
comparer instanceof CodepointCollatingComparer) {
switch (operator) {
case Token.FNE:
case Token.FLT:
return SystemFunction.makeCall("boolean", getRetainedStaticContext(), getRhsExpression());
case Token.FEQ:
case Token.FGE:
if (getRhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE) {
return SystemFunction.makeCall("not", getRetainedStaticContext(), getRhsExpression());
}
}
}
// optimise [position()=last()] etc
if (getLhsExpression().isCallOn(PositionAndLast.Position.class) &&
getRhsExpression().isCallOn(PositionAndLast.Last.class)) {
switch (operator) {
case Token.FEQ:
case Token.FGE:
IsLastExpression iletrue = new IsLastExpression(true);
ExpressionTool.copyLocationInfo(this, iletrue);
return iletrue;
case Token.FNE:
case Token.FLT:
IsLastExpression ilefalse = new IsLastExpression(false);
ExpressionTool.copyLocationInfo(this, ilefalse);
return ilefalse;
case Token.FGT:
return Literal.makeLiteral(BooleanValue.FALSE);
case Token.FLE:
return Literal.makeLiteral(BooleanValue.TRUE);
}
}
if (getLhsExpression().isCallOn(PositionAndLast.Last.class) &&
getRhsExpression().isCallOn(PositionAndLast.Position.class)) {
switch (operator) {
case Token.FEQ:
case Token.FLE:
IsLastExpression iletrue = new IsLastExpression(true);
ExpressionTool.copyLocationInfo(this, iletrue);
return iletrue;
case Token.FNE:
case Token.FGT:
IsLastExpression ilefalse = new IsLastExpression(false);
ExpressionTool.copyLocationInfo(this, ilefalse);
return ilefalse;
case Token.FLT:
return Literal.makeLiteral(BooleanValue.FALSE);
case Token.FGE:
return Literal.makeLiteral(BooleanValue.TRUE);
}
}
// optimize comparison against an integer constant
if (value1 instanceof Int64Value &&
getLhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE &&
th.isSubType(getLhsExpression().getItemType(), NumericType.getInstance())) {
return new CompareToIntegerConstant(getLhsExpression(), operator, ((Int64Value) value1).longValue());
}
if (value0 instanceof Int64Value &&
getRhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE &&
th.isSubType(getRhsExpression().getItemType(), NumericType.getInstance())) {
return new CompareToIntegerConstant(getRhsExpression(), Token.inverse(operator), ((Int64Value) value0).longValue());
}
// optimize (boolean expression) == (boolean literal)
if (p0 == StandardNames.XS_BOOLEAN &&
p1 == StandardNames.XS_BOOLEAN &&
(operator == Token.FEQ || operator == Token.FNE) &&
getLhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE &&
getRhsExpression().getCardinality() == StaticProperty.EXACTLY_ONE &&
(getLhsExpression() instanceof Literal || getRhsExpression() instanceof Literal)) {
Literal literal = (Literal) (getLhsExpression() instanceof Literal ? getLhsExpression() : getRhsExpression());
Expression other = getLhsExpression() instanceof Literal ? getRhsExpression() : getLhsExpression();
boolean negate = (operator == Token.FEQ) != ((BooleanValue) literal.getValue()).getBooleanValue();
if (negate) {
Expression fn = SystemFunction.makeCall("not", getRetainedStaticContext(), other);
ExpressionTool.copyLocationInfo(this, fn);
return fn.optimize(visitor, contextInfo);
} else {
return other;
}
}
// optimize generate-id(X) = generate-id(Y) as "X is Y"
// This construct is often used in XSLT 1.0 stylesheets.
// Only do this if we know the arguments are singletons, because "is" doesn't
// do first-value extraction.
if (getLhsExpression().isCallOn(GenerateId_1.class) && getRhsExpression().isCallOn(GenerateId_1.class)) {
SystemFunctionCall f0 = (SystemFunctionCall) getLhsExpression();
SystemFunctionCall f1 = (SystemFunctionCall) getRhsExpression();
if (!Cardinality.allowsMany(f0.getArg(0).getCardinality()) &&
!Cardinality.allowsMany(f1.getArg(0).getCardinality()) &&
(operator == Token.FEQ)) {
IdentityComparison id =
new IdentityComparison(f0.getArg(0),
Token.IS,
f1.getArg(0));
id.setGenerateIdEmulation(true);
ExpressionTool.copyLocationInfo(this, id);
return id.typeCheck(visitor, contextInfo).optimize(visitor, contextInfo);
}
}
return this;
}
/**
* Optimize comparisons of count(X) to a literal value. The objective here is to count items in the sequence only
* until the result of the comparison is deducible; for example to evaluate (count(X)>2) we can stop at the third
* item in the sequence.
*
* @param visitor the expression visitor
* @param inverted true if the call to count(X) is the right-hand operand
* @return the optimized expression, or null if no optimization is possible
* @throws XPathException if an error occurs
*/
/*@Nullable*/
private Expression optimizeCount(ExpressionVisitor visitor, boolean inverted) throws XPathException {
SystemFunctionCall countFn = (SystemFunctionCall) (inverted ? getRhsExpression() : getLhsExpression());
Expression sequence = countFn.getArg(0);
sequence = sequence.unordered(false, false);
Optimizer opt = getConfiguration().obtainOptimizer();
AtomicValue literalOperand = (AtomicValue) ((Literal) (inverted ? getLhsExpression() : getRhsExpression())).getValue();
int op = inverted ? Token.inverse(operator) : operator;
if (isZero(literalOperand)) {
if (op == Token.FEQ || op == Token.FLE) {
// rewrite count(x)=0 as empty(x)
Expression result = SystemFunction.makeCall("empty", getRetainedStaticContext(), sequence);
opt.trace("Rewrite count()=0 as:", result);
return result;
} else if (op == Token.FNE || op == Token.FGT) {
// rewrite count(x)!=0, count(x)>0 as exists(x)
Expression result = SystemFunction.makeCall("exists", getRetainedStaticContext(), sequence);
opt.trace("Rewrite count()>0 as:", result);
return result;
} else if (op == Token.FGE) {
// rewrite count(x)>=0 as true()
return Literal.makeLiteral(BooleanValue.TRUE);
} else { // singletonOperator == Token.FLT
// rewrite count(x)<0 as false()
return Literal.makeLiteral(BooleanValue.FALSE);
}
} else if (literalOperand instanceof NumericValue) {
long operand;
if (literalOperand instanceof IntegerValue) {
operand = ((IntegerValue) literalOperand).longValue();
} else if (literalOperand.isNaN()) {
return Literal.makeLiteral(BooleanValue.get(op == Token.FNE));
} else if (((NumericValue) literalOperand).isWholeNumber()) {
operand = ((NumericValue) literalOperand).longValue();
} else if (op == Token.FEQ) {
return Literal.makeLiteral(BooleanValue.FALSE);
} else if (op == Token.FNE) {
return Literal.makeLiteral(BooleanValue.TRUE);
} else if (op == Token.FGT || op == Token.FGE) {
operand = ((NumericValue) literalOperand).ceiling().longValue();
op = Token.FGE;
} else /*if (op == Token.FLT || op == Token.FLE)*/ {
operand = ((NumericValue) literalOperand).floor().longValue();
op = Token.FLE;
}
if (operand < 0) {
switch (op) {
case Token.FEQ:
case Token.FLT:
case Token.FLE:
return Literal.makeLiteral(BooleanValue.FALSE);
default:
return Literal.makeLiteral(BooleanValue.TRUE);
}
}
if (operand > Integer.MAX_VALUE) {
switch (op) {
case Token.FEQ:
case Token.FGT:
case Token.FGE:
return Literal.makeLiteral(BooleanValue.FALSE);
default:
return Literal.makeLiteral(BooleanValue.TRUE);
}
}
if (sequence instanceof TailExpression || sequence.isCallOn(Subsequence_2.class)) {
// it's probably the result of a previous optimization
return null;
}
switch (op) {
case Token.FEQ:
case Token.FNE:
case Token.FLE:
case Token.FLT:
// rewrite count(E) op N as count(subsequence(E, 1, N+1)) op N
Expression ss = SystemFunction.makeCall("subsequence",
getRetainedStaticContext(), sequence,
Literal.makeLiteral(IntegerValue.PLUS_ONE),
Literal.makeLiteral(Int64Value.makeIntegerValue(operand + 1)));
Expression ct = SystemFunction.makeCall("count", getRetainedStaticContext(), ss);
CompareToIntegerConstant ctic = new CompareToIntegerConstant(ct, op, operand);
opt.trace("Rewrite count()~N as:", ctic);
ExpressionTool.copyLocationInfo(this, ctic);
return ctic;
case Token.FGE:
case Token.FGT:
// rewrite count(x) gt n as exists(x[n+1])
// and count(x) ge n as exists(x[n])
TailExpression tail = new TailExpression(sequence, (int) (op == Token.FGE ? operand : operand + 1));
ExpressionTool.copyLocationInfo(this, tail);
Expression result = SystemFunction.makeCall("exists", getRetainedStaticContext(), tail);
ExpressionTool.copyLocationInfo(this, result);
opt.trace("Rewrite count()>=N as:", result);
return result;
}
}
return null;
}
/**
* Check whether this specific instance of the expression is negatable
*
* @return true if it is
* @param th the type hierarchy
*/
public boolean isNegatable(TypeHierarchy th) {
// Expression is not negatable if it might involve NaN
return !maybeNaN(getLhsExpression(), th) && !maybeNaN(getRhsExpression(), th);
}
private boolean maybeNaN(Expression exp, TypeHierarchy th) {
return th.relationship(exp.getItemType(), BuiltInAtomicType.DOUBLE) != TypeHierarchy.DISJOINT ||
th.relationship(exp.getItemType(), BuiltInAtomicType.FLOAT) != TypeHierarchy.DISJOINT;
}
/**
* Return the negation of this value comparison: that is, a value comparison that returns true()
* if and only if the original returns false(). The result must be the same as not(this) even in the
* case where one of the operands is ().
*
* @return the inverted comparison
*/
public Expression negate() {
ValueComparison vc = new ValueComparison(getLhsExpression(), Token.negate(operator), getRhsExpression());
vc.comparer = comparer;
if (resultWhenEmpty == null || resultWhenEmpty == BooleanValue.FALSE) {
vc.resultWhenEmpty = BooleanValue.TRUE;
} else {
vc.resultWhenEmpty = BooleanValue.FALSE;
}
ExpressionTool.copyLocationInfo(this, vc);
return vc;
}
/**
* Test whether an expression is constant zero
*
* @param v the value to be tested
* @return true if the operand is the constant zero (of any numeric data type)
*/
private static boolean isZero(Sequence v) {
return v instanceof NumericValue && ((NumericValue) v).compareTo(0) == 0;
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
* @param rebindings
*/
/*@NotNull*/
public Expression copy(RebindingMap rebindings) {
ValueComparison vc = new ValueComparison(getLhsExpression().copy(rebindings), operator, getRhsExpression().copy(rebindings));
ExpressionTool.copyLocationInfo(this, vc);
vc.comparer = comparer;
vc.resultWhenEmpty = resultWhenEmpty;
vc.needsRuntimeCheck = needsRuntimeCheck;
return vc;
}
/**
* Evaluate the effective boolean value of the expression
*
* @param context the given context for evaluation
* @return a boolean representing the result of the comparison of the two operands
*/
public boolean effectiveBooleanValue(XPathContext context) throws XPathException {
try {
AtomicValue v0 = (AtomicValue) getLhsExpression().evaluateItem(context);
if (v0 == null) {
return resultWhenEmpty == BooleanValue.TRUE; // normally false
}
AtomicValue v1 = (AtomicValue) getRhsExpression().evaluateItem(context);
if (v1 == null) {
return resultWhenEmpty == BooleanValue.TRUE; // normally false
}
return compare(v0, operator, v1, comparer.provideContext(context), needsRuntimeCheck);
} catch (XPathException e) {
// re-throw the exception with location information added
e.maybeSetLocation(getLocation());
e.maybeSetContext(context);
throw e;
}
}
/**
* Compare two atomic values, using a specified operator and collation
*
* @param v0 the first operand
* @param op the operator, as defined by constants such as {@link net.sf.saxon.expr.parser.Token#FEQ} or
* {@link net.sf.saxon.expr.parser.Token#FLT}
* @param v1 the second operand
* @param comparer the Collator to be used when comparing strings
* @param checkTypes set to true if it is necessary to check that the types of the arguments are comparable
* @return the result of the comparison: -1 for LT, 0 for EQ, +1 for GT
* @throws XPathException if the values are not comparable
*/
static boolean compare(AtomicValue v0, int op, AtomicValue v1, AtomicComparer comparer, boolean checkTypes)
throws XPathException {
if (checkTypes &&
!Type.isGuaranteedComparable(v0.getPrimitiveType(), v1.getPrimitiveType(), Token.isOrderedOperator(op))) {
XPathException e2 = new XPathException("Cannot compare " + Type.displayTypeName(v0) +
" to " + Type.displayTypeName(v1));
e2.setErrorCode("XPTY0004");
e2.setIsTypeError(true);
throw e2;
}
if (v0.isNaN() || v1.isNaN()) {
return op == Token.FNE;
}
try {
switch (op) {
case Token.FEQ:
return comparer.comparesEqual(v0, v1);
case Token.FNE:
return !comparer.comparesEqual(v0, v1);
case Token.FGT:
return comparer.compareAtomicValues(v0, v1) > 0;
case Token.FLT:
return comparer.compareAtomicValues(v0, v1) < 0;
case Token.FGE:
return comparer.compareAtomicValues(v0, v1) >= 0;
case Token.FLE:
return comparer.compareAtomicValues(v0, v1) <= 0;
default:
throw new UnsupportedOperationException("Unknown operator " + op);
}
} catch (ClassCastException err) {
XPathException e2 = new XPathException("Cannot compare " + Type.displayTypeName(v0) +
" to " + Type.displayTypeName(v1));
e2.setErrorCode("XPTY0004");
e2.setIsTypeError(true);
throw e2;
}
}
/**
* Evaluate the expression in a given context
*
* @param context the given context for evaluation
* @return a BooleanValue representing the result of the numeric comparison of the two operands,
* or null representing the empty sequence
*/
public BooleanValue evaluateItem(XPathContext context) throws XPathException {
try {
AtomicValue v0 = (AtomicValue) getLhsExpression().evaluateItem(context);
if (v0 == null) {
return resultWhenEmpty;
}
AtomicValue v1 = (AtomicValue) getRhsExpression().evaluateItem(context);
if (v1 == null) {
return resultWhenEmpty;
}
return BooleanValue.get(compare(v0, operator, v1, comparer.provideContext(context), needsRuntimeCheck));
} catch (XPathException e) {
// re-throw the exception with location information added
e.maybeSetLocation(getLocation());
e.maybeSetContext(context);
throw e;
}
}
/**
* Call the Callable.
*
* @param context the dynamic evaluation context
* @param arguments the values of the arguments, supplied as Sequences.
* Generally it is advisable, if calling iterate() to process a supplied sequence, to
* call it only once; if the value is required more than once, it should first be converted
* to a {@link net.sf.saxon.om.GroundedValue} by calling the utility methd
* SequenceTool.toGroundedValue().
* If the expected value is a single item, the item should be obtained by calling
* Sequence.head(): it cannot be assumed that the item will be passed as an instance of
* {@link net.sf.saxon.om.Item} or {@link net.sf.saxon.value.AtomicValue}.
* It is the caller's responsibility to perform any type conversions required
* to convert arguments to the type expected by the callee. An exception is where
* this Callable is explicitly an argument-converting wrapper around the original
* Callable.
* @return the result of the evaluation, in the form of a Sequence. It is the responsibility
* of the callee to ensure that the type of result conforms to the expected result type.
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs during the evaluation of the expression
*/
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
return null;
}
/**
* Determine the data type of the expression
*
* @return Type.BOOLEAN
*/
/*@NotNull*/
public ItemType getItemType() {
return BuiltInAtomicType.BOOLEAN;
}
/**
* Determine the static cardinality.
*/
public int computeCardinality() {
if (resultWhenEmpty != null) {
return StaticProperty.EXACTLY_ONE;
} else {
return super.computeCardinality();
}
}
@Override
public String tag() {
return "vc";
}
@Override
protected void explainExtraAttributes(ExpressionPresenter out) {
if (resultWhenEmpty != null) {
out.emitAttribute("onEmpty", resultWhenEmpty.getBooleanValue() ? "1" : "0");
}
out.emitAttribute("comp", comparer.save());
}
/* *//**
* Find any necessary preconditions for the satisfaction of this expression
* as a set of boolean expressions to be evaluated on the context node
*
* @return A set of conditions, or null if none have been computed
*//*
public Set getPreconditions() {
HashSet pre = new HashSet();
Expression r = RealPreconditions.reducedFunction(this);
if(r != null) {
pre.add(r);
}
return pre;
}*/
}