View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jxpath;
17  
18  import java.lang.reflect.Constructor;
19  import java.lang.reflect.Method;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.Set;
24  
25  import org.apache.commons.jxpath.functions.ConstructorFunction;
26  import org.apache.commons.jxpath.functions.MethodFunction;
27  import org.apache.commons.jxpath.util.MethodLookupUtils;
28  import org.apache.commons.jxpath.util.TypeUtils;
29  
30  /***
31   * Extension functions provided by Java classes.  The class prefix specified
32   * in the constructor is used when a constructor or a static method is called.
33   * Usually, a class prefix is a package name (hence the name of this class).
34   *
35   * Let's say, we declared a PackageFunction like this:
36   * <blockquote><pre>
37   *     new PackageFunctions("java.util.", "util")
38   * </pre></blockquote>
39   *
40   * We can now use XPaths like:
41   * <dl>
42   *  <dt><code>"util:Date.new()"</code></dt>
43   *  <dd>Equivalent to <code>new java.util.Date()</code></dd>
44   *  <dt><code>"util:Collections.singleton('foo')"</code></dt>
45   *  <dd>Equivalent to <code>java.util.Collections.singleton("foo")</code></dd>
46   *  <dt><code>"util:substring('foo', 1, 2)"</code></dt>
47   *  <dd>Equivalent to <code>"foo".substring(1, 2)</code>.  Note that in
48   *  this case, the class prefix is not used. JXPath does not check that
49   *  the first parameter of the function (the method target) is in fact
50   *  a member of the package described by this PackageFunctions object.</dd>
51   * </dl>
52   *
53   * <p>
54   * If the first argument of a method or constructor is ExpressionContext, the
55   * expression context in which the function is evaluated is passed to
56   * the method.
57   * </p>
58   * <p>
59   * There is one PackageFunctions object registered by default with each
60   * JXPathContext.  It does not have a namespace and uses no class prefix.
61   * The existence of this object allows us to use XPaths like:
62   * <code>"java.util.Date.new()"</code> and <code>"length('foo')"</code>
63   * without the explicit registration of any extension functions.
64   * </p>
65  
66   *
67   * @author Dmitri Plotnikov
68   * @version $Revision: 1.14 $ $Date: 2004/04/04 23:16:23 $
69   */
70  public class PackageFunctions implements Functions {
71      private String classPrefix;
72      private String namespace;
73      private static final Object[] EMPTY_ARRAY = new Object[0];
74  
75      public PackageFunctions(String classPrefix, String namespace) {
76          this.classPrefix = classPrefix;
77          this.namespace = namespace;
78      }
79  
80      /***
81       * Returns the namespace specified in the constructor
82       */
83      public Set getUsedNamespaces() {
84          return Collections.singleton(namespace);
85      }
86  
87      /***
88       * Returns a Function, if any, for the specified namespace,
89       * name and parameter types.
90       * <p>
91       * @param  namespace - if it is not the same as specified in the
92       * construction, this method returns null
93       * @param name - name of the method, which can one these forms:
94       * <ul>
95       * <li><b>methodname</b>, if invoking a method on an object passed as the
96       * first parameter</li>
97       * <li><b>Classname.new</b>, if looking for a constructor</li>
98       * <li><b>subpackage.subpackage.Classname.new</b>, if looking for a
99       * constructor in a subpackage</li>
100      * <li><b>Classname.methodname</b>, if looking for a static method</li>
101      * <li><b>subpackage.subpackage.Classname.methodname</b>, if looking for a
102      * static method of a class in a subpackage</li>
103      * </ul>
104      *
105      * @return  a MethodFunction, a ConstructorFunction or null if no function
106      * is found
107      */
108     public Function getFunction(
109         String namespace,
110         String name,
111         Object[] parameters) 
112     {
113         if ((namespace == null && this.namespace != null)
114             || (namespace != null && !namespace.equals(this.namespace))) {
115             return null;
116         }
117 
118         if (parameters == null) {
119             parameters = EMPTY_ARRAY;
120         }
121 
122         if (parameters.length >= 1) {
123             Object target = TypeUtils.convert(parameters[0], Object.class);
124             if (target != null) {
125                 Method method =
126                     MethodLookupUtils.lookupMethod(
127                         target.getClass(),
128                         name,
129                         parameters);
130                 if (method != null) {
131                     return new MethodFunction(method);
132                 }
133                     
134                 if (target instanceof NodeSet) {
135                     target = ((NodeSet) target).getPointers();
136                 }
137                 
138                 method =
139                     MethodLookupUtils.lookupMethod(
140                         target.getClass(),
141                         name,
142                         parameters);
143                 if (method != null) {
144                     return new MethodFunction(method);
145                 }
146                 
147                 if (target instanceof Collection) {
148                     Iterator iter = ((Collection) target).iterator();
149                     if (iter.hasNext()) {
150                         target = iter.next();
151                         if (target instanceof Pointer) {
152                             target = ((Pointer) target).getValue();
153                         }
154                     }
155                     else {
156                         target = null;
157                     }
158                 }
159             }
160             if (target != null) {
161                 Method method =
162                     MethodLookupUtils.lookupMethod(
163                         target.getClass(),
164                         name,
165                         parameters);
166                 if (method != null) {
167                     return new MethodFunction(method);
168                 }
169             }
170         }
171 
172         String fullName = classPrefix + name;
173         int inx = fullName.lastIndexOf('.');
174         if (inx == -1) {
175             return null;
176         }
177 
178         String className = fullName.substring(0, inx);
179         String methodName = fullName.substring(inx + 1);
180 
181         Class functionClass;
182         try {
183             functionClass = Class.forName(className);
184         }
185         catch (ClassNotFoundException ex) {
186             throw new JXPathException(
187                 "Cannot invoke extension function "
188                     + (namespace != null ? namespace + ":" + name : name),
189                 ex);
190         }
191 
192         if (methodName.equals("new")) {
193             Constructor constructor =
194                 MethodLookupUtils.lookupConstructor(functionClass, parameters);
195             if (constructor != null) {
196                 return new ConstructorFunction(constructor);
197             }
198         }
199         else {
200             Method method =
201                 MethodLookupUtils.lookupStaticMethod(
202                     functionClass,
203                     methodName,
204                     parameters);
205             if (method != null) {
206                 return new MethodFunction(method);
207             }
208         }
209         return null;
210     }
211 }