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    }