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-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.functions.registry;

import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.Block;
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.lib.NamespaceConstant;
import net.sf.saxon.om.Function;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
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 java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * 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(BuiltInFunctionSet importee) {
        if (!importee.getNamespace().equals(getNamespace())) {
            throw new IllegalArgumentException(importee.getNamespace());
        }
        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 Entry getFunctionDetails(String name, int arity) {
        if (arity == -1) {
            for (int i=0; i<20; i++) {
                Entry found = getFunctionDetails(name, i);
                if (found != null) {
                    return found;
                }
            }
            return null;
        }
        String key = name + "#" + arity;
        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 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, StaticContext env, List reasons) { StructuredQName functionName = symbolicName.getComponentName(); int arity = symbolicName.getArity(); String localName = functionName.getLocalPart(); Entry entry = getFunctionDetails(localName, arity); if (functionName.hasURI(getNamespace()) && entry != null) { 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 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; } 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 { Entry entry = getFunctionDetails(name, arity); if (entry == null) { String diagName = getNamespace().equals(NamespaceConstant.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; } } Class functionClass = entry.implementationClass; SystemFunction f = instantiate(name, functionClass); f.setDetails(entry); f.setArity(arity); return f; } private SystemFunction instantiate(String name, Class functionClass) { SystemFunction f; try { //noinspection RedundantCast f = (SystemFunction)functionClass.newInstance(); } catch (Exception err) { err.printStackTrace(); throw new AssertionError("Failed to instantiate system function " + name + " - " + err.getMessage()); } 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; } 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 Function 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) * @param implementationClass the class used to implement the function * @param itemType the item type of the result of the function * @param cardinality the cardinality of the result of the function * @param properties bitwise properties of the function * @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, Class implementationClass, ItemType itemType, int cardinality, int properties) { Entry e = new Entry(); e.name = new StructuredQName(getConventionalPrefix(), getNamespace(), name); e.arity = arity; e.implementationClass = implementationClass; 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]; } functionTable.put(name + "#" + arity, e); if ((properties & SEQV) != 0) { sequenceVariadicFunctions.put(name, arity - 1); } return e; } /** * Register reduced-arity versions of a function, with arities in a specified range, * using common argument and return types * @param key the function name * @param min the minimum arity * @param max the maximum arity */ protected void registerReducedArityVariants(String key, int min, int max) { Entry master = functionTable.get(key); for (int arity = min; arity <= max; arity++) { Entry e = new Entry(); e.name = master.name; e.arity = arity; e.implementationClass = master.implementationClass; e.itemType = master.itemType; e.cardinality = master.cardinality; e.properties = master.properties; e.argumentTypes = new SequenceType[arity]; e.resultIfEmpty = new Sequence[arity]; e.usage = new OperandUsage[arity]; for (int i=0; i implementationClass; /** * The function set in which this function is defined */ public BuiltInFunctionSet functionSet; /** * The arity of the function */ public int arity; /** * 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 types of the arguments to the function */ public SequenceType[] argumentTypes; /** * 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; /** * 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; /** * 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 Entry arg(int a, ItemType type, int options, Sequence resultIfEmpty) { 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.argumentTypes[a] = SequenceType.makeSequenceType(type, cardinality); this.resultIfEmpty[a] = resultIfEmpty; this.usage[a] = usage; } catch (ArrayIndexOutOfBoundsException err) { System.err.println("Internal Saxon error: Can't set argument " + a + " of " + name); } return this; } /** * Add details for options parameters (only applies to one argument, the function is expected to know which) */ public Entry setOptionDetails(OptionsParameter details) { this.optionDetails = details; return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy