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

net.sf.saxon.query.XQueryFunctionLibrary 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.query;

import net.sf.saxon.Configuration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.functions.CallableFunction;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.functions.hof.UnresolvedXQueryFunctionItem;
import net.sf.saxon.functions.hof.UserFunctionReference;
import net.sf.saxon.om.FunctionItem;
import net.sf.saxon.om.NamespaceUri;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.SpecificFunctionType;
import net.sf.saxon.value.SequenceType;

import java.util.*;

/**
 * An XQueryFunctionLibrary is a function library containing all the user-defined functions available for use within a
 * particular XQuery module: that is, the functions declared in that module, and the functions imported from other
 * modules. It also contains (transiently during compilation) a list of function calls within the module that have not
 * yet been bound to a specific function declaration.
 */

public class XQueryFunctionLibrary implements FunctionLibrary, XQueryFunctionBinder {

    private Configuration config;

    // The functions in this library are represented using a HashMap
    // The key of the hashmap is an object that encodes the QName of the function and its arity
    // The value in the hashmap is an XQueryFunction
    /*@NotNull*/ private HashMap functions =
            new HashMap<>(20);

    private HashMap> functionsByName =
            new HashMap<>(20);

    /**
     * Create an XQueryFunctionLibrary
     *
     * @param config the Saxon configuration
     */

    public XQueryFunctionLibrary(Configuration config) {
        this.config = config;
    }

    /**
     * Set the Configuration options
     *
     * @param config the Saxon configuration
     */

    @Override
    public void setConfiguration(Configuration config) {
        this.config = config;
    }

    /**
     * Get the Configuration options
     *
     * @return the Saxon configuration
     */

    public Configuration getConfiguration() {
        return config;
    }

    /**
     * Register a user-defined XQuery function
     *
     * @param function the function to be registered
     * @throws XPathException if there is an existing function with the same name and arity
     */

    public void declareFunction(/*@NotNull*/ XQueryFunction function) throws XPathException {
        SymbolicName keyObj = function.getIdentificationKey();

        // Test if the arity range of this function overlaps the arity range of another function
        StructuredQName functionName = function.getFunctionName();
        @SuppressWarnings("Convert2Diamond")
        List existingFunctions = functionsByName.computeIfAbsent(
                functionName, k -> new ArrayList(2));
        for (XQueryFunction existing : existingFunctions) {
            if (existing == function) {
                return;
            }
            if (hasOverlappingArity(function, existing)) {
                XPathException err = new XPathException("Conflicting definition of function " +
                                                                function.getDisplayName() +
                                                                " (see line " + existing.getLineNumber() + " in " + existing.getSystemId() + ')');
                err.setErrorCode("XQST0034");
                err.setIsStaticError(true);
                err.setLocator(function);
                throw err;
            }
        }
        functions.put(keyObj, function);
        existingFunctions.add(function);
    }

    private static boolean hasOverlappingArity(XQueryFunction f1, XQueryFunction f2) {
        // From https://stackoverflow.com/questions/3269434,
        // [x1:x2] overlaps [y1:y2] === x1 <= y2 && y1 <= x2
        return f1.getMinimumArity() <= f2.getNumberOfParameters()
                && f2.getMinimumArity() <= f1.getNumberOfParameters();
    }

    /**
     * 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 functionName 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 */ @Override public FunctionItem getFunctionItem(SymbolicName.F functionName, StaticContext staticContext) throws XPathException { XQueryFunction fd = getDeclaration(functionName.getComponentName(), functionName.getArity()); if (fd != null) { if (fd.isPrivate() && !fd.getSystemId().equals(staticContext.getStaticBaseURI())) { throw new XPathException("Cannot call the private function " + functionName.getComponentName().getDisplayName() + " from outside its module", "XPST0017"); } final UserFunction fn = fd.getUserFunction(); // FunctionItemType type = new SpecificFunctionType( // fd.getArgumentTypes(), fd.getResultType(), fd.getAnnotations()); if (fn == null) { // not yet compiled: create a dummy UserFunction uf = new UserFunction(); uf.setFunctionName(functionName.getComponentName()); uf.setResultType(fd.getResultType()); uf.setParameterDefinitions(fd.getParameterDefinitions()); final UserFunctionReference ref = new UserFunctionReference(uf, functionName); fd.registerReference(ref); return new UnresolvedXQueryFunctionItem(fd, functionName, ref); } else if (functionName.getArity() == fd.getNumberOfParameters()) { // all arguments supplied return fn; } else { // return a reference to a reduced-arity version in which some of the arguments are defaulted Callable callable = new ReducedArityCallable(fd, fn); SequenceType[] argTypes = new SequenceType[functionName.getArity()]; for (int i=0; i< functionName.getArity(); i++) { argTypes[i] = fd.getArgumentTypes()[i]; } SpecificFunctionType functionType = new SpecificFunctionType(argTypes, fd.getResultType()); return new CallableFunction(functionName, callable, functionType); } } else { return null; } } /** * Test whether a function with a given name and arity is available *

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

* * @param functionName the qualified name of the function being called * @param languageLevel the XPath language level times 10 (31 = XPath 3.1) * @return true if a function of this name and arity is available for calling */ @Override public boolean isAvailable(SymbolicName.F functionName, int languageLevel) { return functions.get(functionName) != null; } /** * Inner class containing information about a reference to a function whose declaration * has not yet been encountered. The references gets fixed up later, once information * about all user-declared functions is available. */ public static class UnresolvedCallable implements UserFunctionResolvable, Callable { SymbolicName.F symbolicName; UserFunction function; public UnresolvedCallable(SymbolicName.F symbolicName) { this.symbolicName = symbolicName; } public StructuredQName getFunctionName() { return symbolicName.getComponentName(); } public int getArity() { return symbolicName.getArity(); } //public void setFunctionItem(CallableFunctionItem fi) { // this.functionItem = fi; //} /** * Evaluate the expression * * @param context the dynamic evaluation context * @param arguments the values of the arguments, supplied as Sequences * @return the result of the evaluation, in the form of a Sequence * @throws net.sf.saxon.trans.XPathException * if a dynamic error occurs during the evaluation of the expression */ @Override public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { if (function == null) { throw new XPathException("Forwards reference to XQuery function has not been resolved"); } Sequence[] args = new Sequence[arguments.length]; for (int i = 0; i < arguments.length; i++) { args[i] = arguments[i].materialize(); // TODO: is the copy needed? } return function.call(context.newCleanContext(), args); } @Override public void setFunction(UserFunction function) { this.function = function; } public UserFunction getFunction() { return function; } } /** * Identify a (namespace-prefixed) function appearing in the expression. This * method is called by the XQuery parser to resolve function calls found within * the query. *

Note that a function call may appear earlier in the query than the definition * of the function to which it is bound. Unlike XSLT, we cannot search forwards to * find the function definition. Binding of function calls is therefore a two-stage * process; at the time the function call is parsed, we simply register it as * pending; subsequently at the end of query parsing all the pending function * calls are resolved. Another consequence of this is that we cannot tell at the time * a function call is parsed whether it is a call to an internal (XSLT or XQuery) * function or to an extension function written in Java. * * @return an Expression representing the function call. This will normally be * a FunctionCall, but it may be rewritten as some other expression. */ /*@Nullable*/ @Override public Expression bind(SymbolicName.F functionName, Expression[] arguments, Map keywords, StaticContext env, List reasons) throws XPathException { XQueryFunction fd = getDeclaration(functionName.getComponentName(), arguments.length); if (fd != null) { if (fd.isPrivate() && fd.getStaticContext() != env) { reasons.add("Cannot call the private XQuery function " + functionName.getComponentName().getDisplayName() + " from outside its module"); return null; } UserFunctionCall ufc = new UserFunctionCall(); ufc.setFunctionName(fd.getFunctionName()); int maxArity = fd.getNumberOfParameters(); if (arguments.length == maxArity && (keywords == null || keywords.isEmpty())) { ufc.setArguments(arguments); } else { Expression[] expandedArgs = UserFunction.makeExpandedArgumentArray(arguments, keywords, fd); ufc.setArguments(expandedArgs); } ufc.setStaticType(fd.getResultType()); UserFunction fn = fd.getUserFunction(); if (fn == null) { // not yet compiled fd.registerReference(ufc); } else { ufc.setFunction(fn); } return ufc; } else { return null; } } /** * Get the function declaration corresponding to a given function name and arity * * @return the XQueryFunction if there is one, or null if not. */ @Override public XQueryFunction getDeclaration(StructuredQName functionName, int staticArgs) { List homonyms = functionsByName.get(functionName); if (homonyms != null) { for (XQueryFunction f : homonyms) { if (f.getMinimumArity() <= staticArgs && f.getNumberOfParameters() >= staticArgs) { return f; } } } return null; } /** * Bind a function call using this XQuery function library, in the situation where * it was not possible to bind it earlier, typically because it was encountered as a forwards * reference. * * @param ufc The unbound function call, which will include a non-null UnboundFunctionCallDetails * @param reasons a list which can be populated with messages indicating why binding failed * @return true if the function call is now bound; false if it remains unbound. */ public boolean bindUnboundFunctionCall(UserFunctionCall ufc, List reasons) { UnboundFunctionLibrary.UnboundFunctionCallDetails details = ufc.getUnboundCallDetails(); assert details != null; StructuredQName functionName = details.functionName.getComponentName(); Expression[] arguments = details.arguments; Map keywords = details.keywords; XQueryFunction fd = getDeclaration(functionName, arguments.length); if (fd != null) { if (fd.isPrivate() && fd.getStaticContext() != details.env) { reasons.add("Cannot call the private XQuery function " + functionName.getDisplayName() + " from outside its module"); return false; } ufc.setFunctionName(fd.getFunctionName()); int maxArity = fd.getNumberOfParameters(); if (arguments.length == maxArity && (details.keywords == null || details.keywords.isEmpty())) { ufc.setArguments(arguments); } else { // 4.0: handle keyword arguments and default arguments Expression[] expandedArgs = Arrays.copyOf(arguments, maxArity); // If there are keyword arguments, reposition them to the correct position in the argument sequence if (keywords != null) { int positionalArgs = arguments.length - keywords.size(); for (Map.Entry entry : keywords.entrySet()) { StructuredQName key = entry.getKey(); int argPos = entry.getValue(); int paramPos = fd.getPositionOfParameter(key); if (paramPos < 0) { throw new UncheckedXPathException("Keyword " + key + " does not match the name of any declared parameter", "XPST0142"); } if (paramPos < positionalArgs) { throw new UncheckedXPathException("Parameter " + key + " is supplied both by position and by keyword", "XPST0141"); } Expression supplied = arguments[paramPos]; expandedArgs[argPos] = null; expandedArgs[paramPos] = supplied; } } for (int a = 0; a < maxArity; a++) { if (expandedArgs[a] == null) { expandedArgs[a] = fd.getParameterDefinitions()[a].getDefaultValueExpression(); } } ufc.setArguments(expandedArgs); } ufc.setStaticType(fd.getResultType()); UserFunction fn = fd.getUserFunction(); if (fn == null) { // not yet compiled fd.registerReference(ufc); } else { ufc.setFunction(fn); } return true; } else { return false; } } /** * Get the function declaration corresponding to a given function name and arity, supplied * in the form "{uri}local/arity" * * @param functionKey a string in the form "{uri}local/arity" identifying the required function * @return the XQueryFunction if there is one, or null if not. */ public XQueryFunction getDeclarationByKey(SymbolicName functionKey) { return functions.get(functionKey); } /** * Get an iterator over the Functions defined in this module * * @return an Iterable, whose items are {@link XQueryFunction} objects. It returns * all function known to this module including those imported from elsewhere; they * can be distinguished by their namespace. */ public Iterable getFunctionDefinitions() { return functions.values(); } /** * Fixup all references to global functions. This method is called * on completion of query parsing. Each XQueryFunction is required to * bind all references to that function to the object representing the run-time * executable code of the function. *

This method is for internal use.

* * @param env the static context for the main query body. * @throws net.sf.saxon.trans.XPathException * if an error occurs */ protected void fixupGlobalFunctions(/*@NotNull*/ QueryModule env) throws XPathException { ExpressionVisitor visitor = ExpressionVisitor.make(env); for (XQueryFunction fn : functions.values()) { fn.compile(); } for (XQueryFunction fn : functions.values()) { fn.checkReferences(visitor); } } /** * Optimize the body of all global functions. This may involve inlining functions calls * * @throws net.sf.saxon.trans.XPathException * if an error occurs * @param topModule the top level module of the compilation unit whose functions are * to be optimized; functions in separately-compiled imported libraries * are unaffected. */ protected void optimizeGlobalFunctions(QueryModule topModule) throws XPathException { for (XQueryFunction fn : functions.values()) { if (((QueryModule)fn.getStaticContext()).getTopLevelModule() == topModule) { fn.optimize(); } } } /** * Output "explain" information about each declared function * * @param out the ExpressionPresenter that renders the output * @throws XPathException if things go wrong */ public void explainGlobalFunctions(/*@NotNull*/ ExpressionPresenter out) throws XPathException { for (XQueryFunction fn : functions.values()) { fn.explain(out); } } /** * Get the function with a given name and arity. This method is provided so that XQuery functions * can be called directly from a Java application. Note that there is no type checking or conversion * of arguments when this is done: the arguments must be provided in exactly the form that the function * signature declares them. * * @param uri the uri of the function name * @param localName the local part of the function name * @param arity the number of arguments. * @return the function identified by the URI, local name, and arity; or null if there is no such function */ /*@Nullable*/ public UserFunction getUserDefinedFunction(/*@NotNull*/ NamespaceUri uri, /*@NotNull*/ String localName, int arity) { SymbolicName functionKey = new SymbolicName.F(new StructuredQName("", uri, localName), arity); XQueryFunction fd = functions.get(functionKey); if (fd == null) { return null; } return fd.getUserFunction(); } /** * 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. */ /*@NotNull*/ @Override public FunctionLibrary copy() { XQueryFunctionLibrary qfl = new XQueryFunctionLibrary(config); qfl.functions = new HashMap(functions); return qfl; } private static class ReducedArityCallable implements Callable { private final XQueryFunction declaredFunction; private final UserFunction userFunction; public ReducedArityCallable(XQueryFunction fd, UserFunction fn) { this.declaredFunction = fd; this.userFunction = fn; } @Override public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { Sequence[] extendedArguments = Arrays.copyOf(arguments, userFunction.getArity()); for (int i = arguments.length; i < userFunction.getArity(); i++) { // Evaluate the default value expression for the omitted argument extendedArguments[i] = declaredFunction.getParameterDefinitions()[i].getDefaultValueExpression().makeElaborator().eagerly().evaluate(context); } return userFunction.call(context, extendedArguments); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy