All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.ValueComparison Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.BooleanEvaluator;
import net.sf.saxon.expr.elab.Elaborator;
import net.sf.saxon.expr.elab.ItemElaborator;
import net.sf.saxon.expr.elab.ItemEvaluator;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.ComparisonException;
import net.sf.saxon.expr.sort.GenericAtomicComparer;
import net.sf.saxon.lib.StringCollator;
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.Cardinality;
import net.sf.saxon.value.SequenceType;

import java.util.function.Supplier;

/**
 * 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 {

    /*@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.
     */

    @Override
    public String getExpressionName() {
        return "ValueComparison";
    }

    /**
     * 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.
     */

    @Override
    public AtomicComparer getAtomicComparer() {
        // TODO: this is scaffolding. ValueComparison no longer uses an AtomicComparer, but this method
        // is retained for paths that require one, e.g. EqualityPatternOptimizer
        ItemType t0 = getLhsExpression().getItemType().getPrimitiveItemType();
        if (!(t0 instanceof BuiltInAtomicType)) {
            // This can happen after loading from a SEF file; the static type information is not always available
            t0 = BuiltInAtomicType.ANY_ATOMIC;
        }
        ItemType t1 = getRhsExpression().getItemType().getPrimitiveItemType();
        if (!(t1 instanceof BuiltInAtomicType)) {
            // This can happen after loading from a SEF file; the static type information is not always available
            t1 = BuiltInAtomicType.ANY_ATOMIC;
        }
        return GenericAtomicComparer.makeAtomicComparer(
                (BuiltInAtomicType)t0,
                (BuiltInAtomicType)t1,
                getStringCollator(),
                getConfiguration().getConversionContext());

    }

    /**
     * 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() {
        try {
            return getConfiguration().getCollation((getRetainedStaticContext().getDefaultCollationName()));
        } catch (XPathException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Get the primitive (singleton) operator used: one of Token.FEQ, Token.FNE, Token.FLT, Token.FGT,
     * Token.FLE, Token.FGE
     */

    @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;
    }

    /**
     * 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*/
    @Override
    public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {

        resetLocalStaticProperties();
        getLhs().typeCheck(visitor, contextInfo);
        getRhs().typeCheck(visitor, contextInfo);

        Configuration config = visitor.getConfiguration();

        if (Literal.isEmptySequence(getLhsExpression())) {
            return resultWhenEmpty == null ? getLhsExpression() : Literal.makeLiteral(resultWhenEmpty, this);
        }

        if (Literal.isEmptySequence(getRhsExpression())) {
            return resultWhenEmpty == null ? getRhsExpression() : Literal.makeLiteral(resultWhenEmpty, this);
        }

        if (convertsUntypedToOther()) {
            return this; // we've already done all that needs to be done
        }

        final SequenceType optionalAtomic = SequenceType.OPTIONAL_ATOMIC;
        TypeChecker tc = config.getTypeChecker(false);

        Supplier role0 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 0);
        setLhsExpression(tc.staticTypeCheck(getLhsExpression(), optionalAtomic, role0, visitor));

        Supplier role1 = () -> new RoleDiagnostic(RoleDiagnostic.BINARY_EXPR, Token.tokens[operator], 1);
        setRhsExpression(tc.staticTypeCheck(getRhsExpression(), optionalAtomic, role1, visitor));

        PlainType t0 = getLhsExpression().getItemType().getAtomizedItemType();
        PlainType t1 = getRhsExpression().getItemType().getAtomizedItemType();

        if (t0.getUType().union(t1.getUType()).overlaps(UType.EXTENSION)) {
            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 +
                        (opt0 ? "?" : "") + " to " + t1 +
                        (opt1 ? "?" : "") + " will fail unless " + which + " empty", SaxonErrorCode.SXWN9026, getLocation());
                needsRuntimeCheck = true;
            } else {
                String message = "In {" + toShortString() + "}: cannot compare " +
                        t0 + " to " + t1;
                XPathException err = new XPathException(message);
                err.setIsTypeError(true);
                err.setErrorCode("XPTY0004");
                err.setLocation(getLocation());
                throw err;
            }
        }
        if (!(operator == Token.FEQ || operator == Token.FNE)) {
            mustBeOrdered(t0, p0);
            mustBeOrdered(t1, p1);
        }
        return this;
    }

    private void mustBeOrdered(PlainType t1, BuiltInAtomicType p1) throws XPathException {
        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;
        }
    }

    /**
     * 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*/ @Override public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException { getLhs().optimize(visitor, contextInfo); getRhs().optimize(visitor, contextInfo); return visitor.obtainOptimizer().optimizeValueComparison(this, visitor, contextInfo); } /** * Check whether this specific instance of the expression is negatable * * @return true if it is * @param th the type hierarchy */ @Override public boolean isNegatable(TypeHierarchy th) { // Expression is not negatable if it might involve NaN return isNeverNaN(getLhsExpression(), th) && isNeverNaN(getRhsExpression(), th); } private boolean isNeverNaN(Expression exp, TypeHierarchy th) { return th.relationship(exp.getItemType(), BuiltInAtomicType.DOUBLE) == Affinity.DISJOINT && th.relationship(exp.getItemType(), BuiltInAtomicType.FLOAT) == Affinity.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 */ @Override public Expression negate() { ValueComparison vc = new ValueComparison(getLhsExpression(), Token.negate(operator), getRhsExpression()); if (resultWhenEmpty == null || resultWhenEmpty == BooleanValue.FALSE) { vc.resultWhenEmpty = BooleanValue.TRUE; } else { vc.resultWhenEmpty = BooleanValue.FALSE; } vc.needsRuntimeCheck = needsRuntimeCheck; ExpressionTool.copyLocationInfo(this, vc); return vc; } @Override public boolean equals(Object other) { return other instanceof ValueComparison && super.equals(other) //&& comparer.equals(((ValueComparison) other).comparer) ; } /** * Get a hashCode for comparing two expressions. Note that this hashcode gives the same * result for (A op B) and for (B op A), whether or not the operator is commutative. */ @Override protected int computeHashCode() { return super.computeHashCode(); } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression * @param rebindings variables that need to be re-bound */ /*@NotNull*/ @Override public Expression copy(RebindingMap rebindings) { ValueComparison vc = new ValueComparison(getLhsExpression().copy(rebindings), operator, getRhsExpression().copy(rebindings)); ExpressionTool.copyLocationInfo(this, vc); 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 */ @Override public boolean effectiveBooleanValue(XPathContext context) throws XPathException { return makeElaborator().elaborateForBoolean().eval(context); } /** * 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 used to compare values. If the comparer is context-sensitive then the context must * already have been bound using comparer.provideContext(). * @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 */ public 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 (ComparisonException err) { throw err.getReason(); } catch (ClassCastException err) { //err.printStackTrace(); 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 */ @Override public BooleanValue evaluateItem(XPathContext context) throws XPathException { return (BooleanValue)makeElaborator().elaborateForItem().eval(context); } /** * Determine the data type of the expression * * @return Type.BOOLEAN */ /*@NotNull*/ @Override public ItemType getItemType() { return BuiltInAtomicType.BOOLEAN; } /** * Get the static type of the expression as a UType, following precisely the type * inference rules defined in the XSLT 3.0 specification. * * @return the static item type of the expression according to the XSLT 3.0 defined rules * @param contextItemType the static type of the context item */ @Override public UType getStaticUType(UType contextItemType) { return UType.BOOLEAN; } /** * Determine the static cardinality. */ @Override protected int computeCardinality() { if (resultWhenEmpty != null) { return StaticProperty.EXACTLY_ONE; } else { return super.computeCardinality(); } } @Override protected String tag() { return "vc"; } @Override protected void explainExtraAttributes(ExpressionPresenter out) { if (resultWhenEmpty != null) { out.emitAttribute("onEmpty", resultWhenEmpty.getBooleanValue() ? "1" : "0"); } if ("JS".equals(out.getOptions().target) && out.getOptions().targetVersion == 2) { // for backwards compatibility, output a comp attribute AtomicComparer comparer = getAtomicComparer(); out.emitAttribute("comp", comparer.save()); } } /** * Make an elaborator for this expression * * @return a suitable elaborator */ @Override public Elaborator getElaborator() { return new ValueComparisonElaborator(); } /** * Elaborator for a value comparison (such as {@code A eq B}), including the case * where a general comparison is reduced to a value comparison by the optimiser */ public static class ValueComparisonElaborator extends ItemElaborator { public ItemEvaluator elaborateForItem() { final ValueComparison expr = (ValueComparison) getExpression(); final ItemEvaluator p0 = expr.getLhsExpression().makeElaborator().elaborateForItem(); final ItemEvaluator p1 = expr.getRhsExpression().makeElaborator().elaborateForItem(); final BooleanValue resultWhenEmpty = expr.getResultWhenEmpty(); StringCollator defaultCollation; try { defaultCollation = expr.getConfiguration().getCollation(expr.getRetainedStaticContext().getDefaultCollationName()); } catch (XPathException e) { throw new IllegalStateException("Unknown default collation in static context: " + expr.getRetainedStaticContext().getDefaultCollationName()); } final int operator = expr.getOperator(); final int card0 = expr.getLhsExpression().getCardinality(); final int card1 = expr.getRhsExpression().getCardinality(); if (card0 == StaticProperty.ALLOWS_ZERO || card1 == StaticProperty.ALLOWS_ZERO) { return context -> resultWhenEmpty; } GenericAtomicComparer.AtomicComparisonFunction comparer = GenericAtomicComparer.makeAtomicComparisonFunction( operandType(expr.getLhsExpression()), operandType(expr.getRhsExpression()), defaultCollation, operator, true); final boolean nullable0 = Cardinality.allowsZero(card0); final boolean nullable1 = Cardinality.allowsZero(card1); if (!nullable0 && !nullable1) { return context -> BooleanValue.get( comparer.compare((AtomicValue) p0.eval(context), (AtomicValue) p1.eval(context), context)); } else { return context -> { AtomicValue v0 = (AtomicValue) p0.eval(context); if (v0 == null) { return resultWhenEmpty; // normally false } AtomicValue v1 = (AtomicValue) p1.eval(context); if (v1 == null) { return resultWhenEmpty; // normally false } return BooleanValue.get(comparer.compare(v0, v1, context)); }; } } private BuiltInAtomicType operandType(Expression operand) { ItemType type = operand.getItemType(); if (type == AnyItemType.getInstance()) { return BuiltInAtomicType.ANY_ATOMIC; } else { return (BuiltInAtomicType) type.getPrimitiveItemType(); } } public BooleanEvaluator elaborateForBoolean() { final ValueComparison expr = (ValueComparison) getExpression(); final ItemEvaluator p0 = expr.getLhsExpression().makeElaborator().elaborateForItem(); final ItemEvaluator p1 = expr.getRhsExpression().makeElaborator().elaborateForItem(); StringCollator defaultCollation; try { defaultCollation = expr.getConfiguration().getCollation(expr.getRetainedStaticContext().getDefaultCollationName()); } catch (XPathException e) { throw new IllegalStateException("Unknown default collation in static context: " + expr.getRetainedStaticContext().getDefaultCollationName()); } final int operator = expr.getOperator(); final boolean resultWhenEmpty = expr.getResultWhenEmpty() != null && expr.getResultWhenEmpty().getBooleanValue(); final int card0 = expr.getLhsExpression().getCardinality(); final int card1 = expr.getRhsExpression().getCardinality(); if (card0 == StaticProperty.ALLOWS_ZERO || card1 == StaticProperty.ALLOWS_ZERO) { return context -> resultWhenEmpty; } ItemType t0 = expr.getLhsExpression().getItemType().getPrimitiveItemType(); if (!(t0 instanceof BuiltInAtomicType)) { // This can happen after loading from a SEF file; the static type information is not always available t0 = BuiltInAtomicType.ANY_ATOMIC; } ItemType t1 = expr.getRhsExpression().getItemType().getPrimitiveItemType(); if (!(t1 instanceof BuiltInAtomicType)) { // This can happen after loading from a SEF file; the static type information is not always available t1 = BuiltInAtomicType.ANY_ATOMIC; } final GenericAtomicComparer.AtomicComparisonFunction comparer = GenericAtomicComparer.makeAtomicComparisonFunction( (BuiltInAtomicType) t0, (BuiltInAtomicType) t1, defaultCollation, operator, true); final boolean nullable0 = Cardinality.allowsZero(card0); final boolean nullable1 = Cardinality.allowsZero(card1); if (!nullable0 && !nullable1) { return context -> comparer.compare((AtomicValue) p0.eval(context), (AtomicValue) p1.eval(context), context); } else { return context -> { AtomicValue v0 = (AtomicValue) p0.eval(context); if (v0 == null) { return resultWhenEmpty; // normally false } AtomicValue v1 = (AtomicValue) p1.eval(context); if (v1 == null) { return resultWhenEmpty; // normally false } return comparer.compare(v0, v1, context); }; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy