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
18 package org.apache.commons.jxpath;
19
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Method;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.Objects;
26 import java.util.Set;
27
28 import org.apache.commons.jxpath.functions.ConstructorFunction;
29 import org.apache.commons.jxpath.functions.MethodFunction;
30 import org.apache.commons.jxpath.util.ClassLoaderUtil;
31 import org.apache.commons.jxpath.util.MethodLookupUtils;
32 import org.apache.commons.jxpath.util.TypeUtils;
33
34 /**
35 * Extension functions provided by Java classes. The class prefix specified in the constructor is used when a constructor or a static method is called. Usually,
36 * a class prefix is a package name (hence the name of this class).
37 *
38 * Let's say, we declared a PackageFunction like this: <blockquote>
39 *
40 * <pre>
41 * new PackageFunctions("java.util.", "util")
42 * </pre>
43 *
44 * </blockquote>
45 *
46 * We can now use XPaths like:
47 * <dl>
48 * <dt>{@code "util:Date.new()"}</dt>
49 * <dd>Equivalent to {@code new java.util.Date()}</dd>
50 * <dt>{@code "util:Collections.singleton('foo')"}</dt>
51 * <dd>Equivalent to {@code java.util.Collections.singleton("foo")}</dd>
52 * <dt>{@code "util:substring('foo', 1, 2)"}</dt>
53 * <dd>Equivalent to {@code "foo".substring(1, 2)}. Note that in this case, the class prefix is not used. JXPath does not check that the first parameter of the
54 * function (the method target) is in fact a member of the package described by this PackageFunctions object.</dd>
55 * </dl>
56 *
57 * <p>
58 * If the first argument of a method or constructor is {@link ExpressionContext}, the expression context in which the function is evaluated is passed to the
59 * method.
60 * </p>
61 * <p>
62 * There is one PackageFunctions object registered by default with each JXPathContext. It does not have a namespace and uses no class prefix. The existence of
63 * this object allows us to use XPaths like: {@code "java.util.Date.new()"} and {@code "length('foo')"} without the explicit registration of any extension
64 * functions.
65 * </p>
66 */
67 public class PackageFunctions implements Functions {
68
69 private static final Object[] EMPTY_ARRAY = {};
70 private final String classPrefix;
71 private final String namespace;
72
73 /**
74 * Constructs a new PackageFunctions.
75 *
76 * @param classPrefix class prefix
77 * @param namespace namespace String
78 */
79 public PackageFunctions(final String classPrefix, final String namespace) {
80 this.classPrefix = classPrefix;
81 this.namespace = namespace;
82 }
83
84 /**
85 * Returns a {@link Function}, if found, for the specified namespace, name and parameter types.
86 *
87 * @param namespace - if it is not the same as specified in the construction, this method returns null
88 * @param name - name of the method, which can one these forms:
89 * <ul>
90 * <li><strong>methodname</strong>, if invoking a method on an object passed as the first parameter</li>
91 * <li><strong>Classname.new</strong>, if looking for a constructor</li>
92 * <li><strong>subpackage.subpackage.Classname.new</strong>, if looking for a constructor in a subpackage</li>
93 * <li><strong>Classname.methodname</strong>, if looking for a static method</li>
94 * <li><strong>subpackage.subpackage.Classname.methodname</strong>, if looking for a static method of a class in a subpackage</li>
95 * </ul>
96 * @param parameters Object[] of parameters
97 * @return a MethodFunction, a ConstructorFunction or null if no function is found
98 */
99 @Override
100 public Function getFunction(final String namespace, final String name, Object[] parameters) {
101 if (!Objects.equals(this.namespace, namespace)) {
102 return null;
103 }
104 if (parameters == null) {
105 parameters = EMPTY_ARRAY;
106 }
107 if (parameters.length >= 1) {
108 Object target = TypeUtils.convert(parameters[0], Object.class);
109 if (target != null) {
110 Method method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
111 if (method != null) {
112 return new MethodFunction(method);
113 }
114 if (target instanceof NodeSet) {
115 target = ((NodeSet) target).getPointers();
116 }
117 method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
118 if (method != null) {
119 return new MethodFunction(method);
120 }
121 if (target instanceof Collection) {
122 final Iterator iter = ((Collection) target).iterator();
123 if (iter.hasNext()) {
124 target = iter.next();
125 if (target instanceof Pointer) {
126 target = ((Pointer) target).getValue();
127 }
128 } else {
129 target = null;
130 }
131 }
132 }
133 if (target != null) {
134 final Method method = MethodLookupUtils.lookupMethod(target.getClass(), name, parameters);
135 if (method != null) {
136 return new MethodFunction(method);
137 }
138 }
139 }
140 final String fullName = classPrefix + name;
141 final int inx = fullName.lastIndexOf('.');
142 if (inx == -1) {
143 return null;
144 }
145 final String className = fullName.substring(0, inx);
146 final String methodName = fullName.substring(inx + 1);
147 Class<?> functionClass;
148 try {
149 functionClass = ClassLoaderUtil.getClass(className, true);
150 } catch (final ClassNotFoundException ex) {
151 throw new JXPathException("Cannot invoke extension function " + (namespace != null ? namespace + ":" + name : name), ex);
152 }
153 if (methodName.equals("new")) {
154 final Constructor constructor = MethodLookupUtils.lookupConstructor(functionClass, parameters);
155 if (constructor != null) {
156 return new ConstructorFunction(constructor);
157 }
158 } else {
159 final Method method = MethodLookupUtils.lookupStaticMethod(functionClass, methodName, parameters);
160 if (method != null) {
161 return new MethodFunction(method);
162 }
163 }
164 return null;
165 }
166
167 /**
168 * Returns the namespace specified in the constructor
169 *
170 * @return (singleton) namespace Set
171 */
172 @Override
173 public Set<String> getUsedNamespaces() {
174 return Collections.singleton(namespace);
175 }
176 }