View Javadoc

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 }