1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 }