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