1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl.util.introspection;
18
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.Hashtable;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26
27 /***
28 *
29 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
30 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
31 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
32 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
33 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
34 * @version $Id: MethodMap.java,v 1.5 2004/08/19 17:15:59 dion Exp $
35 */
36 public class MethodMap
37 {
38 private static final int MORE_SPECIFIC = 0;
39 private static final int LESS_SPECIFIC = 1;
40 private static final int INCOMPARABLE = 2;
41
42 /***
43 * Keep track of all methods with the same name.
44 */
45 Map methodByNameMap = new Hashtable();
46
47 /***
48 * Add a method to a list of methods by name.
49 * For a particular class we are keeping track
50 * of all the methods with the same name.
51 */
52 public void add(Method method)
53 {
54 String methodName = method.getName();
55
56 List l = get( methodName );
57
58 if ( l == null)
59 {
60 l = new ArrayList();
61 methodByNameMap.put(methodName, l);
62 }
63
64 l.add(method);
65 }
66
67 /***
68 * Return a list of methods with the same name.
69 *
70 * @param String key
71 * @return List list of methods
72 */
73 public List get(String key)
74 {
75 return (List) methodByNameMap.get(key);
76 }
77
78 /***
79 * <p>
80 * Find a method. Attempts to find the
81 * most specific applicable method using the
82 * algorithm described in the JLS section
83 * 15.12.2 (with the exception that it can't
84 * distinguish a primitive type argument from
85 * an object type argument, since in reflection
86 * primitive type arguments are represented by
87 * their object counterparts, so for an argument of
88 * type (say) java.lang.Integer, it will not be able
89 * to decide between a method that takes int and a
90 * method that takes java.lang.Integer as a parameter.
91 * </p>
92 *
93 * <p>
94 * This turns out to be a relatively rare case
95 * where this is needed - however, functionality
96 * like this is needed.
97 * </p>
98 *
99 * @param methodName name of method
100 * @param args the actual arguments with which the method is called
101 * @return the most specific applicable method, or null if no
102 * method is applicable.
103 * @throws AmbiguousException if there is more than one maximally
104 * specific applicable method
105 */
106 public Method find(String methodName, Object[] args)
107 throws AmbiguousException
108 {
109 List methodList = get(methodName);
110
111 if (methodList == null)
112 {
113 return null;
114 }
115
116 int l = args.length;
117 Class[] classes = new Class[l];
118
119 for(int i = 0; i < l; ++i)
120 {
121 Object arg = args[i];
122
123
124
125
126
127 classes[i] =
128 arg == null ? null : arg.getClass();
129 }
130
131 return getMostSpecific(methodList, classes);
132 }
133
134 /***
135 * simple distinguishable exception, used when
136 * we run across ambiguous overloading
137 */
138 public static class AmbiguousException extends Exception
139 {
140 }
141
142
143 private static Method getMostSpecific(List methods, Class[] classes)
144 throws AmbiguousException
145 {
146 LinkedList applicables = getApplicables(methods, classes);
147
148 if(applicables.isEmpty())
149 {
150 return null;
151 }
152
153 if(applicables.size() == 1)
154 {
155 return (Method)applicables.getFirst();
156 }
157
158
159
160
161
162
163
164 LinkedList maximals = new LinkedList();
165
166 for (Iterator applicable = applicables.iterator();
167 applicable.hasNext();)
168 {
169 Method app = (Method) applicable.next();
170 Class[] appArgs = app.getParameterTypes();
171 boolean lessSpecific = false;
172
173 for (Iterator maximal = maximals.iterator();
174 !lessSpecific && maximal.hasNext();)
175 {
176 Method max = (Method) maximal.next();
177
178 switch(moreSpecific(appArgs, max.getParameterTypes()))
179 {
180 case MORE_SPECIFIC:
181 {
182
183
184
185
186
187 maximal.remove();
188 break;
189 }
190
191 case LESS_SPECIFIC:
192 {
193
194
195
196
197
198
199
200 lessSpecific = true;
201 break;
202 }
203 }
204 }
205
206 if(!lessSpecific)
207 {
208 maximals.addLast(app);
209 }
210 }
211
212 if(maximals.size() > 1)
213 {
214
215 throw new AmbiguousException();
216 }
217
218 return (Method)maximals.getFirst();
219 }
220
221 /***
222 * Determines which method signature (represented by a class array) is more
223 * specific. This defines a partial ordering on the method signatures.
224 * @param c1 first signature to compare
225 * @param c2 second signature to compare
226 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
227 * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
228 */
229 private static int moreSpecific(Class[] c1, Class[] c2)
230 {
231 boolean c1MoreSpecific = false;
232 boolean c2MoreSpecific = false;
233
234 for(int i = 0; i < c1.length; ++i)
235 {
236 if(c1[i] != c2[i])
237 {
238 c1MoreSpecific =
239 c1MoreSpecific ||
240 isStrictMethodInvocationConvertible(c2[i], c1[i]);
241 c2MoreSpecific =
242 c2MoreSpecific ||
243 isStrictMethodInvocationConvertible(c1[i], c2[i]);
244 }
245 }
246
247 if(c1MoreSpecific)
248 {
249 if(c2MoreSpecific)
250 {
251
252
253
254
255
256 return INCOMPARABLE;
257 }
258
259 return MORE_SPECIFIC;
260 }
261
262 if(c2MoreSpecific)
263 {
264 return LESS_SPECIFIC;
265 }
266
267
268
269
270
271
272 return INCOMPARABLE;
273 }
274
275 /***
276 * Returns all methods that are applicable to actual argument types.
277 * @param methods list of all candidate methods
278 * @param classes the actual types of the arguments
279 * @return a list that contains only applicable methods (number of
280 * formal and actual arguments matches, and argument types are assignable
281 * to formal types through a method invocation conversion).
282 */
283 private static LinkedList getApplicables(List methods, Class[] classes)
284 {
285 LinkedList list = new LinkedList();
286
287 for (Iterator imethod = methods.iterator(); imethod.hasNext();)
288 {
289 Method method = (Method) imethod.next();
290
291 if(isApplicable(method, classes))
292 {
293 list.add(method);
294 }
295
296 }
297 return list;
298 }
299
300 /***
301 * Returns true if the supplied method is applicable to actual
302 * argument types.
303 */
304 private static boolean isApplicable(Method method, Class[] classes)
305 {
306 Class[] methodArgs = method.getParameterTypes();
307
308 if(methodArgs.length != classes.length)
309 {
310 return false;
311 }
312
313 for(int i = 0; i < classes.length; ++i)
314 {
315 if(!isMethodInvocationConvertible(methodArgs[i], classes[i]))
316 {
317 return false;
318 }
319 }
320
321 return true;
322 }
323
324 /***
325 * Determines whether a type represented by a class object is
326 * convertible to another type represented by a class object using a
327 * method invocation conversion, treating object types of primitive
328 * types as if they were primitive types (that is, a Boolean actual
329 * parameter type matches boolean primitive formal type). This behavior
330 * is because this method is used to determine applicable methods for
331 * an actual parameter list, and primitive types are represented by
332 * their object duals in reflective method calls.
333 *
334 * @param formal the formal parameter type to which the actual
335 * parameter type should be convertible
336 * @param actual the actual parameter type.
337 * @return true if either formal type is assignable from actual type,
338 * or formal is a primitive type and actual is its corresponding object
339 * type or an object type of a primitive type that can be converted to
340 * the formal type.
341 */
342 private static boolean isMethodInvocationConvertible(Class formal,
343 Class actual)
344 {
345
346
347
348 if (actual == null && !formal.isPrimitive())
349 {
350 return true;
351 }
352
353
354
355
356
357 if (actual != null && formal.isAssignableFrom(actual))
358 {
359 return true;
360 }
361
362
363
364
365
366
367 if (formal.isPrimitive())
368 {
369 if(formal == Boolean.TYPE && actual == Boolean.class)
370 return true;
371 if(formal == Character.TYPE && actual == Character.class)
372 return true;
373 if(formal == Byte.TYPE && actual == Byte.class)
374 return true;
375 if(formal == Short.TYPE &&
376 (actual == Short.class || actual == Byte.class))
377 return true;
378 if(formal == Integer.TYPE &&
379 (actual == Integer.class || actual == Short.class ||
380 actual == Byte.class))
381 return true;
382 if(formal == Long.TYPE &&
383 (actual == Long.class || actual == Integer.class ||
384 actual == Short.class || actual == Byte.class))
385 return true;
386 if(formal == Float.TYPE &&
387 (actual == Float.class || actual == Long.class ||
388 actual == Integer.class || actual == Short.class ||
389 actual == Byte.class))
390 return true;
391 if(formal == Double.TYPE &&
392 (actual == Double.class || actual == Float.class ||
393 actual == Long.class || actual == Integer.class ||
394 actual == Short.class || actual == Byte.class))
395 return true;
396 }
397
398 return false;
399 }
400
401 /***
402 * Determines whether a type represented by a class object is
403 * convertible to another type represented by a class object using a
404 * method invocation conversion, without matching object and primitive
405 * types. This method is used to determine the more specific type when
406 * comparing signatures of methods.
407 *
408 * @param formal the formal parameter type to which the actual
409 * parameter type should be convertible
410 * @param actual the actual parameter type.
411 * @return true if either formal type is assignable from actual type,
412 * or formal and actual are both primitive types and actual can be
413 * subject to widening conversion to formal.
414 */
415 private static boolean isStrictMethodInvocationConvertible(Class formal,
416 Class actual)
417 {
418
419
420
421 if (actual == null && !formal.isPrimitive())
422 {
423 return true;
424 }
425
426
427
428
429
430 if(formal.isAssignableFrom(actual))
431 {
432 return true;
433 }
434
435
436
437
438
439 if(formal.isPrimitive())
440 {
441 if(formal == Short.TYPE && (actual == Byte.TYPE))
442 return true;
443 if(formal == Integer.TYPE &&
444 (actual == Short.TYPE || actual == Byte.TYPE))
445 return true;
446 if(formal == Long.TYPE &&
447 (actual == Integer.TYPE || actual == Short.TYPE ||
448 actual == Byte.TYPE))
449 return true;
450 if(formal == Float.TYPE &&
451 (actual == Long.TYPE || actual == Integer.TYPE ||
452 actual == Short.TYPE || actual == Byte.TYPE))
453 return true;
454 if(formal == Double.TYPE &&
455 (actual == Float.TYPE || actual == Long.TYPE ||
456 actual == Integer.TYPE || actual == Short.TYPE ||
457 actual == Byte.TYPE))
458 return true;
459 }
460 return false;
461 }
462 }