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

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