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

net.sf.saxon.functions.registry.BuiltInFunctionSet 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.functions.registry;

import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.Block;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.RetainedStaticContext;
import net.sf.saxon.expr.parser.RoleDiagnostic;
import net.sf.saxon.functions.Concat;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.functions.OptionsParameter;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.PlainType;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.z.IntHashMap;

import java.util.*;
import java.util.function.Supplier;

/**
 * This class is used to contain information about a set of built-in functions.
 */
public abstract class BuiltInFunctionSet implements FunctionLibrary {

    public static Sequence EMPTY = EmptySequence.getInstance();

    /**
     * Local names used for cardinality values
     */

    public static final int ONE = StaticProperty.ALLOWS_ONE;
    public static final int OPT = StaticProperty.ALLOWS_ZERO_OR_ONE;
    public static final int STAR = StaticProperty.ALLOWS_ZERO_OR_MORE;
    public static final int PLUS = StaticProperty.ALLOWS_ONE_OR_MORE;

    /**
     * Function properties
     */

    public static final int AS_ARG0 = 1;          // Result has same type as first argument
    public static final int AS_PRIM_ARG0 = 2;     // Result has same primitive type as first argument
    public static final int CITEM = 4;            // Depends on context item
    public static final int BASE = 8;             // Depends on base URI
    public static final int NS = 16;              // Depends on namespace context
    public static final int DCOLL = 32;           // Depends on default collation
    public static final int DLANG = 64;           // Depends on default language
    public static final int FILTER = 256;         // Result is a subset of the value of the first arg
    public static final int LATE = 512;           // Disallow compile-time evaluation
    public static final int UO = 1024;            // Ordering in first argument is irrelevant
    public static final int POSN = 1024*2;        // Depends on position
    public static final int LAST = 1024*4;        // Depends on last
    public static final int SIDE = 1024*8;        // Has side-effects
    public static final int CDOC = 1024*16;       // Depends on context document
    public static final int CARD0 = 1024*32;      // Result is empty only if first arg is empty
    public static final int NEW = 1024*64;        // All nodes in the result are newly created
    public static final int CTRL = 1024*128;      // Controls the optimization of its arguments
    public static final int SEQV = 1024 * 256;    // Sequence-variadic, like concat() in 4.0

    public static final int DEPENDS_ON_STATIC_CONTEXT = BASE | NS | DCOLL;
    public static final int FOCUS = CITEM | POSN | LAST | CDOC;


    /**
     * Classification of function arguments for serialization purposes; note that values must not conflict
     * with bit settings used for cardinalities
     */

    protected static final int INS = 1 << 24;   // = usage INSPECTION
    protected static final int ABS = 1 << 25;   // = usage ABSORPTION (implicit when type is atomic)
    protected static final int TRA = 1 << 26;   // = usage TRANSMISSION (node is included in function result)
    protected static final int NAV = 1 << 27;   // = usage NAVIGATION (function navigates from this node)


    private final HashMap functionTable = new HashMap<>(200);
    //private List importedFunctions = new ArrayList();

    // Sequence-variadic functions (XPath 4.0+) are registered in a table indexed by the local
    // name of the function; the corresponding value is the minimum arity.
    private final HashMap sequenceVariadicFunctions = new HashMap<>(10);

    /**
     * Import another function set (which must be in the same namespace)
     * @param importee the function set to be imported. (No cycles allowed!)
     */

    public final void importFunctionSet(net.sf.saxon.functions.registry.BuiltInFunctionSet importee) {
        if (!importee.getNamespace().equals(getNamespace())) {
            throw new IllegalArgumentException(importee.getNamespace().toString());
        }
        functionTable.putAll(importee.functionTable);
        sequenceVariadicFunctions.putAll(importee.sequenceVariadicFunctions);
        //importedFunctions.add(importee);
    }

    /**
     * Locate the entry for a function with a given name and arity, if it exists
     * @param name the local part of the function name
     * @param arity the arity of the function. -1 considers all possibly arities and returns an arbitrary function
     *              if one exists with the right name.
     * @return the entry for the required function, or null if not found
     */

    public net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry getFunctionDetails(String name, int arity) {
        if (arity == -1) {
            for (int i=0; i<20; i++) {
                net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry found = getFunctionDetails(name, i);
                if (found != null) {
                    return found;
                }
            }
            return null;
        }
        String key = name + "#" + arity;
        net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry entry = functionTable.get(key);
        if (entry != null) {
            return entry;
        }
        // Try for a generalised (XP40) sequence-variadic function
        int minArity = sequenceVariadicFunctions.getOrDefault(name, -1);
        if (minArity != -1 && arity >= minArity) {
            key = name + "#" + (minArity + 1);
            return functionTable.get(key);
        }
//        // Try for a variable-arity function (concat only up to 3.1)
//        if (name.equals("concat") && arity >= 2 && getNamespace().equals(NamespaceConstant.FN)) {
//            key = "concat#-1";
//            entry = functionTable.get(key);
//            return entry;
//        }
        return null;
    }

    /**
     * Bind a function, given the URI and local parts of the function name,
     * and the list of expressions supplied as arguments. This method is called at compile
     * time.
     *
     * @param symbolicName the symbolic name of the function being called
     * @param staticArgs   May be null; if present, the length of the array must match the
     *                     value of arity. Contains the expressions supplied statically in arguments to the function call.
     *                     The intention is
     *                     that the static type of the arguments (obtainable via getItemType() and getCardinality()) may
     *                     be used as part of the binding algorithm. In some cases it may be possible for the function
     *                     to be pre-evaluated at compile time, for example if these expressions are all constant values.
     *                     

The conventions of the XPath language demand that the results of a function depend only on the * values of the expressions supplied as arguments, and not on the form of those expressions. For * example, the result of f(4) is expected to be the same as f(2+2). The actual expression is supplied * here to enable the binding mechanism to select the most efficient possible implementation (including * compile-time pre-evaluation where appropriate).

* @param keywords Keywords used in keyword parameters, with the 0-based integer position of the argument they are used * on. * @param env The static context of the function call * @param reasons If no matching function is found by the function library, it may add * a diagnostic explanation to this list explaining why none of the available * functions could be used. * @return An expression equivalent to a call on the specified function, if one is found; * null if no function was found matching the required name and arity. */ @Override public Expression bind(SymbolicName.F symbolicName, Expression[] staticArgs, Map keywords, StaticContext env, List reasons) throws XPathException { StructuredQName functionName = symbolicName.getComponentName(); int arity = symbolicName.getArity(); String localName = functionName.getLocalPart(); net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry entry = getFunctionDetails(localName, arity); if (functionName.hasURI(getNamespace()) && entry != null) { entry.ensurePopulated(); if ((entry.properties & SEQV) != 0) { // sequence-variadic functions in 4.0 (e.g..concat, codepoints-to-string) // combine the "variable" arguments into a single argument if (env.getXPathVersion() < 40) { // Need to special-case the fn:concat() function prior to XPath 4.0 if (localName.equals("concat")) { if (staticArgs.length < 2) { reasons.add("concat() prior to XPath 4.0 requires at least two arguments"); return null; } // Require each argument to be a singleton Expression[] a2 = new Expression[staticArgs.length]; for (int i=0; i role = () -> new RoleDiagnostic(RoleDiagnostic.FUNCTION, "concat", pos); a2[i] = CardinalityChecker.makeCardinalityChecker( staticArgs[i], StaticProperty.ALLOWS_ZERO_OR_ONE, role); a2[i].setRetainedStaticContext(env.makeRetainedStaticContext()); } } staticArgs = a2; } } int declaredArity = entry.maxArity; Expression[] newArgs = Arrays.copyOf(staticArgs, declaredArity); if (declaredArity > staticArgs.length) { newArgs[declaredArity - 1] = Literal.makeEmptySequence(); } else if (declaredArity < staticArgs.length) { Expression block = new Block(Arrays.copyOfRange(staticArgs, declaredArity - 1, staticArgs.length)); ExpressionTool.copyLocationInfo(staticArgs[0], block); newArgs[declaredArity - 1] = block; } staticArgs = newArgs; } else if ((keywords != null && !keywords.isEmpty()) || staticArgs.length < entry.maxArity) { staticArgs = UserFunction.makeExpandedArgumentArray(staticArgs, keywords, entry); } RetainedStaticContext rsc = new RetainedStaticContext(env); try { SystemFunction fn = makeFunction(localName, staticArgs.length); fn.setRetainedStaticContext(rsc); Expression f = fn.makeFunctionCall(staticArgs); f.setRetainedStaticContext(rsc); return f; } catch (XPathException e) { reasons.add(e.getMessage()); return null; } } else { return null; } } public SystemFunction makeFunction(String name, int arity) throws XPathException { net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry entry = getFunctionDetails(name, arity); if (entry == null) { String diagName = getNamespace().equals(NamespaceUri.FN) ? "System function " + name : "Function Q{" + getNamespace() + "}" + name; if (getFunctionDetails(name, -1) == null) { XPathException err = new XPathException(diagName + "() does not exist or is not available in this environment"); err.setErrorCode("XPST0017"); err.setIsStaticError(true); throw err; } else { XPathException err = new XPathException(diagName + "() cannot be called with " + pluralArguments(arity)); err.setErrorCode("XPST0017"); err.setIsStaticError(true); throw err; } } entry.ensurePopulated(); SystemFunction f = entry.implementationFactory.get(); f.setDetails(entry); f.setArity(arity); return f; } /** * Utility routine used in constructing error messages * * @param num the number of arguments * @return the string " argument" or "arguments" depending whether num is plural */ private static String pluralArguments(int num) { if (num == 0) { return "zero arguments"; } if (num == 1) { return "one argument"; } return num + " arguments"; } /** * Test whether a function with a given name and arity is available *

This supports the function-available() function in XSLT.

* * @param symbolicName the qualified name of the function being called, together with its arity. * For legacy reasons, the arity may be set to -1 to mean any arity will do * @param languageLevel the XPath language level (times 10, e.g. 31 for XPath 3.1) * @return true if a function of this name and arity is available for calling */ @Override public boolean isAvailable(SymbolicName.F symbolicName, int languageLevel) { StructuredQName qn = symbolicName.getComponentName(); if (!qn.hasURI(getNamespace())) { return false; } net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry entry = getFunctionDetails(qn.getLocalPart(), symbolicName.getArity()); if (entry == null) { return false; } //if ((entry.properties & SEQV) != 0) { // sequence-variadic functions in 4.0 (e.g..concat, codepoints-to-string) // combine the "variable" arguments into a single argument if (languageLevel < 40 && symbolicName.getComponentName().getLocalPart().equals("concat") && symbolicName.getArity() < 2) { return false; } //} return true; } /** * This method creates a copy of a FunctionLibrary: if the original FunctionLibrary allows * new functions to be added, then additions to this copy will not affect the original, or * vice versa. * * @return a copy of this function library. This must be an instance of the original class. */ @Override public FunctionLibrary copy() { return this; } /** * Test whether a function with a given name and arity is available; if so, return a function * item that can be dynamically called. *

This supports the function-lookup() function in XPath 3.0.

* * @param symbolicName the qualified name of the function being called * @param staticContext the static context to be used by the function, in the event that * it is a system function with dependencies on the static context * @return if a function of this name and arity is available for calling, then a corresponding * function item; or null if the function does not exist * @throws XPathException in the event of certain errors, for example attempting to get a function * that is private */ @Override public FunctionItem getFunctionItem(SymbolicName.F symbolicName, StaticContext staticContext) throws XPathException { StructuredQName functionName = symbolicName.getComponentName(); int arity = symbolicName.getArity(); if (functionName.hasURI(getNamespace()) && getFunctionDetails(functionName.getLocalPart(), arity) != null) { RetainedStaticContext rsc = staticContext.makeRetainedStaticContext(); SystemFunction fn = makeFunction(functionName.getLocalPart(), arity); if (staticContext.getXPathVersion() < 40 && fn instanceof Concat && arity < 2) { // Treat concat() specially prior to 4.0 return null; } fn.setRetainedStaticContext(rsc); return fn; } else { return null; } } /** * Register a system function in the table of function details. * * @param name the function name * @param arity the function arity (-1 is treated specially) * @return the entry describing the function. The entry is incomplete, it does not yet contain information * about the function arguments. */ // /*@NotNull*/ // protected Entry register(String name, // int arity, // Supplier functionFactory, // ItemType itemType, // int cardinality, // int properties) { // Instrumentation.count("Register function"); // Instrumentation.count(getNamespace().toString()); // Entry e = new Entry(); // e.name = new StructuredQName(getConventionalPrefix(), getNamespace(), name); // e.arity = arity; // e.implementationFactory = functionFactory; // e.functionSet = this; // e.itemType = itemType; // e.cardinality = cardinality; // e.properties = properties; // if (e.arity == -1) { // // special case for concat() // e.argumentTypes = new SequenceType[1]; // e.resultIfEmpty = new AtomicValue[1]; // e.usage = new OperandUsage[1]; // } else { // e.argumentTypes = new SequenceType[arity]; // e.resultIfEmpty = new Sequence[arity]; // e.usage = new OperandUsage[arity]; // } // if (functionTable.containsKey(name + "#" + arity)) { // Instrumentation.count("Duplicate function " + name + "#" + arity); // } // functionTable.put(name + "#" + arity, e); // if ((properties & SEQV) != 0) { // sequenceVariadicFunctions.put(name, arity - 1); // } // return e; // } protected Entry register(String name, int arity, java.util.function.Function populator) { Entry e = new Entry(); e.name = new StructuredQName(getConventionalPrefix(), getNamespace(), name); e.minArity = arity; e.maxArity = arity; e.populator = populator; e.functionSet = this; functionTable.put(name + "#" + arity, e); return e; } protected Entry register(String name, int minArity, int maxArity, java.util.function.Function populator) { Entry e = new Entry(); e.name = new StructuredQName(getConventionalPrefix(), getNamespace(), name); e.minArity = minArity; e.maxArity = maxArity; e.populator = populator; e.functionSet = this; for (int a = minArity; a <= maxArity; a++) { functionTable.put(name + "#" + a, e); } return e; } protected Entry registerVariadic(String name, int arity, java.util.function.Function populator) { Entry e = register(name, arity, populator); sequenceVariadicFunctions.put(name, arity - 1); return e; } /** * Return the namespace URI for the functions local to this function set. * @return the namespace URI of the functions local to this function set. * Note that functions imported from another function set may have a different * namespace URI. */ public NamespaceUri getNamespace() { return NamespaceUri.FN; } /** * Return a conventional prefix for use with this namespace, typically * the prefix used in the documentation of these functions. * @return the string "fn" */ public String getConventionalPrefix() { return "fn"; } /** * An entry in the table describing the properties of a function */ public static class Entry implements FunctionDefinition { /** * The name of the function as a QName */ public StructuredQName name; /** * The class containing the implementation of this function (always a subclass of SystemFunction) */ public Supplier implementationFactory; public java.util.function.Function populator; /** * The function set in which this function is defined */ public net.sf.saxon.functions.registry.BuiltInFunctionSet functionSet; /** * The upper bound of the arity range */ public int maxArity; /** * The lower bound of the arity range */ public int minArity; /** * The item type of the result of the function */ public ItemType itemType; /** * The cardinality of the result of the function */ public int cardinality; /** * The syntactic context of each argument for the purposes of streamability analysis */ public OperandUsage[] usage; /** * An array holding the names of the parameters to the function */ public String[] paramNames; /** * An array holding the types of the parameters to the function */ public SequenceType[] paramTypes; /** * An array holding, for each declared argument, the value that is to be returned if an empty sequence * as the value of this argument allows the result to be determined irrespective of the values of the * other arguments; null if there is no such calculation possible */ public Sequence[] resultIfEmpty; /** * An array holding functions to evaluate default arguments. The array is * allocated only if there are parameters with default values defined */ public IntHashMap defaultValueExpressions; /** * Any additional properties. Various bit settings are defined: for example SAME_AS_FIRST_ARGUMENT indicates that * the result type is the same as the type of the first argument */ public int properties; /** * For options parameters, details of the accepted options, their defaults, and required type */ public OptionsParameter optionDetails; public synchronized void ensurePopulated() { if (implementationFactory == null) { populator.apply(this); } } public Entry populate(Supplier functionFactory, ItemType itemType, int cardinality, int properties) { this.implementationFactory = functionFactory; this.itemType = itemType; this.cardinality = cardinality; this.properties = properties; if (this.maxArity == -1) { // special case for concat() this.paramTypes = new SequenceType[1]; this.resultIfEmpty = new AtomicValue[1]; this.usage = new OperandUsage[1]; } else { this.paramTypes = new SequenceType[maxArity]; this.resultIfEmpty = new Sequence[maxArity]; this.usage = new OperandUsage[maxArity]; } if ((properties & SEQV) != 0) { this.functionSet.sequenceVariadicFunctions.put(name.getLocalPart(), this.maxArity - 1); } NamespaceUri ns = name.getNamespaceUri(); Map paramNameMap; if (ns == NamespaceUri.FN) { paramNameMap = ParamKeywords.fnParamNames; } else if (ns == NamespaceUri.MAP_FUNCTIONS) { paramNameMap = ParamKeywords.mapParamNames; } else if (ns == NamespaceUri.ARRAY_FUNCTIONS) { paramNameMap = ParamKeywords.arrayParamNames; } else if (ns == NamespaceUri.MATH) { paramNameMap = ParamKeywords.mathParamNames; } else { paramNameMap = Collections.emptyMap(); } String keywords = paramNameMap.get(name.getLocalPart()); if (keywords == null) { keywords = paramNameMap.get(name.getLocalPart() + "#" + maxArity); } if (keywords == null) { keywords = "a|b|c|d|e|f"; } this.paramNames = keywords.split("\\|"); return this; } /** * Add information to a function entry about the argument types of the function * * @param a the position of the argument, counting from zero * @param type the item type of the argument * @param options the cardinality and usage of the argument * @param resultIfEmpty the value returned by the function if an empty sequence appears as the value * of this argument, in the case when this result is unaffected by any other arguments. Supply null * if this does not apply. * @return this entry (to allow chaining) */ public net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry arg(int a, ItemType type, int options, Sequence resultIfEmpty) { return arg(a, type, options, resultIfEmpty, null); } public net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry arg( int a, ItemType type, int options, Sequence resultIfEmpty, Expression defaultValue) { int cardinality = options & StaticProperty.CARDINALITY_MASK; OperandUsage usage = OperandUsage.NAVIGATION; if ((options & ABS) != 0) { usage = OperandUsage.ABSORPTION; } else if ((options & TRA) != 0) { usage = OperandUsage.TRANSMISSION; } else if ((options & INS) != 0) { usage = OperandUsage.INSPECTION; } else if (type instanceof PlainType) { usage = OperandUsage.ABSORPTION; } try { this.paramTypes[a] = SequenceType.makeSequenceType(type, cardinality); this.resultIfEmpty[a] = resultIfEmpty; this.usage[a] = usage; if (defaultValue != null) { withDefault(a, defaultValue); } } catch (ArrayIndexOutOfBoundsException err) { System.err.println("Internal Saxon error: Can't set argument " + a + " of " + name); } return this; } public net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry withDefault(int arg, Expression defaultValue) { if (this.defaultValueExpressions == null) { this.defaultValueExpressions = new IntHashMap<>(getNumberOfParameters()); } this.defaultValueExpressions.put(arg, defaultValue); // if argument N can be defaulted, then register a variant of the function with arity N-1 return this; } /** * Add details for options parameters (only applies to one argument, the function is expected to know which) */ public net.sf.saxon.functions.registry.BuiltInFunctionSet.Entry setOptionDetails(OptionsParameter details) { this.optionDetails = details; return this; } /** * Get the name of the function * * @return the function name */ @Override public StructuredQName getFunctionName() { return name; } @Override public int getNumberOfParameters() { return maxArity; } /** * Get the number of mandatory arguments (the lower bound of the arity range) * * @return the number of mandatory arguments */ @Override public int getMinimumArity() { return minArity; } /** * Get the name (keyword) of the Nth parameter * * @param i the position of the required parameter * @return the expression for computing the value of the Nth parameter */ @Override public StructuredQName getParameterName(int i) { return new StructuredQName("", "", paramNames[i]); } /** * Get the default value expression of the Nth parameter, if any * * @param i the position of the required parameter * @return the expression for computing the value of the Nth parameter, or null if there is none */ @Override public Expression getDefaultValueExpression(int i) { if (defaultValueExpressions != null) { return defaultValueExpressions.get(i); } else { return null; } } /** * Get the position in the parameter list of a given parameter name * * @param name the name of the required parameter * @return the position of the parameter in the parameter list, or -1 if absent */ @Override public int getPositionOfParameter(StructuredQName name) { for (int i = 0; i< maxArity; i++) { if (paramNames[i].equals(name.getLocalPart())) { return i; } } return -1; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy