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.HashMap;
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 679922 2008-07-25 22:58:13Z nbubna $
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 HashMap();
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 getBestMatch(methodList, classes);
136     }
137 
138     private static Method getBestMatch(List methods, Class[] args)
139     {
140         List equivalentMatches = null;
141         Method bestMatch = null;
142         Class[] bestMatchTypes = null;
143         for (Iterator i = methods.iterator(); i.hasNext(); )
144         {
145             Method method = (Method)i.next();
146             if (isApplicable(method, args))
147             {
148                 if (bestMatch == null)
149                 {
150                     bestMatch = method;
151                     bestMatchTypes = method.getParameterTypes();
152                 }
153                 else
154                 {
155                     Class[] methodTypes = method.getParameterTypes();
156                     switch (compare(methodTypes, bestMatchTypes))
157                     {
158                         case MORE_SPECIFIC:
159                             if (equivalentMatches == null)
160                             {
161                                 bestMatch = method;
162                                 bestMatchTypes = methodTypes;
163                             }
164                             else
165                             {
166                                 // have to beat all other ambiguous ones...
167                                 int ambiguities = equivalentMatches.size();
168                                 for (int a=0; a < ambiguities; a++)
169                                 {
170                                     Method other = (Method)equivalentMatches.get(a);
171                                     switch (compare(methodTypes, other.getParameterTypes()))
172                                     {
173                                         case MORE_SPECIFIC:
174                                             // ...and thus replace them all...
175                                             bestMatch = method;
176                                             bestMatchTypes = methodTypes;
177                                             equivalentMatches = null;
178                                             ambiguities = 0;
179                                             break;
180 
181                                         case INCOMPARABLE:
182                                             // ...join them...
183                                             equivalentMatches.add(method);
184                                             break;
185 
186                                         case LESS_SPECIFIC:
187                                             // ...or just go away.
188                                             break;
189                                     }
190                                 }
191                             }
192                             break;
193 
194                         case INCOMPARABLE:
195                             if (equivalentMatches == null)
196                             {
197                                 equivalentMatches = new ArrayList(bestMatchTypes.length);
198                             }
199                             equivalentMatches.add(method);
200                             break;
201 
202                         case LESS_SPECIFIC:
203                             // do nothing
204                             break;
205                     }
206                 }
207             }
208         }
209                 
210         if (equivalentMatches != null)
211         {
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                 c2MoreSpecific =
265                     c2MoreSpecific ||
266                     isStrictConvertible(c1[i], c2[i], last);
267             }
268         }
269 
270         if(c1MoreSpecific)
271         {
272             if(c2MoreSpecific)
273             {
274                 /*
275                  * If one method accepts varargs and the other does not,
276                  * call the non-vararg one more specific.
277                  */
278                 boolean last1Array = c1[c1.length - 1].isArray();
279                 boolean last2Array = c2[c2.length - 1].isArray();
280                 if (last1Array && !last2Array)
281                 {
282                     return LESS_SPECIFIC;
283                 }
284                 if (!last1Array && last2Array)
285                 {
286                     return MORE_SPECIFIC;
287                 }
288 
289                 /*
290                  *  Incomparable due to cross-assignable arguments (i.e.
291                  * foo(String, Object) vs. foo(Object, String))
292                  */
293                 return INCOMPARABLE;
294             }
295 
296             return MORE_SPECIFIC;
297         }
298 
299         if(c2MoreSpecific)
300         {
301             return LESS_SPECIFIC;
302         }
303 
304         /*
305          * Incomparable due to non-related arguments (i.e.
306          * foo(Runnable) vs. foo(Serializable))
307          */
308 
309         return INCOMPARABLE;
310     }
311 
312     /**
313      * Returns true if the supplied method is applicable to actual
314      * argument types.
315      * 
316      * @param method method that will be called
317      * @param classes arguments to method
318      * @return true if method is applicable to arguments
319      */
320     private static boolean isApplicable(Method method, Class[] classes)
321     {
322         Class[] methodArgs = method.getParameterTypes();
323 
324         if (methodArgs.length > classes.length)
325         {
326             // if there's just one more methodArg than class arg
327             // and the last methodArg is an array, then treat it as a vararg
328             if (methodArgs.length == classes.length + 1 &&
329                 methodArgs[methodArgs.length - 1].isArray())
330             {
331                 return true;
332             }
333             else
334             {
335                 return false;
336             }
337         }
338         else if (methodArgs.length == classes.length)
339         {
340             // this will properly match when the last methodArg
341             // is an array/varargs and the last class is the type of array
342             // (e.g. String when the method is expecting String...)
343             for(int i = 0; i < classes.length; ++i)
344             {
345                 if(!isConvertible(methodArgs[i], classes[i], false))
346                 {
347                     // if we're on the last arg and the method expects an array
348                     if (i == classes.length - 1 && methodArgs[i].isArray())
349                     {
350                         // check to see if the last arg is convertible
351                         // to the array's component type
352                         return isConvertible(methodArgs[i], classes[i], true);
353                     }
354                     return false;
355                 }
356             }
357         }
358         else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
359         {
360             // check that the last methodArg is an array
361             Class lastarg = methodArgs[methodArgs.length - 1];
362             if (!lastarg.isArray())
363             {
364                 return false;
365             }
366 
367             // check that they all match up to the last method arg
368             for (int i = 0; i < methodArgs.length - 1; ++i)
369             {
370                 if (!isConvertible(methodArgs[i], classes[i], false))
371                 {
372                     return false;
373                 }
374             }
375 
376             // check that all remaining arguments are convertible to the vararg type
377             Class vararg = lastarg.getComponentType();
378             for (int i = methodArgs.length - 1; i < classes.length; ++i)
379             {
380                 if (!isConvertible(vararg, classes[i], false))
381                 {
382                     return false;
383                 }
384             }
385         }
386 
387         return true;
388     }
389 
390     private static boolean isConvertible(Class formal, Class actual,
391                                          boolean possibleVarArg)
392     {
393         return IntrospectionUtils.
394             isMethodInvocationConvertible(formal, actual, possibleVarArg);
395     }
396 
397     private static boolean isStrictConvertible(Class formal, Class actual,
398                                                boolean possibleVarArg)
399     {
400         return IntrospectionUtils.
401             isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
402     }
403 }