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

net.sf.saxon.expr.instruct.GlobalVariable Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 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.instruct;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.expr.parser.RoleLocator;
import net.sf.saxon.expr.parser.TypeChecker;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.GroundedValue;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StandardNames;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.query.XQueryFunction;
import net.sf.saxon.query.XQueryFunctionLibrary;
import net.sf.saxon.trace.InstructionInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.iter.UnfailingIterator;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.SingletonClosure;

import java.util.*;

/**
* A compiled global variable in a stylesheet or query. 
*/ public class GlobalVariable extends GeneralVariable implements Container, net.sf.saxon.query.Declaration, InstructionInfo { protected List references = new ArrayList(10); // Note that variableReferences on this list might be dormant; // that is, they might be disconnected from the live expression tree. private Executable executable; /*@Nullable*/ private SlotManager stackFrameMap = null; private boolean indexed; private int lineNumber; private String systemId; private boolean isPrivate = false; private boolean isAssignable = false; private GlobalVariable originalVariable; /** * Create a global variable */ public GlobalVariable(){} /** * Get the executable containing this global variable * @return the containing executable */ public Executable getExecutable() { return executable; } /** * Set the containing executable * @param executable the executable that contains this global variable */ public void setExecutable(Executable executable) { this.executable = executable; } /** * Say that this (XQuery) variable is a copy of some originally declared variable. It's a * separate variable when imported into another module, but it retains the link * to the original. * @param var the variable in the imported module from which this variable is derived */ public void setOriginalVariable(GlobalVariable var) { originalVariable = var; } /** * Get the original declaration of this variable * @return the variable in the imported module from which this variable is derived */ public GlobalVariable getOriginalVariable() { return originalVariable; } /** * Get the original declaration of this variable, or its original declaration, transitively * @return the real variable declaration in some transitively imported module from which this variable is * ultimately derived */ public GlobalVariable getUltimateOriginalVariable() { if (originalVariable == null) { return this; } else { return originalVariable.getUltimateOriginalVariable(); } } /** * Set the line number where the variable declaration appears in the source * @param lineNumber the line number */ public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } /** * Get the line number where the declaration appears */ public int getLineNumber() { return lineNumber; } public int getLineNumber(long locationId) { return lineNumber; } /** * Set the system ID of the module where the variable declaration appears * @param systemId the System ID (base URI) */ public void setSystemId(String systemId) { this.systemId = systemId; } /** * Get the system ID of the module containing the variable declaration * @return the System ID (base URI) */ public String getSystemId() { return systemId; } /** * Get the column number within the document, entity, or module containing a particular location * * @param locationId identifier of the location in question (as passed down the Receiver pipeline) * @return the column number within the document, entity, or module, or -1 if this is not available */ public int getColumnNumber(long locationId) { return executable.getLocationMap().getLineNumber(locationId); } /** * Ask whether this global variable is private * @return true if this variable is private */ public boolean isPrivate() { return isPrivate; } /** * Say whether this global variable is a "parameter" (an external variable, in XQuery terminology) * @param b true if this variable is external */ public void setPrivate(boolean b) { isPrivate = b; } /** * Indicate whether this variable is assignable using saxon:assign * @param assignable true if this variable is assignable */ public void setAssignable(boolean assignable) { isAssignable = assignable; } /** * Test whether it is permitted to assign to the variable using the saxon:assign * extension element. This will only be true if the extra attribute saxon:assignable="yes" * is present. */ public final boolean isAssignable() { return isAssignable; } /** * Get the type of construct. This will either be the fingerprint of a standard XSLT instruction name * (values in {@link net.sf.saxon.om.StandardNames}: all less than 1024) * or it will be a constant in class {@link net.sf.saxon.trace.Location}. * * @return an integer identifying the kind of construct */ public int getConstructType() { return StandardNames.XSL_VARIABLE; } /** * Get a name identifying the object of the expression, for example a function name, template name, * variable name, key name, element name, etc. This is used only where the name is known statically. * * @return the QName of the object declared or manipulated by this instruction or expression */ public StructuredQName getObjectName() { return getVariableQName(); } /** * Get the value of a particular property of the instruction. Properties * of XSLT instructions are generally known by the name of the stylesheet attribute * that defines them. * * @param name The name of the required property * @return The value of the requested property, or null if the property is not available */ public Object getProperty(String name) { return null; } /** * Get an iterator over all the properties available. The values returned by the iterator * will be of type String, and each string can be supplied as input to the getProperty() * method to retrieve the value of the property. The iterator may return properties whose * value is null. * * @return an iterator over the properties. */ public Iterator getProperties() { List list = Collections.emptyList(); return list.iterator(); } /** * Get the container in which this expression is located. This will usually be a top-level construct * such as a function or global variable, and XSLT template, or an XQueryExpression. In the case of * free-standing XPath expressions it will be the StaticContext object * * @return the expression's container */ @Override public Container getContainer() { return this; } /** * Get the host language (XSLT, XQuery, XPath) used to implement the code in this container * @return typically {@link net.sf.saxon.Configuration#XSLT} or {@link net.sf.saxon.Configuration#XQUERY} */ public int getHostLanguage() { return executable.getHostLanguage(); } /** * Mark this as an indexed variable, to allow fast searching */ public void setIndexedVariable() { indexed = true; } /** * Ask whether this is an indexed variable * @return true if this variable is indexed */ public boolean isIndexedVariable() { return indexed; } /** * Get the granularity of the container. * @return 0 for a temporary container created during parsing; 1 for a container * that operates at the level of an XPath expression; 2 for a container at the level * of a global function or template */ public int getContainerGranularity() { return 2; } /** * The expression that initializes a global variable may itself use local variables. * In this case a stack frame needs to be allocated while evaluating the global variable * @param map The stack frame map for local variables used while evaluating this global * variable. */ public void setContainsLocals(SlotManager map) { this.stackFrameMap = map; } /** * Is this a global variable? * @return true (yes, it is a global variable) */ public boolean isGlobal() { return true; } /** * Register a variable reference that refers to this global variable * @param ref the variable reference */ public void registerReference(BindingReference ref) { references.add(ref); } /** * Iterate over the references to this variable * @return an iterator over the references: returns objects of class {@link VariableReference} */ public Iterator iterateReferences() { return references.iterator(); } /** * Create a compiled representation of this global variable * @param exec the executable * @param slot the slot number allocated to this variable * @throws XPathException if compile-time errors are found. */ public void compile(/*@NotNull*/ Executable exec, int slot) throws XPathException { if (references.isEmpty()) { return; } TypeHierarchy th = exec.getConfiguration().getTypeHierarchy(); setSlotNumber(slot); if (this instanceof GlobalParam) { setRequiredParam(select == null); } final SequenceType type = getRequiredType(); for (BindingReference ref : references) { ref.fixup(this); GroundedValue constantValue = null; int properties = 0; Expression select = getSelectExpression(); if (select instanceof Literal && !(this instanceof GlobalParam)) { // we can't rely on the constant value because it hasn't yet been type-checked, // which could change it (eg by numeric promotion). Rather than attempt all the type-checking // now, we do a quick check. See test bug64 int relation = th.relationship(select.getItemType(th), type.getPrimaryType()); if (relation == TypeHierarchy.SAME_TYPE || relation == TypeHierarchy.SUBSUMED_BY) { constantValue = ((Literal) select).getValue(); } } if (select != null) { properties = select.getSpecialProperties(); } properties |= StaticProperty.NON_CREATIVE; // a variable reference is non-creative even if its initializer is creative ref.setStaticType(type, constantValue, properties); } exec.registerGlobalVariable(this); setReferenceCount(10); // TODO: temporary! } /** * Type check the compiled representation of this global variable * @param visitor an expression visitor * @throws XPathException if compile-time errors are found. */ public void typeCheck(/*@NotNull*/ ExpressionVisitor visitor) throws XPathException { Expression value = getSelectExpression(); if (value != null) { value.checkForUpdatingSubexpressions(); if (value.isUpdatingExpression()) { throw new XPathException( "Initializing expression for global variable must not be an updating expression", "XUST0001"); } value.setContainer(this); RoleLocator role = new RoleLocator( RoleLocator.VARIABLE, getVariableQName(), 0); ExpressionVisitor.ContextItemType cit = new ExpressionVisitor.ContextItemType(AnyItemType.getInstance(), true); Expression value2 = TypeChecker.strictTypeCheck( visitor.typeCheck(visitor.simplify(value), cit), getRequiredType(), role, visitor.getStaticContext()); value2 = value2.optimize(visitor, cit); setSelectExpression(value2); value2.setContainer(this); // the value expression may declare local variables SlotManager map = visitor.getConfiguration().makeSlotManager(); int slots = ExpressionTool.allocateSlots(value2, 0, map); if (slots > 0) { setContainsLocals(map); } if (getRequiredType() == SequenceType.ANY_SEQUENCE && !(this instanceof GlobalParam)) { // no type was declared; try to deduce a type from the value try { final TypeHierarchy th = visitor.getConfiguration().getTypeHierarchy(); final ItemType itemType = value.getItemType(th); final int cardinality = value.getCardinality(); setRequiredType(SequenceType.makeSequenceType(itemType, cardinality)); GroundedValue constantValue = null; if (value2 instanceof Literal) { constantValue = ((Literal)value2).getValue(); } for (BindingReference reference : references) { BindingReference ref = (reference); if (ref instanceof VariableReference) { ((VariableReference) ref).refineVariableType( itemType, cardinality, constantValue, value.getSpecialProperties(), visitor); } } } catch (Exception err) { // exceptions can happen because references to variables and functions are still unbound } } } } /** * Check for cycles in this variable definition * @param referees the calls leading up to this one; it's an error if this variable is on the * stack, because that means it calls itself directly or indirectly. The stack may contain * variable definitions (GlobalVariable objects) and user-defined functions (UserFunction objects). * It will never contain the same object more than once. * @param globalFunctionLibrary the library containing all global functions * @throws net.sf.saxon.trans.XPathException if cycles are found */ public void lookForCycles(Stack referees, XQueryFunctionLibrary globalFunctionLibrary) throws XPathException { if (referees.contains(this)) { int s = referees.indexOf(this); referees.push(this); String message = "Circular definition of global variable: "; if (getVariableQName().getLocalPart().equals("context-item")) { message += "Context item"; } else { message += "$" + getVariableQName().getDisplayName(); } for (int i = s; i < referees.size() - 1; i++) { if (i != s) { message += ", which"; } if (referees.get(i + 1) instanceof GlobalVariable) { GlobalVariable next = (GlobalVariable)referees.get(i + 1); if (next.getVariableQName().getLocalPart().equals("context-item")) { message += " uses context item"; } else { message += " uses $" + next.getVariableQName().getDisplayName(); } } else if (referees.get(i + 1) instanceof XQueryFunction) { XQueryFunction next = (XQueryFunction)referees.get(i + 1); message += " calls " + next.getFunctionName().getDisplayName() + "#" + next.getNumberOfArguments() + "()"; } } message += '.'; XPathException err = new XPathException(message); String errorCode; if (getHostLanguage() == Configuration.XSLT) { errorCode = "XTDE0640"; } else if (executable.isAllowXPath30()) { errorCode = "XQDY0054"; } else { errorCode = "XQST0054"; } err.setErrorCode(errorCode); err.setIsStaticError(true); err.setLocator(this); throw err; } if (select != null) { referees.push(this); List list = new ArrayList(10); ExpressionTool.gatherReferencedVariables(select, list); for (Binding b : list) { if (b instanceof GlobalVariable) { ((GlobalVariable) b).lookForCycles(referees, globalFunctionLibrary); } } List flist = new ArrayList(); ExpressionTool.gatherCalledFunctionNames(select, flist); for (String s : flist) { XQueryFunction f = globalFunctionLibrary.getDeclarationByKey(s); if (!referees.contains(f)) { // recursive function calls are allowed lookForFunctionCycles(f, referees, globalFunctionLibrary); } } referees.pop(); } } /** * Look for cyclic variable references that go via one or more function calls * @param f a used-defined function * @param referees a list of variables and functions that refer directly or indirectly to this variable * @param globalFunctionLibrary the library containing all global functions */ private static void lookForFunctionCycles( XQueryFunction f, Stack referees, XQueryFunctionLibrary globalFunctionLibrary) throws XPathException { Expression body = f.getBody(); referees.push(f); List list = new ArrayList(10); ExpressionTool.gatherReferencedVariables(body, list); for (Binding b : list) { if (b instanceof GlobalVariable) { ((GlobalVariable) b).lookForCycles(referees, globalFunctionLibrary); } } List flist = new ArrayList(); ExpressionTool.gatherCalledFunctionNames(body, flist); for (String s : flist) { XQueryFunction qf = globalFunctionLibrary.getDeclarationByKey(s); if (!referees.contains(qf)) { // recursive function calls are allowed lookForFunctionCycles(qf, referees, globalFunctionLibrary); } } referees.pop(); } /** * Evaluate the variable. That is, * get the value of the select expression if present or the content * of the element otherwise, either as a tree or as a sequence */ public Sequence getSelectValue(XPathContext context) throws XPathException { if (select==null) { throw new AssertionError("*** No select expression for global variable $" + getVariableQName().getDisplayName() + "!!"); } else { try { XPathContextMajor c2 = context.newCleanContext(); c2.setOrigin(this); final Controller controller = c2.getController(); assert controller != null; UnfailingIterator initialNode = SingletonIterator.makeIterator(controller.getContextForGlobalVariables()); initialNode.next(); c2.setCurrentIterator(initialNode); if (stackFrameMap != null) { c2.openStackFrame(stackFrameMap); } return ExpressionTool.evaluate(select, evaluationMode, c2, referenceCount); } catch (XPathException e) { if (!getVariableQName().getURI().equals(NamespaceConstant.SAXON_GENERATED_GLOBAL)) { e.setIsGlobalError(true); } throw e; } } } /** * Evaluate the variable */ public Sequence evaluateVariable(XPathContext context) throws XPathException { final Controller controller = context.getController(); assert controller != null; final Bindery b = controller.getBindery(); final Sequence v = b.getGlobalVariable(getSlotNumber()); if (v != null) { return v; } else { return actuallyEvaluate(context); } } /** * Evaluate the global variable, and save its value for use in subsequent references. * @param context the XPath dynamic context * @return the value of the variable * @throws XPathException if evaluation fails */ protected Sequence actuallyEvaluate(XPathContext context) throws XPathException { final Controller controller = context.getController(); assert controller != null; final Bindery b = controller.getBindery(); try { // This is the first reference to a global variable; try to evaluate it now. // But first check for circular dependencies. setDependencies(b, this, context); // Set a flag to indicate that the variable is being evaluated. This is designed to prevent // (where possible) the same global variable being evaluated several times in different threads boolean go = b.setExecuting(this); if (!go) { // some other thread has evaluated the variable while we were waiting return b.getGlobalVariable(getSlotNumber()); } Sequence value = getSelectValue(context); if (indexed) { value = controller.getConfiguration().obtainOptimizer().makeIndexedValue(value.iterate()); } return b.saveGlobalVariableValue(this, value); } catch (XPathException err) { b.setNotExecuting(this); if (err instanceof XPathException.Circularity) { String errorCode; if (getHostLanguage() == Configuration.XSLT) { errorCode = "XTDE0640"; } else if (executable.isAllowXPath30()) { errorCode = "XQDY0054"; } else { errorCode = "XQST0054"; } err.setErrorCode(errorCode); err.setXPathContext(context); // Detect it more quickly the next time (in a pattern, the error is recoverable) SingletonClosure closure = new SingletonClosure(new ErrorExpression(err), context); b.setGlobalVariable(this, closure); err.setLocator(this); throw err; } else { throw err; } } } /** * Get the variable that is immediately dependent on this one, and register the dependency, so * that circularities can be detected across threads. This relies on the fact that when the initialiser * for variable X contains a reference to variable Y, then when Y is evaluated, a stack frame will be found * on the context stack representing the evaluation of X. We don't set a dependency from X to Y if the value * of Y was already available in the Bindery; it's not needed, because in this case we know that evaluation * of Y is unproblematic, and can't lead to any circularities. * @param bindery the Bindery * @param var the global variable or parameter being evaluated * @param context the dynamic evaluation context */ protected static void setDependencies(Bindery bindery, GlobalVariable var, XPathContext context) throws XPathException { if (!(context instanceof XPathContextMajor)) { context = getMajorCaller(context); } while (context != null) { do { InstructionInfo instructionInfo = ((XPathContextMajor) context).getOrigin(); if (instructionInfo instanceof GlobalVariable) { bindery.registerDependency((GlobalVariable)instructionInfo, var); return; } context = getMajorCaller(context); } while (context != null); } } private static XPathContextMajor getMajorCaller(XPathContext context) { XPathContext caller = context.getCaller(); while (!(caller == null || caller instanceof XPathContextMajor)) { caller = caller.getCaller(); } return (XPathContextMajor)caller; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy