• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.apache.commons.lang3.reflect.TypeUtils;
23 
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Type;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.ListIterator;
31 import java.util.Map;
32 import java.util.concurrent.ConcurrentHashMap;
33 
34 /**
35  *
36  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
37  * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
38  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
39  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
40  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
41  * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
42  * @version $Id$
43  */
44 public class MethodMap
45 {
46     /* Constants for specificity */
47     private static final int INCOMPARABLE = 0;
48     private static final int MORE_SPECIFIC = 1;
49     private static final int EQUIVALENT = 2;
50     private static final int LESS_SPECIFIC = 3;
51 
52     /* Constants for applicability */
53     private static final int NOT_CONVERTIBLE = 0;
54     private static final int EXPLICITLY_CONVERTIBLE = 1;
55     private static final int IMPLCITLY_CONVERTIBLE = 2;
56     private static final int STRICTLY_CONVERTIBLE = 3;
57 
58     TypeConversionHandler conversionHandler;
59 
60     /**
61      * Default constructor
62      */
MethodMap()63     public MethodMap()
64     {
65         this(null);
66     }
67 
68     /**
69      * Constructor with provided conversion handler
70      * @param conversionHandler conversion handler
71      * @since 2.0
72      */
MethodMap(TypeConversionHandler conversionHandler)73     public MethodMap(TypeConversionHandler conversionHandler)
74     {
75         this.conversionHandler = conversionHandler;
76     }
77 
78     /**
79      * Keep track of all methods with the same name.
80      */
81     Map<String, List<Method>> methodByNameMap = new ConcurrentHashMap<>();
82 
83     /**
84      * Add a method to a list of methods by name.
85      * For a particular class we are keeping track
86      * of all the methods with the same name.
87      * @param method
88      */
add(Method method)89     public void add(Method method)
90     {
91         String methodName = method.getName();
92 
93         List<Method> l = get( methodName );
94 
95         if ( l == null)
96         {
97             l = new ArrayList<>();
98             methodByNameMap.put(methodName, l);
99         }
100 
101         l.add(method);
102     }
103 
104     /**
105      * Return a list of methods with the same name.
106      *
107      * @param key
108      * @return List list of methods
109      */
get(String key)110     public List<Method> get(String key)
111     {
112         return methodByNameMap.get(key);
113     }
114 
115     /**
116      *  <p>
117      *  Find a method.  Attempts to find the
118      *  most specific applicable method using the
119      *  algorithm described in the JLS section
120      *  15.12.2 (with the exception that it can't
121      *  distinguish a primitive type argument from
122      *  an object type argument, since in reflection
123      *  primitive type arguments are represented by
124      *  their object counterparts, so for an argument of
125      *  type (say) java.lang.Integer, it will not be able
126      *  to decide between a method that takes int and a
127      *  method that takes java.lang.Integer as a parameter.
128      *  </p>
129      *
130      *  <p>
131      *  This turns out to be a relatively rare case
132      *  where this is needed - however, functionality
133      *  like this is needed.
134      *  </p>
135      *
136      *  @param methodName name of method
137      *  @param args the actual arguments with which the method is called
138      *  @return the most specific applicable method, or null if no
139      *  method is applicable.
140      *  @throws AmbiguousException if there is more than one maximally
141      *  specific applicable method
142      */
find(String methodName, Object[] args)143     public Method find(String methodName, Object[] args)
144         throws AmbiguousException
145     {
146         List<Method> methodList = get(methodName);
147 
148         if (methodList == null)
149         {
150             return null;
151         }
152 
153         int l = args.length;
154         Class<?>[] classes = new Class[l];
155 
156         for(int i = 0; i < l; ++i)
157         {
158             Object arg = args[i];
159 
160             /*
161              * if we are careful down below, a null argument goes in there
162              * so we can know that the null was passed to the method
163              */
164             classes[i] =
165                     arg == null ? null : arg.getClass();
166         }
167 
168         return getBestMatch(methodList, classes);
169     }
170 
171     private class Match
172     {
173         /* target method */
174         Method method;
175 
176         /* cache arguments classes array */
177         Type[] methodTypes;
178 
179         /* specificity: how does the best match compare to provided arguments
180          * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */
181         int specificity;
182 
183         /* applicability which conversion level is needed against provided arguments
184          * one of STRICTLY_CONVERTIBLE, IMPLICITLY_CONVERTIBLE and EXPLICITLY_CONVERTIBLE_ */
185         int applicability;
186 
187         /* whether the method has varrags */
188         boolean varargs;
189 
Match(Method method, int applicability, Class<?>[] unboxedArgs)190         Match(Method method, int applicability, Class<?>[] unboxedArgs)
191         {
192             this.method = method;
193             this.applicability = applicability;
194             this.methodTypes = method.getGenericParameterTypes();
195             this.specificity = compare(methodTypes, unboxedArgs);
196             this.varargs = methodTypes.length > 0 && TypeUtils.isArrayType(methodTypes[methodTypes.length - 1]);
197         }
198     }
199 
onlyNullOrObjects(Class<?>[] args)200     private static boolean onlyNullOrObjects(Class<?>[] args)
201     {
202         for (Class<?> cls : args)
203         {
204             if (cls != null && cls != Object.class) return false;
205         }
206         return args.length > 0;
207     }
208 
getBestMatch(List<Method> methods, Class<?>[] args)209     private Method getBestMatch(List<Method> methods, Class<?>[] args)
210     {
211         List<Match> bestMatches = new LinkedList<>();
212         Class<?>[] unboxedArgs = new Class<?>[args.length];
213         for (int i = 0; i < args.length; ++i)
214         {
215             unboxedArgs[i] = IntrospectionUtils.getUnboxedClass(args[i]);
216         }
217         for (Method method : methods)
218         {
219             int applicability = getApplicability(method, unboxedArgs);
220             if (applicability > NOT_CONVERTIBLE)
221             {
222                 Match match = new Match(method, applicability, unboxedArgs);
223                 if (bestMatches.size() == 0)
224                 {
225                     bestMatches.add(match);
226                 }
227                 else
228                 {
229                     /* filter existing matches */
230                     boolean keepMethod = true;
231                     for (ListIterator<Match> it = bestMatches.listIterator(); keepMethod && it.hasNext();)
232                     {
233                         Match best = it.next();
234                         /* do not retain match if it's more specific than (or incomparable to) provided (unboxed) arguments
235                          * while one of the best matches is less specific
236                          */
237                         if (best.specificity == LESS_SPECIFIC && match.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
238                         {
239                             keepMethod = false;
240                         }
241                         /* drop considered best match if match is less specific than (unboxed) provided args while
242                          * the considered best match is more specific or incomparable
243                          */
244                         else if (match.specificity == LESS_SPECIFIC && best.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
245                         {
246                             it.remove();
247                         }
248                         /* compare applicability */
249                         else if (best.applicability > match.applicability)
250                         {
251                             keepMethod = false;
252                         }
253                         else if (best.applicability < match.applicability)
254                         {
255                             it.remove();
256                         }
257                         /* compare methods between them */
258                         else
259                         {
260                             /* but only if some provided args are non null and not Object */
261                             if (onlyNullOrObjects(args))
262                             {
263                                 /* in this case we only favor non-varrags methods */
264                                 if (match.varargs != best.varargs)
265                                 {
266                                     if (match.varargs)
267                                     {
268                                         keepMethod = false;
269                                     }
270                                     else if (best.varargs)
271                                     {
272                                         it.remove();
273                                     }
274                                 }
275                             }
276                             else
277                             {
278                                 switch (compare(match.methodTypes, best.methodTypes))
279                                 {
280                                     case LESS_SPECIFIC:
281                                         keepMethod = false;
282                                         break;
283                                     case MORE_SPECIFIC:
284                                         it.remove();
285                                         break;
286                                     case INCOMPARABLE:
287                                         /* Java compiler favors non-vararg methods. Let's do the same. */
288                                         if (match.varargs != best.varargs)
289                                         {
290                                             if (match.varargs)
291                                             {
292                                                 keepMethod = false;
293                                             }
294                                             else if (best.varargs)
295                                             {
296                                                 it.remove();
297                                             }
298                                         }
299                                         /* otherwise it's an equivalent match */
300                                         break;
301                                     case EQUIVALENT:
302                                         break;
303                                 }
304                             }
305                         }
306                     }
307                     if (keepMethod)
308                     {
309                         bestMatches.add(match);
310                     }
311                 }
312             }
313         }
314 
315         switch (bestMatches.size())
316         {
317             case 0: return null;
318             case 1: return bestMatches.get(0).method;
319             default: throw new AmbiguousException();
320         }
321     }
322 
323     /**
324      *  Simple distinguishable exception, used when
325      *  we run across ambiguous overloading.  Caught
326      *  by the introspector.
327      */
328     public static class AmbiguousException extends RuntimeException
329     {
330         /**
331          * Version Id for serializable
332          */
333         private static final long serialVersionUID = -2314636505414551663L;
334     }
335 
336     /**
337      * Determines which method signature (represented by a class array) is more
338      * specific. This defines a partial ordering on the method signatures.
339      * @param t1 first signature to compare
340      * @param t2 second signature to compare
341      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
342      * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
343      */
compare(Type[] t1, Type[] t2)344     private int compare(Type[] t1, Type[] t2)
345     {
346         boolean t1IsVararag = false;
347         boolean t2IsVararag = false;
348         boolean fixedLengths = false;
349 
350         // compare lengths to handle comparisons where the size of the arrays
351         // doesn't match, but the methods are both applicable due to the fact
352         // that one is a varargs method
353         if (t1.length > t2.length)
354         {
355             int l2 = t2.length;
356             if (l2 == 0)
357             {
358                 return MORE_SPECIFIC;
359             }
360             t2 = Arrays.copyOf(t2, t1.length);
361             Type itemType = TypeUtils.getArrayComponentType(t2[l2 - 1]);
362             /* if item class is null, then it implies the vaarg is #1
363              * (and receives an empty array)
364              */
365             if (itemType == null)
366             {
367                 /* by construct, we have c1.length = l2 + 1 */
368                 t1IsVararag = true;
369                 t2[t1.length - 1] = null;
370             }
371             else
372             {
373                 t2IsVararag = true;
374                 for (int i = l2 - 1; i < t1.length; ++i)
375                 {
376                 /* also overwrite the vaargs itself */
377                     t2[i] = itemType;
378                 }
379             }
380             fixedLengths = true;
381         }
382         else if (t2.length > t1.length)
383         {
384             int l1 = t1.length;
385             if (l1 == 0)
386             {
387                 return LESS_SPECIFIC;
388             }
389             t1 = Arrays.copyOf(t1, t2.length);
390             Type itemType = TypeUtils.getArrayComponentType(t1[l1 - 1]);
391             /* if item class is null, then it implies the vaarg is #2
392              * (and receives an empty array)
393              */
394             if (itemType == null)
395             {
396                 /* by construct, we have c2.length = l1 + 1 */
397                 t2IsVararag = true;
398                 t1[t2.length - 1] = null;
399             }
400             else
401             {
402                 t1IsVararag = true;
403                 for (int i = l1 - 1; i < t2.length; ++i)
404                 {
405                 /* also overwrite the vaargs itself */
406                     t1[i] = itemType;
407                 }
408             }
409             fixedLengths = true;
410         }
411 
412         /* ok, move on and compare those of equal lengths */
413         int fromC1toC2 = STRICTLY_CONVERTIBLE;
414         int fromC2toC1 = STRICTLY_CONVERTIBLE;
415         for(int i = 0; i < t1.length; ++i)
416         {
417             Class<?> c1 = t1[i] == null ? null : IntrospectionUtils.getTypeClass(t1[i]);
418             Class<?> c2 = t2[i] == null ? null : IntrospectionUtils.getTypeClass(t2[i]);
419             boolean last = !fixedLengths && (i == t1.length - 1);
420             if (t1[i] == null && t2[i] != null || t1[i] != null && t2[i] == null || !t1[i].equals(t2[i]))
421             {
422                 if (t1[i] == null)
423                 {
424                     fromC2toC1 = NOT_CONVERTIBLE;
425                     if (c2 != null && c2.isPrimitive())
426                     {
427                         fromC1toC2 = NOT_CONVERTIBLE;
428                     }
429                 }
430                 else if (t2[i] == null)
431                 {
432                     fromC1toC2 = NOT_CONVERTIBLE;
433                     if (c1 != null && c1.isPrimitive())
434                     {
435                         fromC2toC1 = NOT_CONVERTIBLE;
436                     }
437                 }
438                 else
439                 {
440                     if (c1 != null)
441                     {
442                         switch (fromC1toC2)
443                         {
444                             case STRICTLY_CONVERTIBLE:
445                                 if (isStrictConvertible(t2[i], c1, last)) break;
446                                 fromC1toC2 = IMPLCITLY_CONVERTIBLE;
447                             case IMPLCITLY_CONVERTIBLE:
448                                 if (isConvertible(t2[i], c1, last)) break;
449                                 fromC1toC2 = EXPLICITLY_CONVERTIBLE;
450                             case EXPLICITLY_CONVERTIBLE:
451                                 if (isExplicitlyConvertible(t2[i], c1, last)) break;
452                                 fromC1toC2 = NOT_CONVERTIBLE;
453                         }
454                     }
455                     else if (fromC1toC2 > NOT_CONVERTIBLE)
456                     {
457                         fromC1toC2 = TypeUtils.isAssignable(t1[i], t2[i]) ?
458                             Math.min(fromC1toC2, IMPLCITLY_CONVERTIBLE) :
459                             NOT_CONVERTIBLE;
460                     }
461                     if (c2 != null)
462                     {
463                         switch (fromC2toC1)
464                         {
465                             case STRICTLY_CONVERTIBLE:
466                                 if (isStrictConvertible(t1[i], c2, last)) break;
467                                 fromC2toC1 = IMPLCITLY_CONVERTIBLE;
468                             case IMPLCITLY_CONVERTIBLE:
469                                 if (isConvertible(t1[i], c2, last)) break;
470                                 fromC2toC1 = EXPLICITLY_CONVERTIBLE;
471                             case EXPLICITLY_CONVERTIBLE:
472                                 if (isExplicitlyConvertible(t1[i], c2, last)) break;
473                                 fromC2toC1 = NOT_CONVERTIBLE;
474                         }
475                     }
476                     else if (fromC2toC1 > NOT_CONVERTIBLE)
477                     {
478                         fromC2toC1 = TypeUtils.isAssignable(t2[i], t1[i]) ?
479                             Math.min(fromC2toC1, IMPLCITLY_CONVERTIBLE) :
480                             NOT_CONVERTIBLE;
481                     }
482                 }
483             }
484         }
485 
486         if (fromC1toC2 == NOT_CONVERTIBLE && fromC2toC1 == NOT_CONVERTIBLE)
487         {
488             /*
489              *  Incomparable due to cross-assignable arguments (i.e.
490              * foo(String, Foo) vs. foo(Foo, String))
491              */
492             return INCOMPARABLE;
493         }
494 
495         if (fromC1toC2 > fromC2toC1)
496         {
497             return MORE_SPECIFIC;
498         }
499         else if (fromC2toC1 > fromC1toC2)
500         {
501             return LESS_SPECIFIC;
502         }
503         else
504         {
505             /*
506              * If one method accepts varargs and the other does not,
507              * call the non-vararg one more specific.
508              */
509             boolean last1Array = t1IsVararag || !fixedLengths && TypeUtils.isArrayType (t1[t1.length - 1]);
510             boolean last2Array = t2IsVararag || !fixedLengths && TypeUtils.isArrayType(t2[t2.length - 1]);
511             if (last1Array && !last2Array)
512             {
513                 return LESS_SPECIFIC;
514             }
515             if (!last1Array && last2Array)
516             {
517                 return MORE_SPECIFIC;
518             }
519         }
520         return EQUIVALENT;
521     }
522 
523     /**
524      * Returns the applicability of the supplied method against actual argument types.
525      *
526      * @param method method that will be called
527      * @param classes arguments to method
528      * @return the level of applicability:
529      *         0 = not applicable
530      *         1 = explicitly applicable (i.e. using stock or custom conversion handlers)
531      *         2 = implicitly applicable (i.e. using JAva implicit boxing/unboxing and primitive types widening)
532      *         3 = strictly applicable
533      */
getApplicability(Method method, Class<?>[] classes)534     private int getApplicability(Method method, Class<?>[] classes)
535     {
536         Type[] methodArgs = method.getGenericParameterTypes();
537         int ret = STRICTLY_CONVERTIBLE;
538         if (methodArgs.length > classes.length)
539         {
540             // if there's just one more methodArg than class arg
541             // and the last methodArg is an array, then treat it as a vararg
542             if (methodArgs.length == classes.length + 1 && TypeUtils.isArrayType(methodArgs[methodArgs.length - 1]))
543             {
544                 // all the args preceding the vararg must match
545                 for (int i = 0; i < classes.length; i++)
546                 {
547                     if (!isStrictConvertible(methodArgs[i], classes[i], false))
548                     {
549                         if (isConvertible(methodArgs[i], classes[i], false))
550                         {
551                             ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
552                         }
553                         else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
554                         {
555                             ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
556                         }
557                         else
558                         {
559                             return NOT_CONVERTIBLE;
560                         }
561                     }
562                 }
563                 return ret;
564             }
565             else
566             {
567                 return NOT_CONVERTIBLE;
568             }
569         }
570         else if (methodArgs.length == classes.length)
571         {
572             // this will properly match when the last methodArg
573             // is an array/varargs and the last class is the type of array
574             // (e.g. String when the method is expecting String...)
575             for(int i = 0; i < classes.length; ++i)
576             {
577                 boolean possibleVararg = i == classes.length - 1 && TypeUtils.isArrayType(methodArgs[i]);
578                 if (!isStrictConvertible(methodArgs[i], classes[i], possibleVararg))
579                 {
580                     if (isConvertible(methodArgs[i], classes[i], possibleVararg))
581                     {
582                         ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
583                     }
584                     else if (isExplicitlyConvertible(methodArgs[i], classes[i], possibleVararg))
585                     {
586                         ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
587                     }
588                     else
589                     {
590                         return NOT_CONVERTIBLE;
591                     }
592                 }
593             }
594             return ret;
595         }
596         else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
597         {
598             // check that the last methodArg is an array
599             Type lastarg = methodArgs[methodArgs.length - 1];
600             if (!TypeUtils.isArrayType(lastarg))
601             {
602                 return NOT_CONVERTIBLE;
603             }
604 
605             // check that they all match up to the last method arg component type
606             for (int i = 0; i < methodArgs.length - 1; ++i)
607             {
608                 if (!isStrictConvertible(methodArgs[i], classes[i], false))
609                 {
610                     if (isConvertible(methodArgs[i], classes[i], false))
611                     {
612                         ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
613                     }
614                     else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
615                     {
616                         ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
617                     }
618                     else
619                     {
620                         return NOT_CONVERTIBLE;
621                     }
622                 }
623             }
624 
625             // check that all remaining arguments are convertible to the vararg type
626             Type vararg = TypeUtils.getArrayComponentType(lastarg);
627             for (int i = methodArgs.length - 1; i < classes.length; ++i)
628             {
629                 if (!isStrictConvertible(vararg, classes[i], false))
630                 {
631                     if (isConvertible(vararg, classes[i], false))
632                     {
633                         ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
634                     }
635                     else if (isExplicitlyConvertible(vararg, classes[i], false))
636                     {
637                         ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
638                     }
639                     else
640                     {
641                         return NOT_CONVERTIBLE;
642                     }
643                 }
644             }
645             return ret;
646         }
647         return NOT_CONVERTIBLE;
648     }
649 
650     /**
651      * Returns true if <code>actual</code> is convertible to <code>formal</code> by implicit Java method call conversions
652      *
653      * @param formal
654      * @param actual
655      * @param possibleVarArg
656      * @return convertible
657      */
isConvertible(Type formal, Class<?> actual, boolean possibleVarArg)658     private boolean isConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
659     {
660         return IntrospectionUtils.
661             isMethodInvocationConvertible(formal, actual, possibleVarArg);
662     }
663 
664     /**
665      * Returns true if <code>actual</code> is strictly convertible to <code>formal</code> (aka without implicit
666      * boxing/unboxing)
667      *
668      * @param formal
669      * @param actual
670      * @param possibleVarArg
671      * @return convertible
672      */
isStrictConvertible(Type formal, Class<?> actual, boolean possibleVarArg)673     private static boolean isStrictConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
674     {
675         return IntrospectionUtils.
676             isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
677     }
678 
679     /**
680      * Returns true if <code>actual</code> is convertible to <code>formal</code> using an explicit converter
681      *
682      * @param formal
683      * @param actual
684      * @param possibleVarArg
685      * @return
686      */
isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)687     private boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
688     {
689         return conversionHandler != null && conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg);
690     }
691 }
692