package client.net.sf.saxon.ce.expr; import client.net.sf.saxon.ce.tree.util.FastStringBuffer; import client.net.sf.saxon.ce.om.StructuredQName; import client.net.sf.saxon.ce.trans.NoDynamicContextException; import client.net.sf.saxon.ce.trans.XPathException; import client.net.sf.saxon.ce.type.ItemType; import client.net.sf.saxon.ce.value.SequenceExtent; import client.net.sf.saxon.ce.value.Value; import java.util.Arrays; import java.util.Iterator; /** * Abstract superclass for calls to system-defined and user-defined functions */ public abstract class FunctionCall extends Expression { /** * The name of the function */ private StructuredQName name; /** * The array of expressions representing the actual parameters * to the function call */ protected Expression[] argument; /** * Set the name of the function being called * @param name the name of the function */ public final void setFunctionName(StructuredQName name) { this.name = name; } /** * Get the qualified of the function being called * @return the qualified name */ public StructuredQName getFunctionName() { return name; } /** * Determine the number of actual arguments supplied in the function call * @return the arity (the number of arguments) */ public final int getNumberOfArguments() { return argument.length; } /** * Method called by the expression parser when all arguments have been supplied * @param args the expressions contained in the argument list of the function call */ public void setArguments(Expression[] args) { argument = args; for (int a=0; a<args.length; a++) { adoptChildExpression(args[a]); } } /** * Get the expressions supplied as actual arguments to the function * @return the array of expressions supplied in the argument list of the function call */ public Expression[] getArguments() { return argument; } /** * Simplify the function call. Default method is to simplify each of the supplied arguments and * evaluate the function if all are now known. * @param visitor an expression visitor */ public Expression simplify(ExpressionVisitor visitor) throws XPathException { return simplifyArguments(visitor); } /** * Simplify the arguments of the function. * Called from the simplify() method of each function. * @return the result of simplifying the arguments of the expression * @param visitor an expression visitor */ protected final Expression simplifyArguments(ExpressionVisitor visitor) throws XPathException { for (int i=0; i<argument.length; i++) { Expression exp = visitor.simplify(argument[i]); if (exp != argument[i]) { adoptChildExpression(exp); argument[i] = exp; } } return this; } /** * Type-check the expression. This also calls preEvaluate() to evaluate the function * if all the arguments are constant; functions that do not require this behavior * can override the preEvaluate method. */ public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { boolean fixed = true; for (int i=0; i<argument.length; i++) { Expression exp = visitor.typeCheck(argument[i], contextItemType); if (exp != argument[i]) { adoptChildExpression(exp); argument[i] = exp; } if (!(argument[i] instanceof Literal)) { fixed = false; } } checkArguments(visitor); if (fixed) { try { return preEvaluate(visitor); } catch (NoDynamicContextException err) { // Early evaluation failed, typically because the implicit timezone is not yet known. // Try again later at run-time. return this; } } else { return this; } } /** * Perform optimisation of an expression and its subexpressions. * <p/> * <p>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.</p> * * @param visitor an expression visitor * @param contextItemType 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 client.net.sf.saxon.ce.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) */ public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException { boolean fixed = true; for (int i=0; i<argument.length; i++) { Expression exp = visitor.optimize(argument[i], contextItemType); if (exp != argument[i]) { adoptChildExpression(exp); argument[i] = exp; } if (fixed && !(argument[i] instanceof Literal)) { fixed = false; } } checkArguments(visitor); if (fixed) { return preEvaluate(visitor); } else { return this; } } /** * Pre-evaluate a function at compile time. Functions that do not allow * pre-evaluation, or that need access to context information, can override this method. * @param visitor an expression visitor * @return the result of the early evaluation, or the original expression, or potentially * a simplified expression */ public Expression preEvaluate(ExpressionVisitor visitor) throws XPathException { if (getIntrinsicDependencies() != 0) { return this; } try { Literal lit = Literal.makeLiteral( Value.asValue(SequenceExtent.makeSequenceExtent( iterate(visitor.getStaticContext().makeEarlyEvaluationContext())))); ExpressionTool.copyLocationInfo(this, lit); return lit; } catch (NoDynamicContextException e) { // early evaluation failed, usually because implicit timezone required return this; } } /** * Promote this expression if possible */ public Expression promote(PromotionOffer offer, Expression parent) throws XPathException { Expression exp = offer.accept(parent, this); if (exp != null) { return exp; } else { if (offer.action != PromotionOffer.UNORDERED) { for (int i=0; i<argument.length; i++) { argument[i] = doPromotion(argument[i], offer); } } return this; } } /** * Method supplied by each class of function to check arguments during parsing, when all * the argument expressions have been read * @param visitor the expression visitor */ protected abstract void checkArguments(ExpressionVisitor visitor) throws XPathException; /** * Check number of arguments. <BR> * A convenience routine for use in subclasses. * @param min the minimum number of arguments allowed * @param max the maximum number of arguments allowed * @param visitor an expression visitor * @return the actual number of arguments * @throws client.net.sf.saxon.ce.trans.XPathException if the number of arguments is out of range */ protected int checkArgumentCount(int min, int max, ExpressionVisitor visitor) throws XPathException { int numArgs = argument.length; if (min==max && numArgs != min) { throw new XPathException("Function " + getDisplayName() + " must have " + min + pluralArguments(min), getSourceLocator()); } if (numArgs < min) { throw new XPathException("Function " + getDisplayName() + " must have at least " + min + pluralArguments(min), getSourceLocator()); } if (numArgs > max) { throw new XPathException("Function " + getDisplayName() + " must have no more than " + max + pluralArguments(max), getSourceLocator()); } return numArgs; } /** * Utility routine used in constructing error messages: get the word "argument" or "arguments" * @param num the number of arguments * @return the singular or plural word */ private static String pluralArguments(int num) { if (num==1) return " argument"; return " arguments"; } /** * Get the immediate subexpressions of this expression */ public Iterator<Expression> iterateSubExpressions() { // try { return Arrays.asList(argument).iterator(); // } catch (NullPointerException err) { // // typically caused by doing CopyLocationInfo after creating the function // // but before creating its arguments // throw err; // } } /** * Replace one subexpression by a replacement subexpression * @param original the original subexpression * @param replacement the replacement subexpression * @return true if the original subexpression is found */ public boolean replaceSubExpression(Expression original, Expression replacement) { boolean found = false; for (int i=0; i<argument.length; i++) { if (argument[i] == original) { argument[i] = replacement; found = true; } } return found; } /** * Get the name of the function for display in messages * @return the name of the function as a lexical QName */ public final String getDisplayName() { StructuredQName fName = getFunctionName(); return (fName == null ? "(anonymous)" : fName.getDisplayName()); } /** * 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() { FastStringBuffer buff = new FastStringBuffer(FastStringBuffer.SMALL); buff.append(getDisplayName()); Iterator iter = iterateSubExpressions(); boolean first = true; while (iter.hasNext()) { buff.append(first ? "(" : ", "); buff.append(iter.next().toString()); first = false; } buff.append(first ? "()" : ")"); return buff.toString(); } /** * Determine whether two expressions are equivalent */ public boolean equals(Object o) { if (!(o instanceof FunctionCall)) { return false; } FunctionCall f = (FunctionCall)o; if (!getFunctionName().equals(f.getFunctionName())) { return false; } if (getNumberOfArguments() != f.getNumberOfArguments()) { return false; } for (int i=0; i<getNumberOfArguments(); i++) { if (!argument[i].equals(f.argument[i])) { return false; } } return true; } /** * Get hashCode in support of equals() method */ public int hashCode() { int h = getFunctionName().hashCode(); for (int i=0; i<getNumberOfArguments(); i++) { h ^= argument[i].hashCode(); } return h; } } // 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.