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 }