View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jxpath;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Method;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.Set;
25  
26  import org.apache.commons.jxpath.functions.ConstructorFunction;
27  import org.apache.commons.jxpath.functions.MethodFunction;
28  import org.apache.commons.jxpath.util.ClassLoaderUtil;
29  import org.apache.commons.jxpath.util.MethodLookupUtils;
30  import org.apache.commons.jxpath.util.TypeUtils;
31  
32  /**
33   * Extension functions provided by Java classes.  The class prefix specified
34   * in the constructor is used when a constructor or a static method is called.
35   * Usually, a class prefix is a package name (hence the name of this class).
36   *
37   * Let's say, we declared a PackageFunction like this:
38   * <blockquote><pre>
39   *     new PackageFunctions("java.util.", "util")
40   * </pre></blockquote>
41   *
42   * We can now use XPaths like:
43   * <dl>
44   *  <dt><code>"util:Date.new()"</code></dt>
45   *  <dd>Equivalent to <code>new java.util.Date()</code></dd>
46   *  <dt><code>"util:Collections.singleton('foo')"</code></dt>
47   *  <dd>Equivalent to <code>java.util.Collections.singleton("foo")</code></dd>
48   *  <dt><code>"util:substring('foo', 1, 2)"</code></dt>
49   *  <dd>Equivalent to <code>"foo".substring(1, 2)</code>.  Note that in
50   *  this case, the class prefix is not used. JXPath does not check that
51   *  the first parameter of the function (the method target) is in fact
52   *  a member of the package described by this PackageFunctions object.</dd>
53   * </dl>
54   *
55   * <p>
56   * If the first argument of a method or constructor is {@link ExpressionContext},
57   * the expression context in which the function is evaluated is passed to
58   * the method.
59   * </p>
60   * <p>
61   * There is one PackageFunctions object registered by default with each
62   * JXPathContext.  It does not have a namespace and uses no class prefix.
63   * The existence of this object allows us to use XPaths like:
64   * <code>"java.util.Date.new()"</code> and <code>"length('foo')"</code>
65   * without the explicit registration of any extension functions.
66   * </p>
67   *
68   * @author Dmitri Plotnikov
69   * @version $Revision: 916559 $ $Date: 2010-02-26 04:55:46 +0100 (Fr, 26 Feb 2010) $
70   */
71  public class PackageFunctions implements Functions {
72      private String classPrefix;
73      private String namespace;
74      private static final Object[] EMPTY_ARRAY = new Object[0];
75  
76      /**
77       * Create a new PackageFunctions.
78       * @param classPrefix class prefix
79       * @param namespace namespace String
80       */
81      public PackageFunctions(String classPrefix, String namespace) {
82          this.classPrefix = classPrefix;
83          this.namespace = namespace;
84      }
85  
86      /**
87       * Returns the namespace specified in the constructor
88       * @return (singleton) namespace Set
89       */
90      public Set getUsedNamespaces() {
91          return Collections.singleton(namespace);
92      }
93  
94      /**
95       * Returns a {@link Function}, if found, for the specified namespace,
96       * name and parameter types.
97       * <p>
98       * @param  namespace - if it is not the same as specified in the
99       * 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 }