View Javadoc
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 }