You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2017/04/08 13:20:55 UTC

svn commit: r1790680 - in /velocity/engine/trunk: ./ velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/

Author: cbrisson
Date: Sat Apr  8 13:20:55 2017
New Revision: 1790680

URL: http://svn.apache.org/viewvc?rev=1790680&view=rev
Log:
[engine] Full review of method disambiguation

Modified:
    velocity/engine/trunk/pom.xml
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
    velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
    velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java

Modified: velocity/engine/trunk/pom.xml
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/pom.xml?rev=1790680&r1=1790679&r2=1790680&view=diff
==============================================================================
--- velocity/engine/trunk/pom.xml (original)
+++ velocity/engine/trunk/pom.xml Sat Apr  8 13:20:55 2017
@@ -83,8 +83,8 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <debug>false</debug>
-          <optimize>true</optimize>
+          <debug>true</debug>
+          <optimize>false</optimize>
           <showDeprecation>true</showDeprecation>
           <showWarning>true</showWarning>
           <source>${maven.compiler.source}</source>

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java?rev=1790680&r1=1790679&r2=1790680&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java Sat Apr  8 13:20:55 2017
@@ -216,10 +216,10 @@ public class IntrospectionUtils
                                                               Class actual,
                                                               boolean possibleVarArg)
     {
-        /* we shouldn't get a null into, but if so */
-        if (actual == null && !formal.isPrimitive())
+        /* Check for nullity */
+        if (actual == null)
         {
-            return true;
+            return !formal.isPrimitive();
         }
 
         /* Check for identity or widening reference conversion */

Modified: velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java?rev=1790680&r1=1790679&r2=1790680&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java Sat Apr  8 13:20:55 2017
@@ -19,10 +19,14 @@ package org.apache.velocity.util.introsp
  * under the License.
  */
 
+import org.apache.velocity.exception.VelocityException;
+
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -38,9 +42,17 @@ import java.util.concurrent.ConcurrentHa
  */
 public class MethodMap
 {
-    private static final int MORE_SPECIFIC = 0;
-    private static final int LESS_SPECIFIC = 1;
-    private static final int INCOMPARABLE = 2;
+    /* Constants for specificity */
+    private static final int INCOMPARABLE = 0;
+    private static final int MORE_SPECIFIC = 1;
+    private static final int EQUIVALENT = 2;
+    private static final int LESS_SPECIFIC = 3;
+
+    /* Constants for applicability */
+    private static final int NOT_CONVERTIBLE = 0;
+    private static final int EXPLICITLY_CONVERTIBLE = 1;
+    private static final int IMPLCITLY_CONVERTIBLE = 2;
+    private static final int STRICTLY_CONVERTIBLE = 3;
 
     ConversionHandler conversionHandler;
 
@@ -155,12 +167,43 @@ public class MethodMap
         return getBestMatch(methodList, classes);
     }
 
+    private class Match
+    {
+        /* target method */
+        Method method;
+
+        /* cache arguments classes array */
+        Class[] methodTypes;
+
+        /* specificity: how does the best match compare to provided arguments
+         * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */
+        int specificity;
+
+        /* applicability which conversion level is needed against provided arguments
+         * one of STRICTLY_CONVERTIBLE, IMPLICITLY_CONVERTIBLE and EXPLICITLY_CONVERTIBLE_ */
+        int applicability;
+
+        Match(Method method, int applicability, Class[] unboxedArgs)
+        {
+            this.method = method;
+            this.applicability = applicability;
+            this.methodTypes = method.getParameterTypes();
+            this.specificity = compare(methodTypes, unboxedArgs);
+        }
+    }
+
+    private static boolean onlyNullOrObjects(Class[] args)
+    {
+        for (Class cls : args)
+        {
+            if (cls != null && cls != Object.class) return false;
+        }
+        return true;
+    }
+
     private Method getBestMatch(List<Method> methods, Class[] args)
     {
-        List equivalentMatches = null;
-        Method bestMatch = null;
-        Class[] bestMatchTypes = null;
-        int bestMatchComp = INCOMPARABLE; /* how does the best match compare to provided type */
+        List<Match> bestMatches = new LinkedList<Match>();
         Class[] unboxedArgs = new Class[args.length];
         for (int i = 0; i < args.length; ++i)
         {
@@ -168,128 +211,79 @@ public class MethodMap
         }
         for (Method method : methods)
         {
-            if (isApplicable(method, args))
+            int applicability = getApplicability(method, args);
+            if (applicability > NOT_CONVERTIBLE)
             {
-                if (bestMatch == null)
+                Match match = new Match(method, applicability, unboxedArgs);
+                if (bestMatches.size() == 0)
                 {
-                    bestMatch = method;
-                    bestMatchTypes = method.getParameterTypes();
-                    bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                } else
-                {
-                    Class[] methodTypes = method.getParameterTypes();
-                    switch (compare(methodTypes, bestMatchTypes))
-                    {
-                        case MORE_SPECIFIC:
-                            /* do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments
-                             * while best batch is less specific
-                             */
-                            if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC)
-                            {
-                                break;
-                            }
-                            if (equivalentMatches == null)
-                            {
-                                bestMatch = method;
-                                bestMatchTypes = methodTypes;
-                                bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                            } else
+                    bestMatches.add(match);
+                }
+                else
+                {
+                    /* filter existing matches */
+                    boolean keepMethod = true;
+                    for (ListIterator<Match> it = bestMatches.listIterator(); keepMethod && it.hasNext();)
+                    {
+                        Match best = it.next();
+                        /* do not retain match if it's more specific than (or incomparable to) provided (unboxed) arguments
+                         * while one of the best matches is less specific
+                         */
+                        if (best.specificity == LESS_SPECIFIC && match.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
+                        {
+                            keepMethod = false;
+                        }
+                        /* drop considered best match if match is less specific than (unboxed) provided args while
+                         * the considered best match is more specific or incomparable
+                         */
+                        else if (match.specificity == LESS_SPECIFIC && best.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
+                        {
+                            it.remove();
+                        }
+                        /* compare methods between them */
+                        else
+                        {
+                            /* but only if some provided args are non null and not Object */
+                            if (!onlyNullOrObjects(args))
                             {
-                                /* have to beat all other ambiguous ones... */
-                                int ambiguities = equivalentMatches.size();
-                                for (int a = 0; a < ambiguities; a++)
+                                switch (compare(match.methodTypes, best.methodTypes))
                                 {
-                                    Method other = (Method) equivalentMatches.get(a);
-                                    switch (compare(methodTypes, other.getParameterTypes()))
-                                    {
-                                        case MORE_SPECIFIC:
-                                        /* ...and thus replace them all...
-                                         * but do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments
-                                         * while best batch is less specific
-                                         */
-                                            if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC)
-                                            {
-                                                break;
-                                            }
-                                            bestMatch = method;
-                                            bestMatchTypes = methodTypes;
-                                            bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                                            equivalentMatches = null;
-                                            ambiguities = 0;
-                                            break;
-
-                                        case INCOMPARABLE:
-                                            /* ...join them...*/
-                                            equivalentMatches.add(method);
-                                            break;
-
-                                        case LESS_SPECIFIC:
-                                            /* retain it anyway if less specific than (unboxed) provided args while
-                                             * bestmatch is more specific
-                                             */
-                                            if (bestMatchComp == MORE_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC)
-                                            {
-                                                bestMatch = method;
-                                                bestMatchTypes = methodTypes;
-                                                bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                                                equivalentMatches = null;
-                                                ambiguities = 0;
-                                            }
-                                            break;
-                                    }
+                                    case LESS_SPECIFIC:
+                                        keepMethod = false;
+                                        break;
+                                    case MORE_SPECIFIC:
+                                        it.remove();
+                                        break;
+                                    case EQUIVALENT:
+                                    case INCOMPARABLE:
+                                        /* compare applicability */
+                                        if (best.applicability > match.applicability)
+                                        {
+                                            keepMethod = false;
+                                        } else if (best.applicability < match.applicability)
+                                        {
+                                            it.remove();
+                                        }
+                                        /* otherwise it's an equivalent match */
+                                        break;
                                 }
                             }
-                            break;
-
-                        case INCOMPARABLE:
-                            /* do not retain method if it's more specific than (or incomparable to) provided (unboxed) arguments
-                             * while best batch is less specific
-                             */
-                            if (bestMatchComp == LESS_SPECIFIC && compare(methodTypes, unboxedArgs) != LESS_SPECIFIC)
-                            {
-                                break;
-                            }
-                            /* retain it anyway if less specific than (unboxed) provided args while
-                             * bestmatch is more specific or incomparable
-                             */
-                            if (bestMatchComp != LESS_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC)
-                            {
-                                bestMatch = method;
-                                bestMatchTypes = methodTypes;
-                                bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                                equivalentMatches = null;
-                                break;
-                            }
-                            if (equivalentMatches == null)
-                            {
-                                equivalentMatches = new ArrayList(bestMatchTypes.length);
-                            }
-                            equivalentMatches.add(method);
-                            break;
-
-                        case LESS_SPECIFIC:
-                            /* retain it anyway if less specific than (unboxed) provided args while
-                             * bestmatch is more specific or incomparable
-                             */
-                            if (bestMatchComp != LESS_SPECIFIC && compare(methodTypes, unboxedArgs) == LESS_SPECIFIC)
-                            {
-                                bestMatch = method;
-                                bestMatchTypes = methodTypes;
-                                bestMatchComp = compare(bestMatchTypes, unboxedArgs);
-                                equivalentMatches = null;
-                            }
-                            break;
+                        }
+                    }
+                    if (keepMethod)
+                    {
+                        bestMatches.add(match);
                     }
                 }
             }
         }
 
-        if (equivalentMatches != null)
+        switch (bestMatches.size())
         {
-            System.out.println("ambiguous: "+equivalentMatches);
-            throw new AmbiguousException();
+            case 0: return null;
+            case 1: return bestMatches.get(0).method;
+            default: throw new AmbiguousException();
         }
-        return bestMatch;
     }
 
     /**
@@ -317,104 +311,172 @@ public class MethodMap
     {
         boolean c1MoreSpecific = false;
         boolean c2MoreSpecific = false;
+        boolean fixedLengths = false;
 
         // compare lengths to handle comparisons where the size of the arrays
         // doesn't match, but the methods are both applicable due to the fact
         // that one is a varargs method
         if (c1.length > c2.length)
         {
-            return MORE_SPECIFIC;
-        }
-        if (c2.length > c1.length)
-        {
-            return LESS_SPECIFIC;
-        }
-
-        // ok, move on and compare those of equal lengths
-        for(int i = 0; i < c1.length; ++i)
-        {
-            if(c1[i] != c2[i] && c1[i] != null && c2[i] != null)
+            int l2 = c2.length;
+            if (l2 == 0)
             {
-                boolean last = (i == c1.length - 1);
-                c1MoreSpecific =
-                    c1MoreSpecific ||
-                    isStrictConvertible(c2[i], c1[i], last) ||
-                    c2[i] == Object.class;//Object is always least-specific
-                c2MoreSpecific =
-                    c2MoreSpecific ||
-                    isStrictConvertible(c1[i], c2[i], last) ||
-                    c1[i] == Object.class;//Object is always least-specific
+                return MORE_SPECIFIC;
             }
+            c2 = Arrays.copyOf(c2, c1.length);
+            Class itemClass = c2[l2 - 1].getComponentType();
+            /* if item class is null, then it implies the vaarg is #1
+             * (and receives an empty array)
+             */
+            if (itemClass == null)
+            {
+                /* by construct, we have c1.length = l2 + 1 */
+                c2[c1.length - 1] = c1[c1.length - 1];
+            }
+            else
+            {
+                for (int i = l2 - 1; i < c1.length; ++i)
+                {
+                /* also overwrite the vaargs itself */
+                    c2[i] = itemClass;
+                }
+            }
+            fixedLengths = true;
         }
-
-        /* check for conversions */
-        if (!c1MoreSpecific && !c2MoreSpecific)
+        else if (c2.length > c1.length)
         {
-            for(int i = 0; i < c1.length; ++i)
+            int l1 = c1.length;
+            if (l1 == 0)
+            {
+                return LESS_SPECIFIC;
+            }
+            c1 = Arrays.copyOf(c1, c2.length);
+            Class itemClass = c1[l1 - 1].getComponentType();
+            /* if item class is null, then it implies the vaarg is #2
+             * (and receives an empty array)
+             */
+            if (itemClass == null)
             {
-                boolean last = (i == c1.length - 1);
-                if (c1[i] != c2[i] && c1[i] != null && c2[i] != null)
+                /* by construct, we have c2.length = l1 + 1 */
+                c1[c2.length - 1] = c2[c2.length - 1];
+            }
+            else
+            {
+                for (int i = l1 - 1; i < c2.length; ++i)
                 {
-                    c1MoreSpecific = c1MoreSpecific || isConvertible(c2[i], c1[i], last);
-                    c2MoreSpecific = c2MoreSpecific || isConvertible(c1[i], c2[i], last);
+                /* also overwrite the vaargs itself */
+                    c1[i] = itemClass;
                 }
             }
+            fixedLengths = true;
         }
 
-        if(c1MoreSpecific)
+        /* ok, move on and compare those of equal lengths */
+        int fromC1toC2 = STRICTLY_CONVERTIBLE;
+        int fromC2toC1 = STRICTLY_CONVERTIBLE;
+        for(int i = 0; i < c1.length; ++i)
         {
-            if(c2MoreSpecific)
+            boolean last = !fixedLengths && (i == c1.length - 1);
+            if (c1[i] != c2[i])
             {
-                /*
-                 * If one method accepts varargs and the other does not,
-                 * call the non-vararg one more specific.
-                 */
-                boolean last1Array = c1[c1.length - 1].isArray();
-                boolean last2Array = c2[c2.length - 1].isArray();
-                if (last1Array && !last2Array)
+                if (c1[i] == null)
                 {
-                    return LESS_SPECIFIC;
+                    fromC2toC1 = NOT_CONVERTIBLE;
+                    if (c2[i].isPrimitive())
+                    {
+                        fromC1toC2 = NOT_CONVERTIBLE;
+                    }
                 }
-                if (!last1Array && last2Array)
+                else if (c2[i] == null)
                 {
-                    return MORE_SPECIFIC;
+                    fromC1toC2 = NOT_CONVERTIBLE;
+                    if (c1[i].isPrimitive())
+                    {
+                        fromC2toC1 = NOT_CONVERTIBLE;
+                    }
+                }
+                else
+                {
+                    switch (fromC1toC2)
+                    {
+                        case STRICTLY_CONVERTIBLE:
+                            if (isStrictConvertible(c2[i], c1[i], last)) break;
+                            fromC1toC2 = IMPLCITLY_CONVERTIBLE;
+                        case IMPLCITLY_CONVERTIBLE:
+                            if (isConvertible(c2[i], c1[i], last)) break;
+                            fromC1toC2 = EXPLICITLY_CONVERTIBLE;
+                        case EXPLICITLY_CONVERTIBLE:
+                            if (isExplicitlyConvertible(c2[i], c1[i], last)) break;
+                            fromC1toC2 = NOT_CONVERTIBLE;
+                    }
+                    switch (fromC2toC1)
+                    {
+                        case STRICTLY_CONVERTIBLE:
+                            if (isStrictConvertible(c1[i], c2[i], last)) break;
+                            fromC2toC1 = IMPLCITLY_CONVERTIBLE;
+                        case IMPLCITLY_CONVERTIBLE:
+                            if (isConvertible(c1[i], c2[i], last)) break;
+                            fromC2toC1 = EXPLICITLY_CONVERTIBLE;
+                        case EXPLICITLY_CONVERTIBLE:
+                            if (isExplicitlyConvertible(c1[i], c2[i], last)) break;
+                            fromC2toC1 = NOT_CONVERTIBLE;
+                    }
                 }
-
-                /*
-                 *  Incomparable due to cross-assignable arguments (i.e.
-                 * foo(String, Object) vs. foo(Object, String))
-                 */
-                return INCOMPARABLE;
             }
+        }
 
-            return MORE_SPECIFIC;
+        if (fromC1toC2 == NOT_CONVERTIBLE && fromC2toC1 == NOT_CONVERTIBLE)
+        {
+            /*
+             *  Incomparable due to cross-assignable arguments (i.e.
+             * foo(String, Foo) vs. foo(Foo, String))
+             */
+            return INCOMPARABLE;
         }
 
-        if(c2MoreSpecific)
+        if (fromC1toC2 > fromC2toC1)
+        {
+            return MORE_SPECIFIC;
+        }
+        else if (fromC2toC1 > fromC1toC2)
         {
             return LESS_SPECIFIC;
         }
-
-        /*
-         * Incomparable due to non-related arguments (i.e.
-         * foo(Runnable) vs. foo(Serializable))
-         */
-
-        return INCOMPARABLE;
+        else
+        {
+            /*
+             * If one method accepts varargs and the other does not,
+             * call the non-vararg one more specific.
+             */
+            boolean last1Array = !fixedLengths && c1[c1.length - 1].isArray();
+            boolean last2Array = !fixedLengths && c2[c2.length - 1].isArray();
+            if (last1Array && !last2Array)
+            {
+                return LESS_SPECIFIC;
+            }
+            if (!last1Array && last2Array)
+            {
+                return MORE_SPECIFIC;
+            }
+        }
+        return EQUIVALENT;
     }
 
     /**
-     * Returns true if the supplied method is applicable to actual
-     * argument types.
+     * Returns the applicability of the supplied method against actual argument types.
      *
      * @param method method that will be called
      * @param classes arguments to method
-     * @return true if method is applicable to arguments
+     * @return the level of applicability:
+     *         0 = not applicable
+     *         1 = explicitly applicable (i.e. using stock or custom conversion handlers)
+     *         2 = implicitly applicable (i.e. using JAva implicit boxing/unboxing and primitive types widening)
+     *         3 = strictly applicable
      */
-    private boolean isApplicable(Method method, Class[] classes)
+    private int getApplicability(Method method, Class[] classes)
     {
         Class[] methodArgs = method.getParameterTypes();
-
+        int ret = STRICTLY_CONVERTIBLE;
         if (methodArgs.length > classes.length)
         {
             // if there's just one more methodArg than class arg
@@ -425,17 +487,27 @@ public class MethodMap
                 // all the args preceding the vararg must match
                 for (int i = 0; i < classes.length; i++)
                 {
-                    if (!isConvertible(methodArgs[i], classes[i], false) &&
-                            !isExplicitlyConvertible(methodArgs[i], classes[i], false))
+                    if (!isStrictConvertible(methodArgs[i], classes[i], false))
                     {
-                        return false;
+                        if (isConvertible(methodArgs[i], classes[i], false))
+                        {
+                            ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+                        }
+                        else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
+                        {
+                            ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+                        }
+                        else
+                        {
+                            return NOT_CONVERTIBLE;
+                        }
                     }
                 }
-                return true;
+                return ret;
             }
             else
             {
-                return false;
+                return NOT_CONVERTIBLE;
             }
         }
         else if (methodArgs.length == classes.length)
@@ -445,21 +517,23 @@ public class MethodMap
             // (e.g. String when the method is expecting String...)
             for(int i = 0; i < classes.length; ++i)
             {
-                if(!isConvertible(methodArgs[i], classes[i], false) &&
-                        !isExplicitlyConvertible(methodArgs[i], classes[i], false))
+                if (!isStrictConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
                 {
-                    // if we're on the last arg and the method expects an array
-                    if (i == classes.length - 1 && methodArgs[i].isArray())
+                    if (isConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+                    {
+                        ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+                    }
+                    else if (isExplicitlyConvertible(methodArgs[i], classes[i], i == classes.length - 1 && methodArgs[i].isArray()))
+                    {
+                        ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+                    }
+                    else
                     {
-                        // check to see if the last arg is convertible
-                        // to the array's component type
-                        return isConvertible(methodArgs[i], classes[i], true) ||
-                                isExplicitlyConvertible(methodArgs[i], classes[i], true);
+                        return NOT_CONVERTIBLE;
                     }
-                    return false;
                 }
             }
-            return true;
+            return ret;
         }
         else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
         {
@@ -467,15 +541,26 @@ public class MethodMap
             Class lastarg = methodArgs[methodArgs.length - 1];
             if (!lastarg.isArray())
             {
-                return false;
+                return NOT_CONVERTIBLE;
             }
 
-            // check that they all match up to the last method arg
+            // check that they all match up to the last method arg component type
             for (int i = 0; i < methodArgs.length - 1; ++i)
             {
-                if (!isConvertible(methodArgs[i], classes[i], false))
+                if (!isStrictConvertible(methodArgs[i], classes[i], false))
                 {
-                    return false;
+                    if (isConvertible(methodArgs[i], classes[i], false))
+                    {
+                        ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+                    }
+                    else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
+                    {
+                        ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+                    }
+                    else
+                    {
+                        return NOT_CONVERTIBLE;
+                    }
                 }
             }
 
@@ -483,14 +568,25 @@ public class MethodMap
             Class vararg = lastarg.getComponentType();
             for (int i = methodArgs.length - 1; i < classes.length; ++i)
             {
-                if (!isConvertible(vararg, classes[i], false))
+                if (!isStrictConvertible(vararg, classes[i], false))
                 {
-                    return false;
+                    if (isConvertible(vararg, classes[i], false))
+                    {
+                        ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+                    }
+                    else if (isExplicitlyConvertible(vararg, classes[i], false))
+                    {
+                        ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+                    }
+                    else
+                    {
+                        return NOT_CONVERTIBLE;
+                    }
                 }
             }
-            return true;
+            return ret;
         }
-        return false;
+        return NOT_CONVERTIBLE;
     }
 
     /**

Modified: velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java?rev=1790680&r1=1790679&r2=1790680&view=diff
==============================================================================
--- velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java (original)
+++ velocity/engine/trunk/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java Sat Apr  8 13:20:55 2017
@@ -4,6 +4,7 @@ import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.test.BaseTestCase;
 import org.apache.velocity.test.misc.TestLogger;
@@ -49,50 +50,33 @@ public class UberspectImplTestCase exten
     }
 
     @Override
-    public void setUp()
-        throws Exception
+    protected void setUpEngine(VelocityEngine engine)
     {
-        Velocity.reset();
-        Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new TestLogger());
-        Velocity.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME,
-            "org.apache.velocity.util.introspection.UberspectImpl");
-        Velocity.init();
+        engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new TestLogger());
+        engine.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME, "org.apache.velocity.util.introspection.UberspectImpl");
     }
 
     @Override
-    public void tearDown()
+    protected void setUpContext(VelocityContext context)
     {
+        context.put("privateClass", new PrivateClass());
+        context.put("privateMethod", new PrivateMethod());
+        context.put("publicMethod", new PublicMethod());
+        context.put("iterable", new SomeIterable());
+        context.put("over", new OverloadedMethods());
     }
 
     public void testPrivateIterator()
         throws Exception
     {
-        VelocityContext context = new VelocityContext();
-        context.put("privateClass", new PrivateClass());
-        context.put("privateMethod", new PrivateMethod());
-        context.put("publicMethod", new PublicMethod());
-        StringWriter writer = new StringWriter();
-
-        Velocity.evaluate(context, writer, "test", "#foreach($i in $privateClass)$i#end");
-        assertEquals(writer.toString(), "");
-
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "#foreach($i in $privateMethod)$i#end");
-        assertEquals(writer.toString(), "");
-
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "#foreach($i in $publicMethod)$i#end");
-        assertEquals(writer.toString(), "123");
+        assertEvalEquals("", "#foreach($i in $privateClass)$i#end");
+        assertEvalEquals("", "#foreach($i in $privateMethod)$i#end");
+        assertEvalEquals("123", "#foreach($i in $publicMethod)$i#end");
     }
 
     public void testIterableForeach()
     {
-        VelocityContext context = new VelocityContext();
-        context.put("iterable", new SomeIterable());
-        StringWriter writer = new StringWriter();
-
-        Velocity.evaluate(context, writer, "test", "#foreach($i in $iterable)$i#end");
-        assertEquals(writer.toString(), "123");
+        assertEvalEquals("123", "#foreach($i in $iterable)$i#end");
     }
 
     private class PrivateClass
@@ -132,26 +116,20 @@ public class UberspectImplTestCase exten
         public String foo() { return "foo0"; }
         public String foo(String arg1) { return "foo1"; }
         public String foo(String arg1, String arg2) { return "foo2"; }
+
+        public String bar(Number n, int i) { return "bar1"; }
+        public String bar(Number n, String s) { return "bar2"; }
     }
 
     public void testOverloadedMethods()
     {
-        VelocityContext context = new VelocityContext();
-        context.put("over", new OverloadedMethods());
-        StringWriter writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "$over.foo()");
-        assertEquals(writer.toString(), "foo0");
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "$over.foo('a')");
-        assertEquals(writer.toString(), "foo1");
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "$over.foo($null)");
-        assertEquals(writer.toString(), "foo1");
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "$over.foo('a', 'b')");
-        assertEquals(writer.toString(), "foo2");
-        writer = new StringWriter();
-        Velocity.evaluate(context, writer, "test", "$over.foo('a', $null)");
-        assertEquals(writer.toString(), "foo2");
+        assertEvalEquals("foo0", "$over.foo()");
+        assertEvalEquals("foo1", "$over.foo('a')");
+        assertEvalEquals("foo1", "$over.foo($null)");
+        assertEvalEquals("foo2", "$over.foo('a', 'b')");
+        assertEvalEquals("foo2", "$over.foo('a', $null)");
+        assertEvalEquals("bar1", "$over.bar(1,1)");
+        assertEvalEquals("$over.bar(1,1.1)", "$over.bar(1,1.1)"); // this one is definitely ambiguous
+        assertEvalEquals("bar2", "$over.bar(1,'1.1')");
     }
 }