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

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

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 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.event.Outputter;
import net.sf.saxon.event.ReceiverOption;
import net.sf.saxon.expr.flwor.LocalVariableBinding;
import net.sf.saxon.expr.instruct.GlobalParam;
import net.sf.saxon.expr.instruct.LocalParam;
import net.sf.saxon.expr.instruct.LocalParamBlock;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.lib.StandardDiagnostics;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.IntegerValue;
import net.sf.saxon.value.SequenceType;

/**
 * Variable reference: a reference to a variable. This may be an XSLT-defined variable, a range
 * variable defined within the XPath expression, or a variable defined in some other static context.
 */

public abstract class VariableReference extends Expression implements BindingReference {

    /*@Nullable*/ protected Binding binding = null;     // This will be null until fixup() is called; it will also be null
    // if the variable reference has been inlined
    protected SequenceType staticType = null;
    protected GroundedValue constantValue = null;
    private StructuredQName variableName = null;
    private boolean flattened = false;
    private boolean inLoop = false;
    private boolean filtered = false;

    /**
     * Create a Variable Reference
     * @param name the name of the variable
     */

    public VariableReference(StructuredQName name) {
        variableName = name;
    }

    /**
     * Create a Variable Reference
     *
     * @param binding the variable binding to which this variable refers
     */

    public VariableReference(Binding binding) {
        //System.err.println("Creating varRef1");
        variableName = binding.getVariableQName();
        fixup(binding);
    }

    /**
     * Set the variable name
     *
     * @param name the name of the variable
     */

    public void setVariableName(StructuredQName name) {
        variableName = name;
    }

    /**
     * Get the variable name
     *
     * @return the name of the variable
     */

    public StructuredQName getVariableName() {
        return variableName;
    }

    /**
     * Create a clone copy of this VariableReference
     *
     * @return the cloned copy
     * @param rebindings variables that need to switch to new bindings
     */

    /*@NotNull*/
    @Override
    public abstract Expression copy(RebindingMap rebindings);


    @Override
    public int getNetCost() {
        return 0;
    }

    protected void copyFrom(VariableReference ref) {
        binding = ref.binding;
        staticType = ref.staticType;
        constantValue = ref.constantValue;
        variableName = ref.variableName;
        flattened = ref.flattened;
        inLoop = ref.inLoop;
        filtered = ref.filtered;
        ExpressionTool.copyLocationInfo(ref, this);
    }

    /**
     * Set static type. This is a callback from the variable declaration object. As well
     * as supplying the static type, it may also supply a compile-time value for the variable.
     * As well as the type information, other static properties of the value are supplied:
     * for example, whether the value is an ordered node-set.
     *  @param type       the static type of the variable
     * @param value      the value of the variable if this is a compile-time constant, or null otherwise
     * @param properties static properties of the expression to which the variable is bound
     */

    @Override
    public void setStaticType(SequenceType type, GroundedValue value, int properties) {
        // System.err.println(this + " Set static type = " + type);
        if (type == null) {
            type = SequenceType.ANY_SEQUENCE;
        }
        staticType = type;
        constantValue = value;
        // Although the variable may be a context document node-set at the point it is defined,
        // the context at the point of use may be different, so this property cannot be transferred.
        int dependencies = getDependencies();
        staticProperties = (properties & ~StaticProperty.CONTEXT_DOCUMENT_NODESET
                        & ~StaticProperty.ALL_NODES_NEWLY_CREATED) |
                StaticProperty.NO_NODES_NEWLY_CREATED |
                type.getCardinality() |
                dependencies;
    }

    /**
     * Mark an expression as being "flattened". This is a collective term that includes extracting the
     * string value or typed value, or operations such as simple value construction that concatenate text
     * nodes before atomizing. The implication of all of these is that although the expression might
     * return nodes, the identity of the nodes has no significance. This is called during type checking
     * of the parent expression. At present, only variable references take any notice of this notification.
     */

    @Override
    public void setFlattened(boolean flattened) {
        this.flattened = flattened;
    }

    /**
     * Test whether this variable reference is flattened - that is, whether it is atomized etc
     *
     * @return true if the value of the variable is atomized, or converted to a string or number
     */

    public boolean isFlattened() {
        return flattened;
    }

    /**
     * Mark an expression as filtered: that is, it appears as the base expression in a filter expression.
     * This notification currently has no effect except when the expression is a variable reference.
     */

    @Override
    public void setFiltered(boolean filtered) {
        this.filtered = filtered;
    }

    /**
     * Determine whether this variable reference is filtered
     *
     * @return true if the value of the variable is filtered by a predicate
     */

    public boolean isFiltered() {
        return filtered;
    }

    /**
     * Ask whether this variable reference appears in a loop relative to its declaration.
     * By default, when in doubt, returns true. This is calculated during type-checking.
     *
     * @return true if this variable reference occurs in a loop, where the variable declaration is
     * outside the loop
     */

    public boolean isInLoop() {
        return inLoop;
    }

    /**
     * Say whether this variable reference appears in a loop relative to its declaration.
     *
     * @param inLoop true if this variable reference occurs in a loop, where the variable declaration is
     * outside the loop
     */

    public void setInLoop(boolean inLoop) {
        this.inLoop = inLoop;
    }

    /**
     * Type-check the expression. At this stage details of the static type must be known.
     * If the variable has a compile-time value, this is substituted for the variable reference
     */

    /*@NotNull*/
    @Override
    public Expression typeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
        if (constantValue != null) {
            binding = null;
            return Literal.makeLiteral(constantValue, this);
        }
//        if (staticType == null) {
//            throw new IllegalStateException("Variable $" + getDisplayName() + " has not been fixed up");
//        }



//  following code removed because it causes error181 to blow the stack - need to check for circularities well
//            if (binding instanceof GlobalVariable) {
//                ((GlobalVariable)binding).typeCheck(visitor, AnyItemType.getInstance());
//            }

        if (binding != null) {
            recomputeInLoop();
            binding.addReference(this, inLoop);
        }

        return this;
    }

    public void recomputeInLoop() {
        inLoop = ExpressionTool.isLoopingReference(this, binding);
    }

    /**
     * Optimize the expression. At this stage details of the static type must be known.
     * If the variable has a compile-time value, this is substituted for the variable reference
     */

    /*@NotNull*/
    @Override
    public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) throws XPathException {
        if (binding instanceof LetExpression &&
                ((LetExpression)binding).getSequence() instanceof Literal &&
                !((LetExpression) binding).indexedVariable) {
            Expression val = ((LetExpression) binding).getSequence();
            Optimizer.trace(visitor.getConfiguration(), "Replaced variable " + getDisplayName() + " by its value", val);
            binding = null;
            return val.copy(new RebindingMap());
        }
        if (constantValue != null) {
            binding = null;
            Expression result = Literal.makeLiteral(constantValue, this);
            ExpressionTool.copyLocationInfo(this, result);
            Optimizer.trace(visitor.getConfiguration(), "Replaced variable " + getDisplayName() + " by its value", result);
            return result;
        }
        if (binding instanceof GlobalParam && ((GlobalParam)binding).isStatic()) {
            Expression select = ((GlobalParam) binding).getBody();
            if (select instanceof Literal) {
                binding = null;
                Optimizer.trace(visitor.getConfiguration(), "Replaced static parameter " + getDisplayName() + " by its value", select);
                return select.copy(new RebindingMap());
            }
        }
        return this;
    }


    /**
     * Fix up this variable reference to a Binding object, which enables the value of the variable
     * to be located at run-time.
     */

    @Override
    public void fixup(Binding newBinding) {
        boolean indexed = binding instanceof LocalBinding && ((LocalBinding)binding).isIndexedVariable();
        this.binding = newBinding;
        if (indexed && newBinding instanceof LocalBinding) {
            ((LocalBinding)newBinding).setIndexedVariable();
        }
        resetLocalStaticProperties();
    }

    /**
     * Provide additional information about the type of the variable, typically derived by analyzing
     * the initializer of the variable binding
     *
     * @param type          the item type of the variable
     * @param cardinality   the cardinality of the variable
     * @param constantValue the actual value of the variable, if this is known statically, otherwise null
     * @param properties    additional static properties of the variable's initializer
     */

    public void refineVariableType(ItemType type, int cardinality, GroundedValue constantValue, int properties) {
        TypeHierarchy th = getConfiguration().getTypeHierarchy();
        ItemType oldItemType = getItemType();
        ItemType newItemType = oldItemType;
        if (th.isSubType(type, oldItemType)) {
            newItemType = type;
        }
        if (oldItemType instanceof NodeTest && type instanceof AtomicType) {
            // happens when all references are flattened
            newItemType = type;
        }
        int newcard = cardinality & getCardinality();
        if (newcard == 0) {
            // this will probably lead to a type error later
            newcard = getCardinality();
        }
        SequenceType seqType = SequenceType.makeSequenceType(newItemType, newcard);
        setStaticType(seqType, constantValue, properties);
    }

    /**
     * Determine the data type of the expression, if possible
     *
     * @return the type of the variable, if this can be determined statically;
     * otherwise Type.ITEM (meaning not known in advance)
     */

    /*@NotNull*/
    @Override
    public ItemType getItemType() {
        if (staticType == null || staticType.getPrimaryType() == AnyItemType.getInstance()) {
            if (binding != null) {
                SequenceType st = binding.getRequiredType();
                if (st != null) {
                    return st.getPrimaryType();
                }
            }
            return AnyItemType.getInstance();
        } else {
            return staticType.getPrimaryType();
        }
    }

    /**
     * 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 context item type
     */
    @Override
    public UType getStaticUType(UType contextItemType) {
        if (binding != null) {
            if (binding.isGlobal() ||
                    binding instanceof LocalParam ||
                    (binding instanceof LetExpression && ((LetExpression)binding).isInstruction()) ||
                    binding instanceof LocalVariableBinding) {
                SequenceType st = binding.getRequiredType();
                if (st != null) {
                    return st.getPrimaryType().getUType();
                } else {
                    return UType.ANY;
                }
            } else if (binding instanceof Assignation) {
                return ((Assignation) binding).getSequence().getStaticUType(contextItemType);
            }
        }
        return UType.ANY;
    }

    /**
     * For an expression that returns an integer or a sequence of integers, get
     * a lower and upper bound on the values of the integers that may be returned, from
     * static analysis. The default implementation returns null, meaning "unknown" or
     * "not applicable". Other implementations return an array of two IntegerValue objects,
     * representing the lower and upper bounds respectively. The values
     * UNBOUNDED_LOWER and UNBOUNDED_UPPER are used by convention to indicate that
     * the value may be arbitrarily large. The values MAX_STRING_LENGTH and MAX_SEQUENCE_LENGTH
     * are used to indicate values limited by the size of a string or the size of a sequence.
     *
     * @return the lower and upper bounds of integer values in the result, or null to indicate
     * unknown or not applicable.
     */
    @Override
    public IntegerValue[] getIntegerBounds() {
        if (binding != null) {
            return binding.getIntegerBoundsForVariable();
        } else {
            return null;
        }
    }

    /**
     * Get the static cardinality
     */

    @Override
    protected int computeCardinality() {
        if (staticType == null) {
            if (binding == null) {
                return StaticProperty.ALLOWS_ZERO_OR_MORE;
            } else if (binding instanceof LetExpression) {
                return binding.getRequiredType().getCardinality();
            } else if (binding instanceof Assignation) {
                return StaticProperty.EXACTLY_ONE;
            } else if (binding.getRequiredType() == null) {
                return StaticProperty.ALLOWS_ZERO_OR_MORE;
            } else {
                return binding.getRequiredType().getCardinality();
            }
        } else {
            return staticType.getCardinality();
        }
    }

    /**
     * Determine the special properties of this expression
     *
     * @return {@link StaticProperty#NO_NODES_NEWLY_CREATED} (unless the variable is assignable using saxon:assign)
     */

    @Override
    protected int computeSpecialProperties() {
        int p = super.computeSpecialProperties();
        if (binding == null || !binding.isAssignable()) {
            // if the variable reference is assignable, we mustn't move it, or any expression that contains it,
            // out of a loop. The way to achieve this is to treat it as a "creative" expression, because the
            // optimizer recognizes such expressions and handles them with care...
            p |= StaticProperty.NO_NODES_NEWLY_CREATED;
        }
        if (binding instanceof Assignation) {
            Expression exp = ((Assignation) binding).getSequence();
            if (exp != null) {
                p |= exp.getSpecialProperties() & StaticProperty.NOT_UNTYPED_ATOMIC;
            }
        }
        if (staticType != null &&
                !Cardinality.allowsMany(staticType.getCardinality()) &&
                staticType.getPrimaryType() instanceof NodeTest) {
            p |= StaticProperty.SINGLE_DOCUMENT_NODESET;
        }
        return p &~ StaticProperty.ALL_NODES_NEWLY_CREATED;
    }

    /**
     * Test if this expression is the same as another expression.
     * (Note, we only compare expressions that
     * have the same static and dynamic context).
     */

    public boolean equals(Object other) {
        return other instanceof VariableReference &&
                binding == ((VariableReference) other).binding &&
                binding != null;
    }

    /**
     * get HashCode for comparing two expressions
     */

    @Override
    protected int computeHashCode() {
        return binding == null ? 73619830 : binding.hashCode();
    }


    @Override
    public int getIntrinsicDependencies() {
        int d = 0;
        if (binding == null) {
            // assume the worst
            d |= StaticProperty.DEPENDS_ON_LOCAL_VARIABLES |
                    StaticProperty.DEPENDS_ON_ASSIGNABLE_GLOBALS |
                    StaticProperty.DEPENDS_ON_RUNTIME_ENVIRONMENT;
        } else if (binding.isGlobal()) {
            if (binding.isAssignable()) {
                d |= StaticProperty.DEPENDS_ON_ASSIGNABLE_GLOBALS;
            }
            if (binding instanceof GlobalParam) {
                d |= StaticProperty.DEPENDS_ON_RUNTIME_ENVIRONMENT;
            }
        } else {
            d |= StaticProperty.DEPENDS_ON_LOCAL_VARIABLES;
        }
        return d;
    }

    /**
     * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
     * This method indicates which of these methods is provided. This implementation provides both all three methods
     * natively.
     */

    @Override
    public int getImplementationMethod() {
        return (Cardinality.allowsMany(getCardinality()) ? 0 : EVALUATE_METHOD)
                | ITERATE_METHOD | PROCESS_METHOD;
    }

    /**
     * Get the innermost scoping expression of this expression, for expressions that directly
     * depend on something in the dynamic context. For example, in the case of a local variable
     * reference this is the expression that causes the relevant variable to be bound; for a
     * context item expression it is the innermost focus-setting container. For expressions
     * that have no intrinsic dependency on the dynamic context, the value returned is null;
     * the scoping container for such an expression is the innermost scoping container of its
     * operands.
     *
     * @return the innermost scoping container of this expression
     */
    @Override
    public Expression getScopingExpression() {
        if (binding instanceof Expression) {
            if (binding instanceof LocalParam && ((LocalParam)binding).getParentExpression() instanceof LocalParamBlock) {
                LocalParamBlock block = (LocalParamBlock)((LocalParam) binding).getParentExpression();
                return block.getParentExpression();
            } else {
                return (Expression) binding;
            }
        }
        Expression parent = getParentExpression();
        while (parent != null) {
            if (parent.hasVariableBinding(binding)) {
                return parent;
            }
            parent = parent.getParentExpression();
        }
        return null;
    }

    /**
     * Add a representation of this expression to a PathMap. The PathMap captures a map of the nodes visited
     * by an expression in a source tree.
     * 

The default implementation of this method assumes that an expression does no navigation other than * the navigation done by evaluating its subexpressions, and that the subexpressions are evaluated in the * same context as the containing expression. The method must be overridden for any expression * where these assumptions do not hold. For example, implementations exist for AxisExpression, ParentExpression, * and RootExpression (because they perform navigation), and for the doc(), document(), and collection() * functions because they create a new navigation root. Implementations also exist for PathExpression and * FilterExpression because they have subexpressions that are evaluated in a different context from the * calling expression.

* * @param pathMap the PathMap to which the expression should be added * @param pathMapNodeSet the PathMapNodeSet to which the paths embodied in this expression should be added * @return the pathMapNodeSet representing the points in the source document that are both reachable by this * expression, and that represent possible results of this expression. For an expression that does * navigation, it represents the end of the arc in the path map that describes the navigation route. For other * expressions, it is the same as the input pathMapNode. */ @Override public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) { return pathMap.getPathForVariable(getBinding()); } /** * Get the value of this variable in a given context. * * @param c the XPathContext which contains the relevant variable bindings * @return the value of the variable, if it is defined * @throws XPathException if the variable is undefined */ /*@NotNull*/ @Override public SequenceIterator iterate(XPathContext c) throws XPathException { try { Sequence actual = evaluateVariable(c); assert actual != null; return actual.iterate(); } catch (XPathException err) { err.maybeSetLocation(getLocation()); throw err; } catch (NullPointerException err) { err.printStackTrace(); String msg = "Internal error: no value for variable $" + getDisplayName() + " at line " + getLocation().getLineNumber() + (getLocation().getSystemId() == null ? "" : " of " + getLocation().getSystemId()); new StandardDiagnostics().logStackTrace(c, c.getConfiguration().getLogger(), 2); throw new AssertionError(msg); } catch (AssertionError err) { err.printStackTrace(); String msg = err.getMessage() + ". Variable reference $" + getDisplayName() + " at line " + getLocation().getLineNumber() + (getLocation().getSystemId() == null ? "" : " of " + getLocation().getSystemId()); new StandardDiagnostics().logStackTrace(c, c.getConfiguration().getLogger(), 2); throw new AssertionError(msg); } } @Override public Item evaluateItem(XPathContext c) throws XPathException { try { Sequence actual = evaluateVariable(c); assert actual != null; return actual.head(); } catch (XPathException err) { err.maybeSetLocation(getLocation()); throw err; } } @Override public void process(Outputter output, XPathContext c) throws XPathException { try { SequenceIterator iter = evaluateVariable(c).iterate(); Location loc = getLocation(); SequenceTool.supply(iter, (ItemConsumer) item -> output.append(item, loc, ReceiverOption.ALL_NAMESPACES)); } catch (UncheckedXPathException uxe) { XPathException xpe = uxe.getXPathException(); xpe.maybeSetLocation(getLocation()); throw xpe; } catch (XPathException err) { err.maybeSetLocation(getLocation()); throw err; } } /** * Evaluate this variable * * @param c the XPath dynamic context * @return the value of the variable * @throws XPathException if any error occurs */ /*@NotNull*/ public Sequence evaluateVariable(XPathContext c) throws XPathException { try { return binding.evaluateVariable(c); } catch (NullPointerException err) { if (binding == null) { throw new IllegalStateException("Variable $" + variableName.getDisplayName() + " has not been fixed up"); } else { throw err; } } } /** * Get the object bound to the variable * * @return the Binding which declares this variable and associates it with a value */ public Binding getBinding() { return binding; } /** * Get the display name of the variable. This is taken from the variable binding if possible * * @return the display name (a lexical QName */ public String getDisplayName() { if (binding != null) { return binding.getVariableQName().getDisplayName(); } else { return variableName.getDisplayName(); } } /** * Get the EQName of the variable. This is taken from the variable binding if possible. * The returned name is in the format Q{uri}local if in a namespace, or the local name * alone if not. * * @return the EQName, or the local name if not in a namespace */ public String getEQName() { if (binding != null) { StructuredQName q = binding.getVariableQName(); if (q.hasURI("")) { return q.getLocalPart(); } else { return q.getEQName(); } } else { return variableName.getEQName(); } } /** * The toString() method for an expression attempts to give a representation of the expression * in an XPath-like form, but there is no guarantee that the syntax will actually be true XPath. * In the case of XSLT instructions, the toString() method gives an abstracted view of the syntax */ public String toString() { String d = getEQName(); return "$" + (d == null ? "$" : d); } /** * Produce a short string identifying the expression for use in error messages * * @return a short string, sufficient to identify the expression */ @Override public String toShortString() { return "$" + getDisplayName(); } /** * 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 "varRef"; } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. */ @Override public void export(ExpressionPresenter destination) throws XPathException { destination.startElement("varRef", this); destination.emitAttribute("name", variableName); if (binding instanceof LocalBinding) { destination.emitAttribute("slot", "" + ((LocalBinding) binding).getLocalSlotNumber()); } destination.endElement(); } /** * Get the (partial) name of a class that supports streaming of this kind of expression * * @return the partial name of a class that can be instantiated to provide streaming support in Saxon-EE, * or null if there is no such class */ @Override public String getStreamerName() { return "VariableReference"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy