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