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-25 22:55:46 -0500 (Thu, 25 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 }