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.util;
19  
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.util.Arrays;
24  
25  import org.apache.commons.jxpath.ExpressionContext;
26  import org.apache.commons.jxpath.JXPathException;
27  
28  /**
29   * Method lookup utilities, which find static and non-static methods as well as constructors based on a name and list of parameters.
30   */
31  public class MethodLookupUtils {
32  
33      private static final int NO_MATCH = 0;
34      private static final int APPROXIMATE_MATCH = 1;
35      private static final int EXACT_MATCH = 2;
36  
37      /**
38       * Look up a constructor.
39       *
40       * @param targetClass the class constructed
41       * @param parameters  arguments
42       * @return Constructor found if any.
43       */
44      public static Constructor lookupConstructor(final Class targetClass, final Object[] parameters) {
45          boolean tryExact = true;
46          final int count = parameters == null ? 0 : parameters.length;
47          final Class[] types = new Class[count];
48          for (int i = 0; i < count; i++) {
49              final Object param = parameters[i];
50              if (param != null) {
51                  types[i] = param.getClass();
52              } else {
53                  types[i] = null;
54                  tryExact = false;
55              }
56          }
57          Constructor constructor = null;
58          if (tryExact) {
59              // First - without type conversion
60              try {
61                  constructor = targetClass.getConstructor(types);
62                  if (constructor != null) {
63                      return constructor;
64                  }
65              } catch (final NoSuchMethodException ignore) { // NOPMD
66                  // Ignore
67              }
68          }
69          int currentMatch = 0;
70          boolean ambiguous = false;
71          // Then - with type conversion
72          final Constructor[] constructors = targetClass.getConstructors();
73          for (final Constructor constructor2 : constructors) {
74              final int match = matchParameterTypes(constructor2.getParameterTypes(), parameters);
75              if (match != NO_MATCH) {
76                  if (match > currentMatch) {
77                      constructor = constructor2;
78                      currentMatch = match;
79                      ambiguous = false;
80                  } else if (match == currentMatch) {
81                      ambiguous = true;
82                  }
83              }
84          }
85          if (ambiguous) {
86              throw new JXPathException("Ambiguous constructor " + Arrays.asList(parameters));
87          }
88          return constructor;
89      }
90  
91      /**
92       * Look up a method.
93       *
94       * @param targetClass owning class
95       * @param name        method name
96       * @param parameters  method parameters
97       * @return Method found if any
98       */
99      public static Method lookupMethod(Class targetClass, final String name, final Object[] parameters) {
100         if (parameters == null || parameters.length < 1 || parameters[0] == null) {
101             return null;
102         }
103         if (matchType(targetClass, parameters[0]) == NO_MATCH) {
104             return null;
105         }
106         targetClass = TypeUtils.convert(parameters[0], targetClass).getClass();
107         boolean tryExact = true;
108         final int count = parameters.length - 1;
109         final Class[] types = new Class[count];
110         final Object[] arguments = new Object[count];
111         for (int i = 0; i < count; i++) {
112             final Object param = parameters[i + 1];
113             arguments[i] = param;
114             if (param != null) {
115                 types[i] = param.getClass();
116             } else {
117                 types[i] = null;
118                 tryExact = false;
119             }
120         }
121         Method method = null;
122         if (tryExact) {
123             // First - without type conversion
124             try {
125                 method = targetClass.getMethod(name, types);
126                 if (method != null && !Modifier.isStatic(method.getModifiers())) {
127                     return method;
128                 }
129             } catch (final NoSuchMethodException ignore) { // NOPMD
130                 // Ignore
131             }
132         }
133         int currentMatch = 0;
134         boolean ambiguous = false;
135         // Then - with type conversion
136         final Method[] methods = targetClass.getMethods();
137         for (final Method method2 : methods) {
138             if (!Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) {
139                 final int match = matchParameterTypes(method2.getParameterTypes(), arguments);
140                 if (match != NO_MATCH) {
141                     if (match > currentMatch) {
142                         method = method2;
143                         currentMatch = match;
144                         ambiguous = false;
145                     } else if (match == currentMatch) {
146                         ambiguous = true;
147                     }
148                 }
149             }
150         }
151         if (ambiguous) {
152             throw new JXPathException("Ambiguous method call: " + name);
153         }
154         return method;
155     }
156 
157     /**
158      * Look up a static method.
159      *
160      * @param targetClass the owning class
161      * @param name        method name
162      * @param parameters  method parameters
163      * @return Method found if any
164      */
165     public static Method lookupStaticMethod(final Class targetClass, final String name, final Object[] parameters) {
166         boolean tryExact = true;
167         final int count = parameters == null ? 0 : parameters.length;
168         final Class[] types = new Class[count];
169         for (int i = 0; i < count; i++) {
170             final Object param = parameters[i];
171             if (param != null) {
172                 types[i] = param.getClass();
173             } else {
174                 types[i] = null;
175                 tryExact = false;
176             }
177         }
178         Method method = null;
179         if (tryExact) {
180             // First - without type conversion
181             try {
182                 method = targetClass.getMethod(name, types);
183                 if (method != null && Modifier.isStatic(method.getModifiers())) {
184                     return method;
185                 }
186             } catch (final NoSuchMethodException ignore) { // NOPMD
187                 // Ignore
188             }
189         }
190         int currentMatch = 0;
191         boolean ambiguous = false;
192         // Then - with type conversion
193         final Method[] methods = targetClass.getMethods();
194         for (final Method method2 : methods) {
195             if (Modifier.isStatic(method2.getModifiers()) && method2.getName().equals(name)) {
196                 final int match = matchParameterTypes(method2.getParameterTypes(), parameters);
197                 if (match != NO_MATCH) {
198                     if (match > currentMatch) {
199                         method = method2;
200                         currentMatch = match;
201                         ambiguous = false;
202                     } else if (match == currentMatch) {
203                         ambiguous = true;
204                     }
205                 }
206             }
207         }
208         if (ambiguous) {
209             throw new JXPathException("Ambiguous method call: " + name);
210         }
211         return method;
212     }
213 
214     /**
215      * Return a match code of objects to types.
216      *
217      * @param types      Class[] of expected types
218      * @param parameters Object[] to attempt to match
219      * @return int code
220      */
221     private static int matchParameterTypes(final Class[] types, final Object[] parameters) {
222         int pi = 0;
223         if (types.length >= 1 && ExpressionContext.class.isAssignableFrom(types[0])) {
224             pi++;
225         }
226         final int length = parameters == null ? 0 : parameters.length;
227         if (types.length != length + pi) {
228             return NO_MATCH;
229         }
230         int totalMatch = EXACT_MATCH;
231         for (int i = 0; i < length; i++) {
232             final int match = matchType(types[i + pi], parameters[i]);
233             if (match == NO_MATCH) {
234                 return NO_MATCH;
235             }
236             if (match < totalMatch) {
237                 totalMatch = match;
238             }
239         }
240         return totalMatch;
241     }
242 
243     /**
244      * Return a match code between an object and type.
245      *
246      * @param expected class to test
247      * @param object   object to test
248      * @return int code
249      */
250     private static int matchType(final Class expected, final Object object) {
251         if (object == null) {
252             return APPROXIMATE_MATCH;
253         }
254         final Class actual = object.getClass();
255         if (expected.equals(actual)) {
256             return EXACT_MATCH;
257         }
258         if (expected.isAssignableFrom(actual)) {
259             return EXACT_MATCH;
260         }
261         if (TypeUtils.canConvert(object, expected)) {
262             return APPROXIMATE_MATCH;
263         }
264         return NO_MATCH;
265     }
266 
267     /**
268      * Constructs a new instance.
269      *
270      * @deprecated Will be private in the next major version.
271      */
272     @Deprecated
273     public MethodLookupUtils() {
274         // empty
275     }
276 }