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