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  
28  import org.apache.velocity.util.MapFactory;
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 935975 2010-04-20 16:04:55Z 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 = MapFactory.create(false);
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             //System.out.println("ambiguous: "+equivalentMatches);//for debugging only
213             throw new AmbiguousException();
214         }
215         return bestMatch;
216     }
217 
218     /**
219      *  Simple distinguishable exception, used when
220      *  we run across ambiguous overloading.  Caught
221      *  by the introspector.
222      */
223     public static class AmbiguousException extends RuntimeException
224     {
225         /**
226          * Version Id for serializable
227          */
228         private static final long serialVersionUID = -2314636505414551663L;
229     }
230 
231     /**
232      * Determines which method signature (represented by a class array) is more
233      * specific. This defines a partial ordering on the method signatures.
234      * @param c1 first signature to compare
235      * @param c2 second signature to compare
236      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
237      * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
238      */
239     private static int compare(Class[] c1, Class[] c2)
240     {
241         boolean c1MoreSpecific = false;
242         boolean c2MoreSpecific = false;
243 
244         // compare lengths to handle comparisons where the size of the arrays
245         // doesn't match, but the methods are both applicable due to the fact
246         // that one is a varargs method
247         if (c1.length > c2.length)
248         {
249             return MORE_SPECIFIC;
250         }
251         if (c2.length > c1.length)
252         {
253             return LESS_SPECIFIC;
254         }
255 
256         // ok, move on and compare those of equal lengths
257         for(int i = 0; i < c1.length; ++i)
258         {
259             if(c1[i] != c2[i])
260             {
261                 boolean last = (i == c1.length - 1);
262                 c1MoreSpecific =
263                     c1MoreSpecific ||
264                     isStrictConvertible(c2[i], c1[i], last) ||
265                     c2[i] == Object.class;//Object is always least-specific
266                 c2MoreSpecific =
267                     c2MoreSpecific ||
268                     isStrictConvertible(c1[i], c2[i], last) ||
269                     c1[i] == Object.class;//Object is always least-specific
270             }
271         }
272 
273         if(c1MoreSpecific)
274         {
275             if(c2MoreSpecific)
276             {
277                 /*
278                  * If one method accepts varargs and the other does not,
279                  * call the non-vararg one more specific.
280                  */
281                 boolean last1Array = c1[c1.length - 1].isArray();
282                 boolean last2Array = c2[c2.length - 1].isArray();
283                 if (last1Array && !last2Array)
284                 {
285                     return LESS_SPECIFIC;
286                 }
287                 if (!last1Array && last2Array)
288                 {
289                     return MORE_SPECIFIC;
290                 }
291 
292                 /*
293                  *  Incomparable due to cross-assignable arguments (i.e.
294                  * foo(String, Object) vs. foo(Object, String))
295                  */
296                 return INCOMPARABLE;
297             }
298 
299             return MORE_SPECIFIC;
300         }
301 
302         if(c2MoreSpecific)
303         {
304             return LESS_SPECIFIC;
305         }
306 
307         /*
308          * Incomparable due to non-related arguments (i.e.
309          * foo(Runnable) vs. foo(Serializable))
310          */
311 
312         return INCOMPARABLE;
313     }
314 
315     /**
316      * Returns true if the supplied method is applicable to actual
317      * argument types.
318      * 
319      * @param method method that will be called
320      * @param classes arguments to method
321      * @return true if method is applicable to arguments
322      */
323     private static boolean isApplicable(Method method, Class[] classes)
324     {
325         Class[] methodArgs = method.getParameterTypes();
326 
327         if (methodArgs.length > classes.length)
328         {
329             // if there's just one more methodArg than class arg
330             // and the last methodArg is an array, then treat it as a vararg
331             if (methodArgs.length == classes.length + 1 &&
332                 methodArgs[methodArgs.length - 1].isArray())
333             {
334                 // all the args preceding the vararg must match
335                 for (int i = 0; i < classes.length; i++)
336                 {
337                     if (!isConvertible(methodArgs[i], classes[i], false))
338                     {
339                         return false;
340                     }
341                 }
342                 return true;
343             }
344             else
345             {
346                 return false;
347             }
348         }
349         else if (methodArgs.length == classes.length)
350         {
351             // this will properly match when the last methodArg
352             // is an array/varargs and the last class is the type of array
353             // (e.g. String when the method is expecting String...)
354             for(int i = 0; i < classes.length; ++i)
355             {
356                 if(!isConvertible(methodArgs[i], classes[i], false))
357                 {
358                     // if we're on the last arg and the method expects an array
359                     if (i == classes.length - 1 && methodArgs[i].isArray())
360                     {
361                         // check to see if the last arg is convertible
362                         // to the array's component type
363                         return isConvertible(methodArgs[i], classes[i], true);
364                     }
365                     return false;
366                 }
367             }
368         }
369         else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
370         {
371             // check that the last methodArg is an array
372             Class lastarg = methodArgs[methodArgs.length - 1];
373             if (!lastarg.isArray())
374             {
375                 return false;
376             }
377 
378             // check that they all match up to the last method arg
379             for (int i = 0; i < methodArgs.length - 1; ++i)
380             {
381                 if (!isConvertible(methodArgs[i], classes[i], false))
382                 {
383                     return false;
384                 }
385             }
386 
387             // check that all remaining arguments are convertible to the vararg type
388             Class vararg = lastarg.getComponentType();
389             for (int i = methodArgs.length - 1; i < classes.length; ++i)
390             {
391                 if (!isConvertible(vararg, classes[i], false))
392                 {
393                     return false;
394                 }
395             }
396         }
397 
398         return true;
399     }
400 
401     private static boolean isConvertible(Class formal, Class actual,
402                                          boolean possibleVarArg)
403     {
404         return IntrospectionUtils.
405             isMethodInvocationConvertible(formal, actual, possibleVarArg);
406     }
407 
408     private static boolean isStrictConvertible(Class formal, Class actual,
409                                                boolean possibleVarArg)
410     {
411         return IntrospectionUtils.
412             isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
413     }
414 }