![JAR search and dependency download from the Maven repository](/logo.png)
net.sf.saxon.expr.SwitchCaseComparison Maven / Gradle / Ivy
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 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.elab.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.CodepointCollator;
import net.sf.saxon.lib.StringCollator;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.SaxonErrorCode;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.BooleanValue;
import net.sf.saxon.value.SequenceType;
import java.util.function.Supplier;
/**
* Class to handle comparisons for XQuery switch expressions. This only handles equality comparison.
* It implements the rules used for XQuery 3.0 switch expressions:
* - each operand must be zero or one atomic values
* - untypedAtomic is treated as string
* - non-comparable values are not equal (no type errors)
* - two empty sequences are equal to each other
* - two NaN values are equal to each other
* In 4.0 this is extended so the second operand can contain multiple values,
* and the result is true if any of them match.
*/
public class SwitchCaseComparison extends BinaryExpression implements ComparisonExpression {
private AtomicComparer comparer;
private boolean knownToBeComparable = false;
private boolean allowMultiple;
/**
* Create a singleton comparison - that is, a comparison between two singleton (0:1) sequences
* using the general comparison semantics
*
* @param p1 the first operand
* @param operator the operator
* @param p2 the second operand
* @param allowMultiple true if the 4.0 semantics are implemented (second operand may be a sequence)
*/
public SwitchCaseComparison(Expression p1, int operator, Expression p2, boolean allowMultiple) {
super(p1, operator, p2);
this.allowMultiple = allowMultiple;
}
/**
* Type-check the expression. Default implementation for binary operators that accept
* any kind of operand
*/
/*@NotNull*/
@Override
public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
StaticContext env = visitor.getStaticContext();
String defaultCollationName = env.getDefaultCollationName();
final Configuration config = visitor.getConfiguration();
StringCollator collation = config.getCollation(defaultCollationName);
if (collation == null) {
collation = CodepointCollator.getInstance();
}
comparer = new SwitchCaseComparer(collation, config.getConversionContext());
Expression oldOp0 = getLhsExpression();
Expression oldOp1 = getRhsExpression();
getLhs().typeCheck(visitor, contextInfo);
getRhs().typeCheck(visitor, contextInfo);
// Neither operand needs to be sorted
setLhsExpression(getLhsExpression().unordered(false, false));
setRhsExpression(getRhsExpression().unordered(false, false));
SequenceType lhsType = SequenceType.OPTIONAL_ATOMIC;
SequenceType rhsType = allowMultiple ? SequenceType.ATOMIC_SEQUENCE : SequenceType.OPTIONAL_ATOMIC;
TypeChecker tc = config.getTypeChecker(false);
Supplier role0 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, "eq", 0);
setLhsExpression(tc.staticTypeCheck(getLhsExpression(), lhsType, role0, visitor));
Supplier role1 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, "eq", 1);
setRhsExpression(tc.staticTypeCheck(getRhsExpression(), rhsType, role1, visitor));
if (getLhsExpression() != oldOp0) {
adoptChildExpression(getLhsExpression());
}
if (getRhsExpression() != oldOp1) {
adoptChildExpression(getRhsExpression());
}
ItemType t0 = getLhsExpression().getItemType(); // this is always an atomic type or empty-sequence()
ItemType t1 = getRhsExpression().getItemType(); // this is always an atomic type or empty-sequence()
if (t0 instanceof ErrorType) {
t0 = BuiltInAtomicType.ANY_ATOMIC;
}
if (t1 instanceof ErrorType) {
t1 = BuiltInAtomicType.ANY_ATOMIC;
}
if (t0.getUType().union(t1.getUType()).overlaps(UType.EXTENSION)) {
throw new XPathException("Cannot perform comparisons involving external objects")
.asTypeError().withErrorCode("XPTY0004").withLocation(getLocation());
}
BuiltInAtomicType pt0 = (BuiltInAtomicType) t0.getPrimitiveItemType();
BuiltInAtomicType pt1 = (BuiltInAtomicType) t1.getPrimitiveItemType();
if (t0.equals(BuiltInAtomicType.ANY_ATOMIC) || t0.equals(BuiltInAtomicType.UNTYPED_ATOMIC) ||
t1.equals(BuiltInAtomicType.ANY_ATOMIC) || t1.equals(BuiltInAtomicType.UNTYPED_ATOMIC)) {
// then no static type checking is possible
} else {
if (Type.isGuaranteedComparable(pt0, pt1, false)) {
knownToBeComparable = true;
} else if (!Type.isPossiblyComparable(pt0, pt1, visitor.getStaticContext().getXPathVersion())) {
env.issueWarning("Cannot compare " + t0 + " to " + t1, SaxonErrorCode.SXWN9025, getLocation());
// This is not an error in a switch statement, but it means the branch will never be chosen
}
}
try {
if ((getLhsExpression() instanceof Literal) && (getRhsExpression() instanceof Literal)) {
GroundedValue v = evaluateItem(visitor.getStaticContext().makeEarlyEvaluationContext()).materialize();
return Literal.makeLiteral(v, this);
}
} catch (XPathException err) {
// if early evaluation fails, suppress the error: the value might
// not be needed at run-time
}
return this;
}
@Override
public AtomicComparer getAtomicComparer() {
return comparer;
}
/**
* Get the StringCollator used to compare string values.
* @return the collator. May return null if the expression will never be used to compare strings
*/
@Override
public StringCollator getStringCollator() {
return comparer.getCollator();
}
@Override
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.
*/
@Override
public boolean convertsUntypedToOther() {
return false;
}
/**
* Determine the static cardinality. Returns [1..1]
*/
@Override
protected int computeCardinality() {
return StaticProperty.EXACTLY_ONE;
}
/**
* Determine the data type of the expression
*
* @return Type.BOOLEAN
*/
/*@NotNull*/
@Override
public ItemType getItemType() {
return BuiltInAtomicType.BOOLEAN;
}
public boolean isKnownToBeComparable() {
return knownToBeComparable;
}
public AtomicComparer getComparer() {
return comparer;
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
* @param rebindings variables that must be re-bound
*/
/*@NotNull*/
@Override
public Expression copy(RebindingMap rebindings) {
SwitchCaseComparison sc = new SwitchCaseComparison(
getLhsExpression().copy(rebindings),
operator,
getRhsExpression().copy(rebindings),
allowMultiple);
ExpressionTool.copyLocationInfo(this, sc);
sc.comparer = comparer;
sc.knownToBeComparable = knownToBeComparable;
return sc;
}
/**
* 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
*/
@Override
public BooleanValue evaluateItem(XPathContext context) throws XPathException {
return BooleanValue.get(effectiveBooleanValue(context));
}
/**
* Evaluate the expression in a boolean context
*
* @param context the given context for evaluation
* @return a boolean representing the result of the numeric comparison of the two operands
*/
@Override
public boolean effectiveBooleanValue(XPathContext context) throws XPathException {
return makeElaborator().elaborateForBoolean().eval(context);
}
/**
* 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 export() output displaying the expression.
*/
@Override
public String getExpressionName() {
return "equivalent";
}
@Override
protected void explainExtraAttributes(ExpressionPresenter out) {
out.emitAttribute("cardinality", "singleton");
}
@Override
public Elaborator getElaborator() {
return new EquivalenceComparisonElaborator();
}
private static class EquivalenceComparisonElaborator extends BooleanElaborator {
@Override
public BooleanEvaluator elaborateForBoolean() {
SwitchCaseComparison expr = (SwitchCaseComparison)getExpression();
ItemEvaluator eval0 = expr.getLhsExpression().makeElaborator().elaborateForItem();
if (expr.allowMultiple) {
// Switch expression has been generalized in 4.0
PullEvaluator eval1 = expr.getRhsExpression().makeElaborator().elaborateForPull();
return context -> {
AtomicValue v0 = (AtomicValue) eval0.eval(context);
SequenceIterator iter1 = eval1.iterate(context);
AtomicComparer comp2 = expr.comparer.provideContext(context);
if (v0 == null) {
boolean empty = iter1.next() == null;
iter1.close();
return empty;
} else {
AtomicValue v1;
while ((v1 = (AtomicValue)iter1.next()) != null) {
if (((expr.knownToBeComparable || Type.isGuaranteedComparable(v0.getPrimitiveType(), v1.getPrimitiveType(), false)))
&& comp2.comparesEqual(v0, v1)) {
iter1.close();
return true;
}
}
return false;
}
};
} else {
ItemEvaluator eval1 = expr.getRhsExpression().makeElaborator().elaborateForItem();
return context -> {
AtomicValue v0 = (AtomicValue) eval0.eval(context);
AtomicValue v1 = (AtomicValue) eval1.eval(context);
if (v0 == null || v1 == null) {
return (v0 == v1);
}
AtomicComparer comp2 = expr.comparer.provideContext(context);
return ((expr.knownToBeComparable || Type.isGuaranteedComparable(v0.getPrimitiveType(), v1.getPrimitiveType(), false)))
&& comp2.comparesEqual(v0, v1);
};
}
}
}
}
// Copyright (c) 2010-2023 Saxonica Limited
© 2015 - 2025 Weber Informatics LLC | Privacy Policy