Package org.exist.xquery

Source Code of org.exist.xquery.FunctionFactory

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2010 The eXist-db project
*  info@exist-db.org
*  http://www.exist-db.org
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*  $Id$
*/
package org.exist.xquery;

import java.util.ArrayList;
import java.util.List;

import org.exist.Namespaces;
import org.exist.dom.QName;
import org.exist.xquery.functions.fn.ExtNear;
import org.exist.xquery.functions.fn.ExtPhrase;
import org.exist.xquery.parser.XQueryAST;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;

public class FunctionFactory {

    public static final String ENABLE_JAVA_BINDING_ATTRIBUTE = "enable-java-binding";
    public static final String PROPERTY_ENABLE_JAVA_BINDING = "xquery.enable-java-binding";
    public static final String DISABLE_DEPRECATED_FUNCTIONS_ATTRIBUTE = "disable-deprecated-functions";
    public static final String PROPERTY_DISABLE_DEPRECATED_FUNCTIONS = "xquery.disable-deprecated-functions";
    public static final boolean DISABLE_DEPRECATED_FUNCTIONS_BY_DEFAULT = false;

    public static Expression createFunction(XQueryContext context, XQueryAST ast, PathExpr parent, List<Expression> params) throws XPathException {
      QName qname = null;
        try {
            qname = QName.parse(context, ast.getText(), context.getDefaultFunctionNamespace());
        } catch(final XPathException xpe) {
            xpe.setLocation(ast.getLine(), ast.getColumn());
            throw xpe;
        }
        return createFunction(context, qname, ast, parent, params);
    }

    public static Expression createFunction(XQueryContext context, QName qname, XQueryAST ast, PathExpr parent, List<Expression> params) throws XPathException {
        return createFunction(context, qname, ast, parent, params, true);
    }

    /**
     * Create a function call.
     *
     * This method handles all calls to built-in or user-defined
     * functions. It also deals with constructor functions and
     * optimizes some function calls like starts-with, ends-with or
     * contains.
     */
    public static Expression createFunction(XQueryContext context, QName qname, XQueryAST ast, PathExpr parent, List<Expression> params,
        boolean optimizeStrFuncs) throws XPathException {
        final String local = qname.getLocalName();
        final String uri = qname.getNamespaceURI();
        Expression step = null;
        if (optimizeStrFuncs && (Namespaces.XPATH_FUNCTIONS_NS.equals(uri) || Namespaces.XSL_NS.equals(uri))) {
            //TODO : move to text:near()
            if ("near".equals(local)) {
                step = near(context, ast, params);
            } else if("phrase".equals(local)) {
                step = phrase(context, ast, params);
            } else if("starts-with".equals(local)) {
                step = startsWith(context, ast, parent, params);
            } else if("ends-with".equals(local)) {
                step = endsWith(context, ast, parent, params);
            } else if("contains".equals(local)) {
                step = contains(context, ast, parent, params);
            } else if("equals".equals(local)) {
                step = equals(context, ast, parent, params);
            }
        //Check if the namespace belongs to one of the schema namespaces.
        //If yes, the function is a constructor function
        } else if (uri.equals(Namespaces.SCHEMA_NS) ||
                uri.equals(Namespaces.XPATH_DATATYPES_NS)) {
            step = castExpression(context, ast, params, qname);
        //Check if the namespace URI starts with "java:". If yes, treat
        //the function call as a call to an arbitrary Java function.
        } else if (uri.startsWith("java:")) {
            step = javaFunctionBinding(context, ast, params, qname);
        }
        //None of the above matched: function is either a built-in function or
        //a user-defined function
        if (step == null) {
            step = functionCall(context, ast, params, qname);
        }
        return step;
    }

    /**
     * near(node-set, string)
     */
    private static ExtNear near(XQueryContext context, XQueryAST ast,
            List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function near() requires two arguments");
        }
        PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of near() is empty");
        }
        final Expression e1 = p1.getExpression(0);
        final ExtNear near = new ExtNear(context);
        near.setLocation(ast.getLine(), ast.getColumn());
        near.addTerm(e1);
        near.setPath((PathExpr) params.get(0));
        if (params.size() > 2) {
            p1 = (PathExpr) params.get(2);
            if (p1.getLength() == 0) {
                throw new XPathException(ast.getLine(), ast.getColumn(),
                    "Max distance argument of near() is empty");
            }
            near.setMaxDistance(p1);
            if (params.size() == 4) {
                p1 = (PathExpr) params.get(3);
                if(p1.getLength() == 0) {
                    throw new XPathException(ast.getLine(), ast.getColumn(),
                        "Min distance argument of near() is empty");
                }
                near.setMinDistance(p1);
            }
        }
        return near;
    }

    /**
     * phrase(node-set, string)
     */
    private static ExtPhrase phrase(XQueryContext context, XQueryAST ast,
            List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function phrase() requires two arguments");
        }
        final PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of phrase() is empty");
        }
        final Expression e1 = p1.getExpression(0);
        final ExtPhrase phrase = new ExtPhrase(context);
        phrase.setLocation(ast.getLine(), ast.getColumn());
        phrase.addTerm(e1);
        phrase.setPath((PathExpr) params.get(0));
        return phrase;
    }

    /**
     * starts-with(node-set, string)
     */
    private static GeneralComparison startsWith(XQueryContext context,
            XQueryAST ast, PathExpr parent, List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function starts-with() requires two or three arguments");
        }
        if (params.size() > 3) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function starts-with() requires two or three arguments");
        }
        final PathExpr p0 = (PathExpr) params.get(0);
        final PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of starts-with() is empty");
        }
        final GeneralComparison op = new GeneralComparison(context, p0, p1,
            Constants.EQ, Constants.TRUNC_RIGHT);
        op.setLocation(ast.getLine(), ast.getColumn());
        //TODO : not sure for parent -pb
        context.getProfiler().message(parent, Profiler.OPTIMIZATIONS,
                "OPTIMIZATION", "Rewritten start-with as a general comparison with a right truncations");
        if (params.size() == 3) {
            op.setCollation((Expression) params.get(2));
        }
        return op;
    }

    /**
     * ends-with(node-set, string)
     */
    private static GeneralComparison endsWith(XQueryContext context, XQueryAST ast,
            PathExpr parent, List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function ends-with() requires two or three arguments");
        }
        if (params.size() > 3) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function ends-with() requires two or three arguments");
        }
        final PathExpr p0 = (PathExpr) params.get(0);
        final PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of ends-with() is empty");
        }
        final GeneralComparison op = new GeneralComparison(context, p0, p1, Constants.EQ, Constants.TRUNC_LEFT);
        //TODO : not sure for parent -pb
        context.getProfiler().message(parent, Profiler.OPTIMIZATIONS,
            "OPTIMIZATION", "Rewritten ends-with as a general comparison with a left truncations");
        op.setLocation(ast.getLine(), ast.getColumn());
        if(params.size() == 3) {
            op.setCollation((Expression) params.get(2));
        }
        return op;
    }

    /**
     * contains(node-set, string)
     */
    private static GeneralComparison contains(XQueryContext context, XQueryAST ast,
            PathExpr parent, List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function contains() requires two or three arguments");
        }
        if (params.size() > 3) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function contains() requires two or three arguments");
        }
        final PathExpr p0 = (PathExpr) params.get(0);
        final PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of contains() is empty");
        }
        final GeneralComparison op = new GeneralComparison(context, p0, p1,
            Constants.EQ, Constants.TRUNC_BOTH);
        //TODO : not sure for parent -pb
        context.getProfiler().message(parent, Profiler.OPTIMIZATIONS,
            "OPTIMIZATION", "Rewritten contains() as a general comparison with left and right truncations");
        op.setLocation(ast.getLine(), ast.getColumn());
        if (params.size() == 3) {
            op.setCollation((Expression) params.get(2));
        }
        return op;
    }

    /**
     * equals(node-set, string)
     */
    private static GeneralComparison equals(XQueryContext context, XQueryAST ast,
            PathExpr parent, List<Expression> params) throws XPathException {
        if (params.size() < 2) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function equals() requires two or three arguments");
        }
        if (params.size() > 3) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Function equals() requires two or three arguments");
        }
        final PathExpr p0 = (PathExpr) params.get(0);
        final PathExpr p1 = (PathExpr) params.get(1);
        if (p1.getLength() == 0) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Second argument of equals() is empty");
        }
        final GeneralComparison op = new GeneralComparison(context, p0, p1,
            Constants.EQ, Constants.TRUNC_EQUALS);
        //TODO : not sure for parent -pb
        context.getProfiler().message(parent, Profiler.OPTIMIZATIONS,
            "OPTIMIZATION", "Rewritten contains() as a general comparison with no truncations");
        op.setLocation(ast.getLine(), ast.getColumn());
        if (params.size() == 3) {
            op.setCollation((Expression) params.get(2));
        } else {
            op.setCollation(new StringValue("?strength=identical"));
        }
        return op;
    }

    private static CastExpression castExpression(XQueryContext context,
            XQueryAST ast, List<Expression> params, QName qname) throws XPathException {
        if (params.size() != 1) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
            ErrorCodes.XPST0017, "Wrong number of arguments for constructor function");
        }
        final Expression arg = params.get(0);
        final int code = Type.getType(qname);
        final CastExpression castExpr = new CastExpression(context, arg, code, Cardinality.ZERO_OR_ONE);
        castExpr.setLocation(ast.getLine(), ast.getColumn());
        return castExpr;
    }

    private static JavaCall javaFunctionBinding(XQueryContext context,
            XQueryAST ast, List<Expression> params, QName qname) throws XPathException {
        //Only allow java binding if specified in config file <xquery enable-java-binding="yes">
        final String javabinding = (String) context.getBroker().getConfiguration()
            .getProperty(PROPERTY_ENABLE_JAVA_BINDING);
        if(javabinding == null || !"yes".equals(javabinding)) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Java binding is disabled in the current configuration (see conf.xml)." +
                " Call to " + qname.getStringValue() + " denied.");
        }
        final JavaCall call = new JavaCall(context, qname);
        call.setLocation(ast.getLine(), ast.getColumn());
        call.setArguments(params);
        return call;
    }

    private static Function functionCall(XQueryContext context,
            XQueryAST ast, List<Expression> params, QName qname) throws XPathException {
        final Function fn;
        final String uri = qname.getNamespaceURI();
        final Module module = context.getModule(uri);
        if (module != null) {
            //Function belongs to a module
            if (module.isInternalModule()) {
                fn = getInternalModuleFunction(context, ast, params, qname, module);
            } else {
                //Function is from an imported XQuery module
                fn = getXQueryModuleFunction(context, ast, params, qname, module);
            }
        } else {
            fn = getUserDefinedFunction(context, ast, params, qname);
        }
        return fn;
    }

    /**
     * Gets a Java function from an Java XQuery Extension Module
     */
    private static Function getInternalModuleFunction(XQueryContext context,
            XQueryAST ast, List<Expression> params, QName qname, Module module) throws XPathException {
        //For internal modules: create a new function instance from the class
        FunctionDef def = ((InternalModule) module).getFunctionDef(qname, params.size());
        //TODO: rethink: xsl namespace function should search xpath one too
        if (def == null && Namespaces.XSL_NS.equals(qname.getNamespaceURI())) {
            //Search xpath namespace
            Module _module_ = context.getModule(Namespaces.XPATH_FUNCTIONS_NS);
            if(_module_ != null) {
                module = _module_;
                qname.setNamespaceURI(Namespaces.XPATH_FUNCTIONS_NS);
                def = ((InternalModule) module).getFunctionDef(qname, params.size());
            }
        }
        if (def == null) {
            final List<FunctionSignature> funcs = ((InternalModule) module).getFunctionsByName(qname);
            if (funcs.isEmpty()) {
                throw new XPathException(ast.getLine(), ast.getColumn(),
                ErrorCodes.XPST0017, "Function " + qname.getStringValue() + "() " +
                    " is not defined in module namespace: " + qname.getNamespaceURI());
            } else {
                final StringBuilder buf = new StringBuilder();
                buf.append("Unexpectedly received ");
                buf.append(params.size());
                buf.append(" parameter(s) in call to function ");
                buf.append("'");
                buf.append(qname.getStringValue());
                buf.append("()'. ");
                buf.append("Defined function signatures are:\r\n");
                for (final FunctionSignature sig : funcs) {
                    buf.append(sig.toString()).append("\r\n");
                }
                throw new XPathException(ast.getLine(), ast.getColumn(), ErrorCodes.XPST0017, buf.toString());
            }
        }
        if (((Boolean) context.getBroker().getConfiguration()
                .getProperty(PROPERTY_DISABLE_DEPRECATED_FUNCTIONS)).booleanValue() &&
                def.getSignature().isDeprecated()) {
            throw new XPathException(ast.getLine(), ast.getColumn(),
                "Access to deprecated functions is not allowed. Call to '" + qname.getStringValue() + "()' denied. " + def.getSignature().getDeprecated());
        }
        final Function fn = Function.createFunction(context, ast, def);
        fn.setArguments(params);
        fn.setASTNode(ast);
        return new InternalFunctionCall(fn);
    }

    /**
     * Gets an user defined function from the XQuery
     */
    private static FunctionCall getUserDefinedFunction(XQueryContext context, XQueryAST ast, List<Expression> params, QName qname) throws XPathException {
        final FunctionCall fc;
        final UserDefinedFunction func = context.resolveFunction(qname, params.size());
        if (func != null) {
            fc = new FunctionCall(context, func);
            fc.setLocation(ast.getLine(), ast.getColumn());
            fc.setArguments(params);
        } else {
            //Create a forward reference which will be resolved later
            fc = new FunctionCall(context, qname, params);
            fc.setLocation(ast.getLine(), ast.getColumn());
            context.addForwardReference(fc);
        }
        return fc;
    }

    /**
     * Gets an XQuery function from an XQuery Module
     */
    private static FunctionCall getXQueryModuleFunction(XQueryContext context,
            XQueryAST ast, List<Expression> params, QName qname, Module module) throws XPathException {
        final FunctionCall fc;
        final UserDefinedFunction func = ((ExternalModule) module).getFunction(qname, params.size(), context);
        if (func == null) {
            // check if the module has been compiled already
            if (module.isReady()) {
                throw new XPathException(ast.getLine(), ast.getColumn(),
                ErrorCodes.XPST0017, "Function " + qname.getStringValue() +
                    "() is not defined in namespace '" + qname.getNamespaceURI() + "'");
            // If not, postpone the function resolution
            // Register a forward reference with the root module, so it gets resolved
            // when the main query has been compiled.
            } else {
                fc = new FunctionCall(((ExternalModule) module).getContext(), qname, params);
                fc.setLocation(ast.getLine(), ast.getColumn());
                if(((ExternalModule) module).getContext() == context) {
                    context.addForwardReference(fc);
                } else {
                    context.getRootContext().addForwardReference(fc);
                }
            }
        } else {
            fc = new FunctionCall(context, func);
            fc.setArguments(params);
            fc.setLocation(ast.getLine(), ast.getColumn());
        }
        return fc;
    }
    /**
     * Wrap a function call into a user defined function.
     * This is used to handle dynamic function calls or partial
     * function applications on built in functions.
     *
     * @param context
     * @param call the function call to be wrapped
     * @return a new function call referencing an inline function
     * @throws XPathException
     */
    public static FunctionCall wrap(XQueryContext context, Function call) throws XPathException {
    final int argCount = call.getArgumentCount();
    final QName[] variables = new QName[argCount];
    final List<Expression> innerArgs = new ArrayList<Expression>(argCount);
    final List<Expression> wrapperArgs = new ArrayList<Expression>(argCount);
    final FunctionSignature signature = call.getSignature();
    // the parameters of the newly created inline function:
    final List<SequenceType> newParamTypes = new ArrayList<SequenceType>();
    final SequenceType[] paramTypes = signature.getArgumentTypes();
    for (int i = 0; i < argCount; i++) {
      final Expression param = call.getArgument(i);
      wrapperArgs.add(param);
      QName varName = new QName("vp" + i);
      variables[i] = varName;
      final VariableReference ref = new VariableReference(context, varName.toString());
      innerArgs.add(ref);
     
      // copy parameter sequence types
      // overloaded functions like concat may have an arbitrary number of arguments
      if (i < paramTypes.length)
        {newParamTypes.add(paramTypes[i]);}
      else
        // overloaded function: add last sequence type
        {newParamTypes.add(paramTypes[paramTypes.length - 1]);}
    }
    final SequenceType[] newParamArray = newParamTypes.toArray(new SequenceType[newParamTypes.size()]);
    final FunctionSignature newSignature = new FunctionSignature(signature);
        newSignature.setArgumentTypes(newParamArray);

    final UserDefinedFunction func = new UserDefinedFunction(context, newSignature);
    for (final QName varName: variables) {
      func.addVariable(varName);
    }
   
    call.setArguments(innerArgs);
   
    func.setFunctionBody(call);
   
    final FunctionCall wrappedCall = new FunctionCall(context, func);
    wrappedCall.setArguments(wrapperArgs);
    return wrappedCall;
  }
}
TOP

Related Classes of org.exist.xquery.FunctionFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.