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.MethodLookupUtils;
029import 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 */
070public 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}