    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.jxpath;
    018
    019    import java.lang.reflect.Constructor;
    020    import java.lang.reflect.Method;
    021    import java.util.Collection;
    022    import java.util.Collections;
    023    import java.util.Iterator;
    024    import java.util.Set;
    025
    026    import org.apache.commons.jxpath.functions.ConstructorFunction;
    027    import org.apache.commons.jxpath.functions.MethodFunction;
    028    import org.apache.commons.jxpath.util.MethodLookupUtils;
    029    import org.apache.commons.jxpath.util.TypeUtils;
    030
    031    /**
    032     * Extension functions provided by Java classes.  The class prefix specified
    033     * in the constructor is used when a constructor or a static method is called.
    034     * Usually, a class prefix is a package name (hence the name of this class).
    035     *
    036     * Let's say, we declared a PackageFunction like this:
    037     * <blockquote><pre>
    038     *     new PackageFunctions("java.util.", "util")
    039     * </pre></blockquote>
    040     *
    041     * We can now use XPaths like:
    042     * <dl>
    043     *  <dt><code>"util:Date.new()"</code></dt>
    044     *  <dd>Equivalent to <code>new java.util.Date()</code></dd>
    045     *  <dt><code>"util:Collections.singleton('foo')"</code></dt>
    046     *  <dd>Equivalent to <code>java.util.Collections.singleton("foo")</code></dd>
    047     *  <dt><code>"util:substring('foo', 1, 2)"</code></dt>
    048     *  <dd>Equivalent to <code>"foo".substring(1, 2)</code>.  Note that in
    049     *  this case, the class prefix is not used. JXPath does not check that
    050     *  the first parameter of the function (the method target) is in fact
    051     *  a member of the package described by this PackageFunctions object.</dd>
    052     * </dl>
    053     *
    054     * <p>
    055     * If the first argument of a method or constructor is {@link ExpressionContext},
    056     * the expression context in which the function is evaluated is passed to
    057     * the method.
    058     * </p>
    059     * <p>
    060     * There is one PackageFunctions object registered by default with each
    061     * JXPathContext.  It does not have a namespace and uses no class prefix.
    062     * The existence of this object allows us to use XPaths like:
    063     * <code>"java.util.Date.new()"</code> and <code>"length('foo')"</code>
    064     * without the explicit registration of any extension functions.
    065     * </p>
    066     *
    067     * @author Dmitri Plotnikov
    068     * @version $Revision: 670727 $ $Date: 2008-06-23 15:10:38 -0500 (Mon, 23 Jun 2008) $
    069     */
    070    public class PackageFunctions implements Functions {
    071        private String classPrefix;
    072        private String namespace;
    073        private static final Object[] EMPTY_ARRAY = new Object[0];
    074
    075        /**
    076         * Create a new PackageFunctions.
    077         * @param classPrefix class prefix
    078         * @param namespace namespace String
    079         */
    080        public PackageFunctions(String classPrefix, String namespace) {
    081            this.classPrefix = classPrefix;
    082            this.namespace = namespace;
    083        }
    084
    085        /**
    086         * Returns the namespace specified in the constructor
    087         * @return (singleton) namespace Set
    088         */
    089        public Set getUsedNamespaces() {
    090            return Collections.singleton(namespace);
    091        }
    092
    093        /**
    094         * Returns a {@link Function}, if found, for the specified namespace,
    095         * name and parameter types.
    096         * <p>
    097         * @param  namespace - if it is not the same as specified in the
    098         * construction, this method returns null
    099         * @param name - name of the method, which can one these forms:
    100         * <ul>
    101         * <li><b>methodname</b>, if invoking a method on an object passed as the
    102         * first parameter</li>
    103         * <li><b>Classname.new</b>, if looking for a constructor</li>
    104         * <li><b>subpackage.subpackage.Classname.new</b>, if looking for a
    105         * constructor in a subpackage</li>
    106         * <li><b>Classname.methodname</b>, if looking for a static method</li>
    107         * <li><b>subpackage.subpackage.Classname.methodname</b>, if looking for a
    108         * static method of a class in a subpackage</li>
    109         * </ul>
    110         * @param parameters Object[] of parameters
    111         * @return a MethodFunction, a ConstructorFunction or null if no function
    112         * is found
    113         */
    114        public Function getFunction(
    115            String namespace,
    116            String name,
    117            Object[] parameters) {
    118            if ((namespace == null && this.namespace != null) //NOPMD
    119                || (namespace != null && !namespace.equals(this.namespace))) {
    120                return null;
    121            }
    122
    123            if (parameters == null) {
    124                parameters = EMPTY_ARRAY;
    125            }
    126
    127            if (parameters.length >= 1) {
    128                Object target = TypeUtils.convert(parameters[0], Object.class);
    129                if (target != null) {
    130                    Method method =
    131                        MethodLookupUtils.lookupMethod(
    132                            target.getClass(),
    133                            name,
    134                            parameters);
    135                    if (method != null) {
    136                        return new MethodFunction(method);
    137                    }
    138
    139                    if (target instanceof NodeSet) {
    140                        target = ((NodeSet) target).getPointers();
    141                    }
    142
    143                    method =
    144                        MethodLookupUtils.lookupMethod(
    145                            target.getClass(),
    146                            name,
    147                            parameters);
    148                    if (method != null) {
    149                        return new MethodFunction(method);
    150                    }
    151
    152                    if (target instanceof Collection) {
    153                        Iterator iter = ((Collection) target).iterator();
    154                        if (iter.hasNext()) {
    155                            target = iter.next();
    156                            if (target instanceof Pointer) {
    157                                target = ((Pointer) target).getValue();
    158                            }
    159                        }
    160                        else {
    161                            target = null;
    162                        }
    163                    }
    164                }
    165                if (target != null) {
    166                    Method method =
    167                        MethodLookupUtils.lookupMethod(
    168                            target.getClass(),
    169                            name,
    170                            parameters);
    171                    if (method != null) {
    172                        return new MethodFunction(method);
    173                    }
    174                }
    175            }
    176
    177            String fullName = classPrefix + name;
    178            int inx = fullName.lastIndexOf('.');
    179            if (inx == -1) {
    180                return null;
    181            }
    182
    183            String className = fullName.substring(0, inx);
    184            String methodName = fullName.substring(inx + 1);
    185
    186            Class functionClass;
    187            try {
    188                functionClass = Class.forName(className);
    189            }
    190            catch (ClassNotFoundException ex) {
    191                throw new JXPathException(
    192                    "Cannot invoke extension function "
    193                        + (namespace != null ? namespace + ":" + name : name),
    194                    ex);
    195            }
    196
    197            if (methodName.equals("new")) {
    198                Constructor constructor =
    199                    MethodLookupUtils.lookupConstructor(functionClass, parameters);
    200                if (constructor != null) {
    201                    return new ConstructorFunction(constructor);
    202                }
    203            }
    204            else {
    205                Method method =
    206                    MethodLookupUtils.lookupStaticMethod(
    207                        functionClass,
    208                        methodName,
    209                        parameters);
    210                if (method != null) {
    211                    return new MethodFunction(method);
    212                }
    213            }
    214            return null;
    215        }
    216    }