View Javadoc

1   /*
2    * Copyright 2001,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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              * if we are careful down below, a null argument goes in there
125              * so we can know that the null was passed to the method
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          * This list will contain the maximally specific methods. Hopefully at
160          * the end of the below loop, the list will contain exactly one method,
161          * (the most specific method) otherwise we have ambiguity.
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                          * This method is more specific than the previously
184                          * known maximally specific, so remove the old maximum.
185                          */
186 
187                         maximal.remove();
188                         break;
189                     }
190 
191                     case LESS_SPECIFIC:
192                     {
193                         /*
194                          * This method is less specific than some of the
195                          * currently known maximally specific methods, so we
196                          * won't add it into the set of maximally specific
197                          * methods
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             // We have more than one maximally specific method
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                  *  Incomparable due to cross-assignable arguments (i.e.
253                  * foo(String, Object) vs. foo(Object, String))
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          * Incomparable due to non-related arguments (i.e.
269          * foo(Runnable) vs. foo(Serializable))
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          * if it's a null, it means the arg was null
347          */
348         if (actual == null && !formal.isPrimitive())
349         {
350             return true;
351         }
352 
353         /*
354          *  Check for identity or widening reference conversion
355          */
356 
357         if (actual != null && formal.isAssignableFrom(actual))
358         {
359             return true;
360         }
361 
362         /*
363          * Check for boxing with widening primitive conversion. Note that
364          * actual parameters are never primitives.
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          * we shouldn't get a null into, but if so
420          */
421         if (actual == null && !formal.isPrimitive())
422         {
423             return true;
424         }
425 
426         /*
427          *  Check for identity or widening reference conversion
428          */
429 
430         if(formal.isAssignableFrom(actual))
431         {
432             return true;
433         }
434 
435         /*
436          *  Check for widening primitive conversion.
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 }