View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal.introspection;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Executable;
21  import java.lang.reflect.Method;
22  
23  import java.util.Arrays;
24  import java.util.Deque;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.Map;
29  
30  /**
31   * A method key usable by the introspector cache.
32   * <p>
33   * This stores a method (or class) name and parameters.
34   * </p>
35   * <p>
36   * This replaces the original key scheme which used to build the key
37   * by concatenating the method name and parameters class names as one string
38   * with the exception that primitive types were converted to their object class equivalents.
39   * </p>
40   * <p>
41   * The key is still based on the same information, it is just wrapped in an object instead.
42   * Primitive type classes are converted to they object equivalent to make a key;
43   * int foo(int) and int foo(Integer) do generate the same key.
44   * </p>
45   * A key can be constructed either from arguments (array of objects) or from parameters
46   * (array of class).
47   * Roughly 3x faster than string key to access the map and uses less memory.
48   */
49  public final class MethodKey {
50      /** The initial size of the primitive conversion map. */
51      private static final int PRIMITIVE_SIZE = 11;
52      /** The hash code. */
53      private final int hashCode;
54      /** The method name. */
55      private final String method;
56      /** The parameters. */
57      private final Class<?>[] params;
58      /** A marker for empty parameter list. */
59      private static final Class<?>[] NOARGS = {};
60      /** The hash code constants. */
61      private static final int HASH = 37;
62  
63      /**
64       * Creates a key from a method name and a set of arguments.
65       * @param aMethod the method to generate the key from
66       * @param args    the intended method arguments
67       */
68      public MethodKey(final String aMethod, final Object[] args) {
69          // !! keep this in sync with the other ctor (hash code) !!
70          this.method = aMethod;
71          int hash = this.method.hashCode();
72          final int size;
73          // CSOFF: InnerAssignment
74          if (args != null && (size = args.length) > 0) {
75              this.params = new Class<?>[size];
76              for (int p = 0; p < size; ++p) {
77                  final Object arg = args[p];
78                  // null arguments use void as Void.class as marker
79                  final Class<?> parm = arg == null ? Void.class : arg.getClass();
80                  hash = (HASH * hash) + parm.hashCode();
81                  this.params[p] = parm;
82              }
83          } else {
84              this.params = NOARGS;
85          }
86          this.hashCode = hash;
87      }
88  
89      /**
90       * Creates a key from a method.
91       * @param aMethod the method to generate the key from.
92       */
93      MethodKey(final Executable aMethod) {
94          this(aMethod.getName(), aMethod.getParameterTypes());
95      }
96  
97      /**
98       * Creates a key from a method name and a set of parameters.
99       * @param aMethod the method to generate the key from, class name for constructors
100      * @param args    the intended method parameters
101      */
102     MethodKey(final String aMethod, final Class<?>[] args) {
103         // !! keep this in sync with the other ctor (hash code) !!
104         this.method = aMethod.intern();
105         int hash = this.method.hashCode();
106         final int size;
107         // CSOFF: InnerAssignment
108         if (args != null && (size = args.length) > 0) {
109             this.params = new Class<?>[size];
110             for (int p = 0; p < size; ++p) {
111                 final Class<?> parm = primitiveClass(args[p]);
112                 hash = (HASH * hash) + parm.hashCode();
113                 this.params[p] = parm;
114             }
115         } else {
116             this.params = NOARGS;
117         }
118         this.hashCode = hash;
119     }
120 
121     /**
122      * Gets this key's method name.
123      * @return the method name
124      */
125     String getMethod() {
126         return method;
127     }
128 
129     /**
130      * Gets this key's method parameter classes.
131      * @return the parameters
132      */
133     Class<?>[] getParameters() {
134         return params;
135     }
136 
137     @Override
138     public int hashCode() {
139         return hashCode;
140     }
141 
142     @Override
143     public boolean equals(final Object obj) {
144         if (obj instanceof MethodKey) {
145             final MethodKey key = (MethodKey) obj;
146             return method.equals(key.method) && Arrays.equals(params, key.params);
147         }
148         return false;
149     }
150 
151     @Override
152     public String toString() {
153         final StringBuilder builder = new StringBuilder(method);
154         for (final Class<?> c : params) {
155             builder.append(c == Void.class ? "null" : c.getName());
156         }
157         return builder.toString();
158     }
159 
160     /**
161      * Outputs a human-readable debug representation of this key.
162      * @return method(p0, p1, ...)
163      */
164     public String debugString() {
165         final StringBuilder builder = new StringBuilder(method);
166         builder.append('(');
167         for (int i = 0; i < params.length; i++) {
168             if (i > 0) {
169                 builder.append(", ");
170             }
171             builder.append(Void.class == params[i] ? "null" : params[i].getName());
172         }
173         builder.append(')');
174         return builder.toString();
175     }
176 
177     /**
178      * Checks whether a method accepts a variable number of arguments.
179      * <p>May be due to a subtle bug in some JVMs, if a varargs method is an override, depending on (perhaps) the
180      * class introspection order, the isVarargs flag on the method itself will be false.
181      * To circumvent the potential problem, fetch the method with the same signature from the super-classes,
182      * - which will be different if override  -and get the varargs flag from it.
183      * @param method the method or constructor to check for varargs
184      * @return true if declared varargs, false otherwise
185      */
186     public static boolean isVarArgs(final Executable method) {
187         if (method == null) {
188             return false;
189         }
190         if (method.isVarArgs()) {
191             return true;
192         }
193         // before climbing up the hierarchy, verify that the last parameter is an array
194         final Class<?>[] ptypes = method.getParameterTypes();
195         if (ptypes.length == 0 || ptypes[ptypes.length - 1].getComponentType() == null) {
196             return false;
197         }
198         final String mname = method.getName();
199         // if this is an override, was it actually declared as varargs?
200         Class<?> clazz = method.getDeclaringClass();
201         do {
202             try {
203                 final Method m = clazz.getMethod(mname, ptypes);
204                 if (m.isVarArgs()) {
205                     return true;
206                 }
207             } catch (final NoSuchMethodException xignore) {
208                 // this should not happen...
209             }
210             clazz = clazz.getSuperclass();
211         } while(clazz != null);
212         return false;
213     }
214 
215     /**
216      * Gets the most specific method that is applicable to the parameters of this key.
217      * @param methods a list of methods.
218      * @return the most specific method.
219      * @throws MethodKey.AmbiguousException if there is more than one.
220      */
221     public Method getMostSpecificMethod(final Method[] methods) {
222         return getMostSpecific(methods);
223     }
224 
225     /**
226      * Gets the most specific constructor that is applicable to the parameters of this key.
227      * @param methods a list of constructors.
228      * @return the most specific constructor.
229      * @throws MethodKey.AmbiguousException if there is more than one.
230      */
231     public Constructor<?> getMostSpecificConstructor(final Constructor<?>[] methods) {
232         return getMostSpecific(methods);
233     }
234 
235     /**
236      * Determines whether a type represented by a class object is
237      * convertible to another type represented by a class object using a
238      * method invocation conversion, treating object types of primitive
239      * types as if they were primitive types (that is, a Boolean actual
240      * parameter type matches boolean primitive formal type). This behavior
241      * is because this method is used to determine applicable methods for
242      * an actual parameter list, and primitive types are represented by
243      * their object duals in reflective method calls.
244      *
245      * @param formal         the formal parameter type to which the actual
246      *                       parameter type should be convertible
247      * @param actual         the actual parameter type.
248      * @param possibleVarArg whether we're dealing with the last parameter
249      *                       in the method declaration
250      * @return true if either formal type is assignable from actual type,
251      *         or formal is a primitive type and actual is its corresponding object
252      *         type or an object-type of a primitive type that can be converted to
253      *         the formal type.
254      */
255     public static boolean isInvocationConvertible(final Class<?> formal,
256                                                   final Class<?> actual,
257                                                   final boolean possibleVarArg) {
258         return isInvocationConvertible(formal, actual, false, possibleVarArg);
259     }
260 
261     /**
262      * Determines whether a type represented by a class object is
263      * convertible to another type represented by a class object using a
264      * method invocation conversion, without matching object and primitive
265      * types. This method is used to determine the more specific type when
266      * comparing signatures of methods.
267      *
268      * @param formal         the formal parameter type to which the actual
269      *                       parameter type should be convertible
270      * @param actual         the actual parameter type.
271      * @param possibleVarArg whether not we're dealing with the last parameter
272      *                       in the method declaration
273      * @return true if either formal type is assignable from actual type,
274      *         or formal and actual are both primitive types and actual can be
275      *         subject to widening conversion to formal.
276      */
277     public static boolean isStrictInvocationConvertible(final Class<?> formal,
278                                                         final Class<?> actual,
279                                                         final boolean possibleVarArg) {
280         return isInvocationConvertible(formal, actual, true, possibleVarArg);
281     }
282 
283     /** Converts a primitive type to its corresponding class.
284      * <p>
285      * If the argument type is primitive then we want to convert our
286      * primitive type signature to the corresponding Object type so
287      * introspection for methods with primitive types will work
288      * correctly.
289      * </p>
290      * @param parm a may-be primitive type class
291      * @return the equivalent object class
292      */
293     static Class<?> primitiveClass(final Class<?> parm) {
294         // it was marginally faster to get from the map than call isPrimitive...
295         //if (!parm.isPrimitive()) return parm;
296         final Class<?>[] prim = CONVERTIBLES.get(parm);
297         return prim == null ? parm : prim[0];
298     }
299 
300     /**
301      * Helper to build class arrays.
302      * @param args the classes
303      * @return the array
304      */
305     private static Class<?>[] asArray(final Class<?>... args) {
306         return args;
307     }
308 
309     /**
310      * Maps from primitive types to invocation compatible classes.
311      * <p>Considering the key as a parameter type, the value is the list of argument classes that are invocation
312      *   compatible with the parameter. Example is Long is invocation convertible to long.
313      */
314     private static final Map<Class<?>, Class<?>[]> CONVERTIBLES;
315     static {
316         CONVERTIBLES = new HashMap<>(PRIMITIVE_SIZE);
317         CONVERTIBLES.put(Boolean.TYPE,
318                 asArray(Boolean.class));
319         CONVERTIBLES.put(Character.TYPE,
320                 asArray(Character.class));
321         CONVERTIBLES.put(Byte.TYPE,
322                 asArray(Byte.class));
323         CONVERTIBLES.put(Short.TYPE,
324                 asArray(Short.class, Byte.class));
325         CONVERTIBLES.put(Integer.TYPE,
326                 asArray(Integer.class, Short.class, Byte.class));
327         CONVERTIBLES.put(Long.TYPE,
328                 asArray(Long.class, Integer.class, Short.class, Byte.class));
329         CONVERTIBLES.put(Float.TYPE,
330                 asArray(Float.class, Long.class, Integer.class, Short.class, Byte.class));
331         CONVERTIBLES.put(Double.TYPE,
332             asArray(Double.class, Float.class, Long.class, Integer.class, Short.class, Byte.class));
333     }
334 
335     /**
336      * Maps from primitive types to invocation compatible primitive types.
337      * <p>Considering the key as a parameter type, the value is the list of argument types that are invocation
338      * compatible with the parameter. Example is 'int' is invocation convertible to 'long'.
339      */
340     private static final Map<Class<?>, Class<?>[]> STRICT_CONVERTIBLES;
341     static {
342         STRICT_CONVERTIBLES = new HashMap<>(PRIMITIVE_SIZE);
343         STRICT_CONVERTIBLES.put(Short.TYPE,
344                 asArray(Byte.TYPE));
345         STRICT_CONVERTIBLES.put(Integer.TYPE,
346                 asArray(Short.TYPE, Byte.TYPE));
347         STRICT_CONVERTIBLES.put(Long.TYPE,
348                 asArray(Integer.TYPE, Short.TYPE, Byte.TYPE));
349         STRICT_CONVERTIBLES.put(Float.TYPE,
350                 asArray(Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
351         STRICT_CONVERTIBLES.put(Double.TYPE,
352                 asArray(Float.TYPE, Long.TYPE, Integer.TYPE, Short.TYPE, Byte.TYPE));
353     }
354 
355     /**
356      * Determines parameter-argument invocation compatibility.
357      *
358      * @param formal         the formal parameter type
359      * @param type           the argument type
360      * @param strict         whether the check is strict or not
361      * @param possibleVarArg whether we're dealing with the last parameter in the method declaration
362      * @return true if compatible, false otherwise
363      */
364     private static boolean isInvocationConvertible(
365             final Class<?> formal, final Class<?> type, final boolean strict, final boolean possibleVarArg) {
366         Class<?> actual = type;
367         /* if it is a null, it means the arg was null */
368         if (actual == null && !formal.isPrimitive()) {
369             return true;
370         }
371         /* system asssignable, both sides must be arrays or not */
372         if (actual != null && formal.isAssignableFrom(actual) && actual.isArray() == formal.isArray()) {
373             return true;
374         }
375         /* catch all... */
376         if (!strict && formal == Object.class) {
377             return true;
378         }
379         /* Primitive conversion check. */
380         if (formal.isPrimitive()) {
381             final Class<?>[] clist = strict ? STRICT_CONVERTIBLES.get(formal) : CONVERTIBLES.get(formal);
382             if (clist != null) {
383                 for (final Class<?> aClass : clist) {
384                     if (actual == aClass) {
385                         return true;
386                     }
387                 }
388             }
389             return false;
390         }
391         /* Check for vararg conversion. */
392         if (possibleVarArg && formal.isArray()) {
393             if (actual.isArray()) {
394                 actual = actual.getComponentType();
395             }
396             return isInvocationConvertible(formal.getComponentType(), actual, strict, false);
397         }
398         return false;
399     }
400 
401     /**
402      * whether a method/ctor is more specific than a previously compared one.
403      */
404     private static final int MORE_SPECIFIC = 0;
405     /**
406      * whether a method/ctor is less specific than a previously compared one.
407      */
408     private static final int LESS_SPECIFIC = 1;
409     /**
410      * A method/ctor doesn't match a previously compared one.
411      */
412     private static final int INCOMPARABLE = 2;
413 
414     /**
415      * Simple distinguishable exception, used when
416      * we run across ambiguous overloading. Caught
417      * by the introspector.
418      */
419     public static class AmbiguousException extends RuntimeException {
420         /** Version identifier for serializable. */
421         private static final long serialVersionUID = -201801091655L;
422         /** Whether this exception should be considered severe. */
423         private final boolean severe;
424 
425         /**
426          * A severe or not ambiguous exception.
427          * @param flag logging flag
428          */
429         AmbiguousException(final boolean flag) {
430             this.severe = flag;
431         }
432 
433         /**
434          * Whether this exception is considered severe or benign.
435          * <p>Note that this is meant in the context of an ambiguous exception; benign cases can only be triggered
436          * by null arguments often related to runtime problems (not simply on overload signatures).
437          * @return true if severe, false if benign.
438          */
439         public boolean isSevere() {
440             return severe;
441         }
442     }
443 
444     /**
445      * Gets the most specific method that is applicable to actual argument types.<p>
446      * Attempts to find the most specific applicable method using the
447      * algorithm described in the JLS section 15.12.2 (with the exception that it can't
448      * distinguish a primitive type argument from an object type argument, since in reflection
449      * primitive type arguments are represented by their object counterparts, so for an argument of
450      * type (say) java.lang.Integer, it will not be able to decide between a method that takes int and a
451      * method that takes java.lang.Integer as a parameter.
452      * </p>
453      * <p>
454      * This turns out to be a relatively rare case where this is needed - however, functionality
455      * like this is needed.
456      * </p>
457      *
458      * @param methods a list of methods
459      * @return the most specific method.
460      * @throws MethodKey.AmbiguousException if there is more than one.
461      */
462     private <T extends Executable> T getMostSpecific(final T[] methods) {
463         final Class<?>[] args = getParameters();
464         final Deque<T> applicables = getApplicables(methods, args);
465         if (applicables.isEmpty()) {
466             return null;
467         }
468         if (applicables.size() == 1) {
469             return applicables.getFirst();
470         }
471         /*
472          * This list will contain the maximally specific methods. Hopefully at
473          * the end of the below loop, the list will contain exactly one method,
474          * (the most specific method) otherwise we have ambiguity.
475          */
476         final Deque<T> maximals = new LinkedList<>();
477         for (final T app : applicables) {
478             final Class<?>[] parms = app.getParameterTypes();
479             boolean lessSpecific = false;
480             final Iterator<T> maximal = maximals.iterator();
481             while (!lessSpecific && maximal.hasNext()) {
482                 final T max = maximal.next();
483                 switch (moreSpecific(args, parms, max.getParameterTypes())) {
484                     case MORE_SPECIFIC:
485                         /*
486                          * This method is more specific than the previously
487                          * known maximally specific, so remove the old maximum.
488                          */
489                         maximal.remove();
490                         break;
491                     case LESS_SPECIFIC:
492                         /*
493                          * This method is less specific than any of the
494                          * currently known maximally specific methods, so we
495                          * won't add it into the set of maximally specific
496                          * methods
497                          */
498                         lessSpecific = true;
499                         break;
500                     default:
501                         // nothing to do
502                 }
503             }
504             if (!lessSpecific) {
505                 maximals.addLast(app);
506             }
507         }
508         // if we have more than one maximally specific method, this call is ambiguous...
509         if (maximals.size() > 1) {
510             throw ambiguousException(args, applicables);
511         }
512         return maximals.getFirst();
513     } // CSON: RedundantThrows
514 
515     /**
516      * Creates an ambiguous exception.
517      * <p>
518      * This method computes the severity of the ambiguity. The only <em>non-severe</em> case is when there is
519      * at least one null argument and at most one applicable method or constructor has a corresponding 'Object'
520      * parameter.
521      * We thus consider that ambiguity is benign in presence of null arguments but severe in the case where
522      * the corresponding parameter is of type Object in more than one applicable overloads.
523      * <p>
524      * Rephrasing:
525      * <ul>
526      *  <li>If all arguments are valid instances - no null argument -, ambiguity is severe.</li>
527      *  <li>If there is at least one null argument, the ambiguity is severe if more than one method has a
528      *  corresponding parameter of class 'Object'.</li>
529      * </ul>
530      *
531      * @param classes     the argument args
532      * @param applicables the list of applicable methods or constructors
533      * @return an ambiguous exception
534      */
535     private static <T extends Executable>
536     AmbiguousException ambiguousException(final Class<?>[] classes, final Iterable<T> applicables) {
537         boolean severe = false;
538         int instanceArgCount = 0; // count the number of valid instances, aka not null
539         for (int c = 0; c < classes.length; ++c) {
540             final Class<?> argClazz = classes[c];
541             if (Void.class.equals(argClazz)) {
542                 // count the number of methods for which the current arg maps to an Object parameter
543                 int objectParmCount = 0;
544                 for (final T app : applicables) {
545                     final Class<?>[] parmClasses = app.getParameterTypes();
546                     final Class<?> parmClass = parmClasses[c];
547                     if (Object.class.equals(parmClass) && (objectParmCount++ == 2)) {
548                         severe = true;
549                         break;
550                     }
551                 }
552             } else {
553                 instanceArgCount += 1;
554             }
555         }
556         return new AmbiguousException(severe || instanceArgCount == classes.length);
557     }
558 
559     /**
560      * Determines which method signature (represented by a class array) is more
561      * specific. This defines a partial ordering on the method signatures.
562      *
563      * @param a  the arguments signature
564      * @param c1 first method signature to compare
565      * @param c2 second method signature to compare
566      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
567      * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
568      */
569     private static int moreSpecific(final Class<?>[] a, final Class<?>[] c1, final Class<?>[] c2) {
570         // compare lengths to handle comparisons where the size of the arrays
571         // doesn't match, but the methods are both applicable due to the fact
572         // that one is a varargs method
573         if (c1.length > a.length) {
574             return LESS_SPECIFIC;
575         }
576         if (c2.length > a.length) {
577             return MORE_SPECIFIC;
578         }
579         if (c1.length > c2.length) {
580             return MORE_SPECIFIC;
581         }
582         if (c2.length > c1.length) {
583             return LESS_SPECIFIC;
584         }
585         // same length, keep ultimate param offset for vararg checks
586         final int length = c1.length;
587         final int ultimate = c1.length - 1;
588         // ok, move on and compare those of equal lengths
589         for (int i = 0; i < length; ++i) {
590             if (c1[i] != c2[i]) {
591                 final boolean last = (i == ultimate);
592                 // argument is null, prefer an Object param
593                 if (a[i] == Void.class) {
594                     if (c1[i] == Object.class && c2[i] != Object.class) {
595                         return MORE_SPECIFIC;
596                     }
597                     if (c1[i] != Object.class && c2[i] == Object.class) {
598                         return LESS_SPECIFIC;
599                     }
600                 }
601                 // prefer primitive on non-null arg, non-primitive otherwise
602                 boolean c1s = isPrimitive(c1[i], last);
603                 boolean c2s = isPrimitive(c2[i], last);
604                 if (c1s != c2s) {
605                     return (c1s == (a[i] != Void.class)) ? MORE_SPECIFIC : LESS_SPECIFIC;
606                 }
607                 // if c2 can be converted to c1 but not the opposite,
608                 // c1 is more specific than c2
609                 c1s = isStrictConvertible(c2[i], c1[i], last);
610                 c2s = isStrictConvertible(c1[i], c2[i], last);
611                 if (c1s != c2s) {
612                     return c1s ? MORE_SPECIFIC : LESS_SPECIFIC;
613                 }
614             }
615         }
616         // Incomparable due to non-related arguments (i.e.foo(Runnable) vs. foo(Serializable))
617         return INCOMPARABLE;
618     }
619 
620     /**
621      * Checks whether a parameter class is a primitive.
622      *
623      * @param c              the parameter class
624      * @param possibleVarArg true if this is the last parameter which can be a primitive array (vararg call)
625      * @return true if primitive, false otherwise
626      */
627     private static boolean isPrimitive(final Class<?> c, final boolean possibleVarArg) {
628         if (c != null) {
629             if (c.isPrimitive()) {
630                 return true;
631             }
632             if (possibleVarArg) {
633                 final Class<?> t = c.getComponentType();
634                 return t != null && t.isPrimitive();
635             }
636         }
637         return false;
638     }
639 
640     /**
641      * Returns all methods that are applicable to actual argument types.
642      *
643      * @param methods list of all candidate methods
644      * @param classes the actual types of the arguments
645      * @return a list that contains only applicable methods (number of
646      * formal and actual arguments matches, and argument types are assignable
647      * to formal types through a method invocation conversion).
648      */
649     private static <T extends Executable> Deque<T> getApplicables(final T[] methods, final Class<?>[] classes) {
650         final Deque<T> list = new LinkedList<>();
651         for (final T method : methods) {
652             if (isApplicable(method, classes)) {
653                 list.add(method);
654             }
655         }
656         return list;
657     }
658 
659     /**
660      * Returns true if the supplied method is applicable to actual
661      * argument types.
662      *
663      * @param method  method that will be called
664      * @param actuals arguments signature for method
665      * @return true if method is applicable to arguments
666      */
667     private static <T extends Executable> boolean isApplicable(final T method, final Class<?>[] actuals) {
668         final Class<?>[] formals = method.getParameterTypes();
669         // if same number or args or
670         // there's just one more methodArg than class arg
671         // and the last methodArg is an array, then treat it as a vararg
672         if (formals.length == actuals.length) {
673             // this will properly match when the last methodArg
674             // is an array/varargs and the last class is the type of array
675             // (e.g. String when the method is expecting String...)
676             for (int i = 0; i < actuals.length; ++i) {
677                 if (!isConvertible(formals[i], actuals[i], false)) {
678                     // if we're on the last arg and the method expects an array
679                     if (i == actuals.length - 1 && formals[i].isArray()) {
680                         // check to see if the last arg is convertible
681                         // to the array's component type
682                         return isConvertible(formals[i], actuals[i], true);
683                     }
684                     return false;
685                 }
686             }
687             return true;
688         }
689 
690         // number of formal and actual differ, method must be vararg
691         if (!MethodKey.isVarArgs(method)) {
692             return false;
693         }
694 
695         // fewer arguments than method parameters: vararg is null
696         if (formals.length > actuals.length) {
697             // only one parameter, the last (ie vararg) can be missing
698             if (formals.length - actuals.length > 1) {
699                 return false;
700             }
701             // check that all present args match up to the method parms
702             for (int i = 0; i < actuals.length; ++i) {
703                 if (!isConvertible(formals[i], actuals[i], false)) {
704                     return false;
705                 }
706             }
707             return true;
708         }
709 
710         // more arguments given than the method accepts; check for varargs
711         if (formals.length > 0) {
712             // check that they all match up to the last method arg
713             for (int i = 0; i < formals.length - 1; ++i) {
714                 if (!isConvertible(formals[i], actuals[i], false)) {
715                     return false;
716                 }
717             }
718             // check that all remaining arguments are convertible to the vararg type
719             // (last parm is an array since method is vararg)
720             final Class<?> vararg = formals[formals.length - 1].getComponentType();
721             for (int i = formals.length - 1; i < actuals.length; ++i) {
722                 if (!isConvertible(vararg, actuals[i], false)) {
723                     return false;
724                 }
725             }
726             return true;
727         }
728         // no match
729         return false;
730     }
731 
732     /**
733      * @param formal         the formal parameter type to which the actual
734      *                       parameter type should be convertible
735      * @param actual         the actual parameter type.
736      * @param possibleVarArg whether we're dealing with the last parameter
737      *                       in the method declaration
738      * @return see isMethodInvocationConvertible.
739      * @see #isInvocationConvertible(Class, Class, boolean)
740      */
741     private static boolean isConvertible(final Class<?> formal, final Class<?> actual, final boolean possibleVarArg) {
742         // if we see Void.class, the argument was null
743         return isInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg);
744     }
745 
746     /**
747      * @param formal         the formal parameter type to which the actual
748      *                       parameter type should be convertible
749      * @param actual         the actual parameter type.
750      * @param possibleVarArg whether we're dealing with the last parameter
751      *                       in the method declaration
752      * @return see isStrictMethodInvocationConvertible.
753      * @see #isStrictInvocationConvertible(Class, Class, boolean)
754      */
755     private static boolean isStrictConvertible(final Class<?> formal, final Class<?> actual,
756                                                final boolean possibleVarArg) {
757         // if we see Void.class, the argument was null
758         return isStrictInvocationConvertible(formal, actual.equals(Void.class) ? null : actual, possibleVarArg);
759     }
760 
761 }