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.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  
29  /**
30   *
31   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
32   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
33   * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
34   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
35   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
36   * @version $Id: MethodMap.java 928998 2010-03-30 05:37:17Z nbubna $
37   */
38  public class MethodMap
39  {
40      private static final int MORE_SPECIFIC = 0;
41      private static final int LESS_SPECIFIC = 1;
42      private static final int INCOMPARABLE = 2;
43  
44      /**
45       * Keep track of all methods with the same name.
46       */
47      Map methodByNameMap = new ConcurrentHashMap();
48  
49      /**
50       * Add a method to a list of methods by name.
51       * For a particular class we are keeping track
52       * of all the methods with the same name.
53       * @param method
54       */
55      public void add(Method method)
56      {
57          String methodName = method.getName();
58  
59          List l = get( methodName );
60  
61          if ( l == null)
62          {
63              l = new ArrayList();
64              methodByNameMap.put(methodName, l);
65          }
66  
67          l.add(method);
68      }
69  
70      /**
71       * Return a list of methods with the same name.
72       *
73       * @param key
74       * @return List list of methods
75       */
76      public List get(String key)
77      {
78          return (List) methodByNameMap.get(key);
79      }
80  
81      /**
82       *  <p>
83       *  Find a method.  Attempts to find the
84       *  most specific applicable method using the
85       *  algorithm described in the JLS section
86       *  15.12.2 (with the exception that it can't
87       *  distinguish a primitive type argument from
88       *  an object type argument, since in reflection
89       *  primitive type arguments are represented by
90       *  their object counterparts, so for an argument of
91       *  type (say) java.lang.Integer, it will not be able
92       *  to decide between a method that takes int and a
93       *  method that takes java.lang.Integer as a parameter.
94       *  </p>
95       *
96       *  <p>
97       *  This turns out to be a relatively rare case
98       *  where this is needed - however, functionality
99       *  like this is needed.
100      *  </p>
101      *
102      *  @param methodName name of method
103      *  @param args the actual arguments with which the method is called
104      *  @return the most specific applicable method, or null if no
105      *  method is applicable.
106      *  @throws AmbiguousException if there is more than one maximally
107      *  specific applicable method
108      */
109     public Method find(String methodName, Object[] args)
110         throws AmbiguousException
111     {
112         List methodList = get(methodName);
113 
114         if (methodList == null)
115         {
116             return null;
117         }
118 
119         int l = args.length;
120         Class[] classes = new Class[l];
121 
122         for(int i = 0; i < l; ++i)
123         {
124             Object arg = args[i];
125 
126             /*
127              * if we are careful down below, a null argument goes in there
128              * so we can know that the null was passed to the method
129              */
130             classes[i] =
131                     arg == null ? null : arg.getClass();
132         }
133 
134         return getBestMatch(methodList, classes);
135     }
136 
137     private static Method getBestMatch(List methods, Class[] args)
138     {
139         List equivalentMatches = null;
140         Method bestMatch = null;
141         Class[] bestMatchTypes = null;
142         for (Iterator i = methods.iterator(); i.hasNext(); )
143         {
144             Method method = (Method)i.next();
145             if (isApplicable(method, args))
146             {
147                 if (bestMatch == null)
148                 {
149                     bestMatch = method;
150                     bestMatchTypes = method.getParameterTypes();
151                 }
152                 else
153                 {
154                     Class[] methodTypes = method.getParameterTypes();
155                     switch (compare(methodTypes, bestMatchTypes))
156                     {
157                         case MORE_SPECIFIC:
158                             if (equivalentMatches == null)
159                             {
160                                 bestMatch = method;
161                                 bestMatchTypes = methodTypes;
162                             }
163                             else
164                             {
165                                 // have to beat all other ambiguous ones...
166                                 int ambiguities = equivalentMatches.size();
167                                 for (int a=0; a < ambiguities; a++)
168                                 {
169                                     Method other = (Method)equivalentMatches.get(a);
170                                     switch (compare(methodTypes, other.getParameterTypes()))
171                                     {
172                                         case MORE_SPECIFIC:
173                                             // ...and thus replace them all...
174                                             bestMatch = method;
175                                             bestMatchTypes = methodTypes;
176                                             equivalentMatches = null;
177                                             ambiguities = 0;
178                                             break;
179 
180                                         case INCOMPARABLE:
181                                             // ...join them...
182                                             equivalentMatches.add(method);
183                                             break;
184 
185                                         case LESS_SPECIFIC:
186                                             // ...or just go away.
187                                             break;
188                                     }
189                                 }
190                             }
191                             break;
192 
193                         case INCOMPARABLE:
194                             if (equivalentMatches == null)
195                             {
196                                 equivalentMatches = new ArrayList(bestMatchTypes.length);
197                             }
198                             equivalentMatches.add(method);
199                             break;
200 
201                         case LESS_SPECIFIC:
202                             // do nothing
203                             break;
204                     }
205                 }
206             }
207         }
208                 
209         if (equivalentMatches != null)
210         {
211             System.out.println("ambiguous: "+equivalentMatches);
212             throw new AmbiguousException();
213         }
214         return bestMatch;
215     }
216 
217     /**
218      *  Simple distinguishable exception, used when
219      *  we run across ambiguous overloading.  Caught
220      *  by the introspector.
221      */
222     public static class AmbiguousException extends RuntimeException
223     {
224         /**
225          * Version Id for serializable
226          */
227         private static final long serialVersionUID = -2314636505414551663L;
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 compare(Class[] c1, Class[] c2)
239     {
240         boolean c1MoreSpecific = false;
241         boolean c2MoreSpecific = false;
242 
243         // compare lengths to handle comparisons where the size of the arrays
244         // doesn't match, but the methods are both applicable due to the fact
245         // that one is a varargs method
246         if (c1.length > c2.length)
247         {
248             return MORE_SPECIFIC;
249         }
250         if (c2.length > c1.length)
251         {
252             return LESS_SPECIFIC;
253         }
254 
255         // ok, move on and compare those of equal lengths
256         for(int i = 0; i < c1.length; ++i)
257         {
258             if(c1[i] != c2[i])
259             {
260                 boolean last = (i == c1.length - 1);
261                 c1MoreSpecific =
262                     c1MoreSpecific ||
263                     isStrictConvertible(c2[i], c1[i], last) ||
264                     c2[i] == Object.class;//Object is always least-specific
265                 c2MoreSpecific =
266                     c2MoreSpecific ||
267                     isStrictConvertible(c1[i], c2[i], last) ||
268                     c1[i] == Object.class;//Object is always least-specific
269             }
270         }
271 
272         if(c1MoreSpecific)
273         {
274             if(c2MoreSpecific)
275             {
276                 /*
277                  * If one method accepts varargs and the other does not,
278                  * call the non-vararg one more specific.
279                  */
280                 boolean last1Array = c1[c1.length - 1].isArray();
281                 boolean last2Array = c2[c2.length - 1].isArray();
282                 if (last1Array && !last2Array)
283                 {
284                     return LESS_SPECIFIC;
285                 }
286                 if (!last1Array && last2Array)
287                 {
288                     return MORE_SPECIFIC;
289                 }
290 
291                 /*
292                  *  Incomparable due to cross-assignable arguments (i.e.
293                  * foo(String, Object) vs. foo(Object, String))
294                  */
295                 return INCOMPARABLE;
296             }
297 
298             return MORE_SPECIFIC;
299         }
300 
301         if(c2MoreSpecific)
302         {
303             return LESS_SPECIFIC;
304         }
305 
306         /*
307          * Incomparable due to non-related arguments (i.e.
308          * foo(Runnable) vs. foo(Serializable))
309          */
310 
311         return INCOMPARABLE;
312     }
313 
314     /**
315      * Returns true if the supplied method is applicable to actual
316      * argument types.
317      * 
318      * @param method method that will be called
319      * @param classes arguments to method
320      * @return true if method is applicable to arguments
321      */
322     private static boolean isApplicable(Method method, Class[] classes)
323     {
324         Class[] methodArgs = method.getParameterTypes();
325 
326         if (methodArgs.length > classes.length)
327         {
328             // if there's just one more methodArg than class arg
329             // and the last methodArg is an array, then treat it as a vararg
330             if (methodArgs.length == classes.length + 1 &&
331                 methodArgs[methodArgs.length - 1].isArray())
332             {
333                 // all the args preceding the vararg must match
334                 for (int i = 0; i < classes.length; i++)
335                 {
336                     if (!isConvertible(methodArgs[i], classes[i], false))
337                     {
338                         return false;
339                     }
340                 }
341                 return true;
342             }
343             else
344             {
345                 return false;
346             }
347         }
348         else if (methodArgs.length == classes.length)
349         {
350             // this will properly match when the last methodArg
351             // is an array/varargs and the last class is the type of array
352             // (e.g. String when the method is expecting String...)
353             for(int i = 0; i < classes.length; ++i)
354             {
355                 if(!isConvertible(methodArgs[i], classes[i], false))
356                 {
357                     // if we're on the last arg and the method expects an array
358                     if (i == classes.length - 1 && methodArgs[i].isArray())
359                     {
360                         // check to see if the last arg is convertible
361                         // to the array's component type
362                         return isConvertible(methodArgs[i], classes[i], true);
363                     }
364                     return false;
365                 }
366             }
367         }
368         else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
369         {
370             // check that the last methodArg is an array
371             Class lastarg = methodArgs[methodArgs.length - 1];
372             if (!lastarg.isArray())
373             {
374                 return false;
375             }
376 
377             // check that they all match up to the last method arg
378             for (int i = 0; i < methodArgs.length - 1; ++i)
379             {
380                 if (!isConvertible(methodArgs[i], classes[i], false))
381                 {
382                     return false;
383                 }
384             }
385 
386             // check that all remaining arguments are convertible to the vararg type
387             Class vararg = lastarg.getComponentType();
388             for (int i = methodArgs.length - 1; i < classes.length; ++i)
389             {
390                 if (!isConvertible(vararg, classes[i], false))
391                 {
392                     return false;
393                 }
394             }
395         }
396 
397         return true;
398     }
399 
400     private static boolean isConvertible(Class formal, Class actual,
401                                          boolean possibleVarArg)
402     {
403         return IntrospectionUtils.
404             isMethodInvocationConvertible(formal, actual, possibleVarArg);
405     }
406 
407     private static boolean isStrictConvertible(Class formal, Class actual,
408                                                boolean possibleVarArg)
409     {
410         return IntrospectionUtils.
411             isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
412     }
413 }