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