changeset 680:66b641f365b5

Rewrite of MethodOverloadResolver
author Adam Domurad <adomurad@redhat.com>
date Tue, 23 Apr 2013 11:10:24 -0400
parents 50295be3e2da
children 1bdcb1e255b5
files ChangeLog plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java tests/netx/unit/sun/applet/MethodOverloadResolverTest.java
diffstat 4 files changed, 791 insertions(+), 623 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Apr 23 10:18:52 2013 -0400
+++ b/ChangeLog	Tue Apr 23 11:10:24 2013 -0400
@@ -1,3 +1,17 @@
+2013-04-23  Adam Domurad  <adomurad@redhat.com>
+
+	Rewrite of MethodOverloadResolver with detailed unittests.
+	* plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java:
+	Rewritten to reduce duplicated code, fix very subtle bugs in
+	never-tested codepaths, obey spec properly. Introduced new helper types
+	where Object[] arrays with special-meaning positions were passed
+	around.
+	* plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java:
+	Updated to work with newly introduced types / refactored overload
+	resolver.
+	* tests/netx/unit/sun/applet/MethodOverloadResolverTest.java: In-depth
+	unit tests of hairy details of method overloading in JS<->Java.
+
 2013-04-23  Omair Majid  <omajid@redhat.com>
 
 	PR1299
--- a/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java	Tue Apr 23 10:18:52 2013 -0400
+++ b/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java	Tue Apr 23 11:10:24 2013 -0400
@@ -41,449 +41,403 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import netscape.javascript.JSObject;
 
 /*
  * This class resolved overloaded methods in Java objects using a cost
- * based-approach as described here:
+ * based-approach described here:
  *
- * http://java.sun.com/javase/6/webnotes/6u10/plugin2/liveconnect/#OVERLOADED_METHODS
+ * http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
  */
 
 public class MethodOverloadResolver {
+    static final int NUMERIC_SAME_COST = 1;
+    static final int NULL_TO_OBJECT_COST = 2;
+    static final int CLASS_SAME_COST = 3;
+    static final int NUMERIC_CAST_COST = 4;
+    static final int NUMERIC_BOOLEAN_COST = 5;
 
-    private static boolean debugging = false;
+    static final int STRING_NUMERIC_CAST_COST = 5;
+
+    static final int CLASS_SUPERCLASS_COST = 6;
+
+    static final int CLASS_STRING_COST = 7;
+    static final int JSOBJECT_TO_ARRAY_COST = CLASS_STRING_COST;
+    static final int ARRAY_CAST_COST = 8;
+
+    /* A method signature with its casted parameters
+     * We pretend a Constructor is a normal 'method' for ease of code reuse */
+    static class ResolvedMethod {
+
+        private java.lang.reflect.AccessibleObject method;
+        private Object[] castedParameters;
+        private int cost;
 
-    public static void main(String[] args) {
-        testMethodResolver();
+        public ResolvedMethod(int cost, java.lang.reflect.AccessibleObject method, Object[] castedParameters) {
+            this.cost = cost;
+            this.method = method;
+            this.castedParameters = castedParameters;
+        }
+
+        java.lang.reflect.AccessibleObject getAccessibleObject() {
+            return method;
+        }
+
+        public Method getMethod() {
+            return (Method)method;
+        }
+
+        public Constructor<?> getConstructor() {
+            return (Constructor<?>)method;
+        }
+
+        public Object[] getCastedParameters() {
+            return castedParameters;
+        }
+
+        public int getCost() {
+            return cost;
+        }
     }
 
-    public static void testMethodResolver() {
-        debugging = true;
-
-        ArrayList<Object[]> list = new ArrayList<Object[]>(20);
-        FooClass fc = new FooClass();
-
-        // Numeric to java primitive
-        // foo_i has Integer and int params
-        String s1 = "foo_string_int(S,I)";
-        String s1a = "foo_string_int(S,S)";
-        Object[] o1 = { fc.getClass(), "foo_string_int", "blah", 42 };
-        list.add(o1);
-        Object[] o1a = { fc.getClass(), "foo_string_int", "blah", "42.42" };
-        list.add(o1a);
-
-        // Null to non-primitive type
-        // foo_i is overloaded with Integer and int
-        String s2 = "foo_string_int(N)";
-        Object[] o2 = { fc.getClass(), "foo_string_int", "blah", null };
-        list.add(o2);
+    /* A cast with an associated 'cost', used for picking method overloads */
+    static class WeightedCast {
 
-        // foo_jsobj is overloaded with JSObject and String params
-        String s3 = "foo_jsobj(LLowCostSignatureComputer/JSObject;)";
-        Object[] o3 = { fc.getClass(), "foo_jsobj", new JSObject() };
-        list.add(o3);
-
-        // foo_classtype is overloaded with Number and Integer
-        String s4 = "foo_classtype(Ljava/lang/Integer;)";
-        Object[] o4 = { fc.getClass(), "foo_classtype", 42 };
-        list.add(o4);
-
-        // foo_multiprim is overloaded with int, long and float types
-        String s5 = "foo_multiprim(I)";
-        String s6 = "foo_multiprim(F)";
-        String s6a = "foo_multiprim(D)";
-
-        Object[] o5 = { fc.getClass(), "foo_multiprim", new Integer(42) };
-        Object[] o6 = { fc.getClass(), "foo_multiprim", new Float(42.42) };
-        Object[] o6a = { fc.getClass(), "foo_multiprim", new Double(42.42) };
-        list.add(o5);
-        list.add(o6);
-        list.add(o6a);
-
-        // foo_float has float, String and JSObject type
-        String s7 = "foo_float(I)";
-        Object[] o7 = { fc.getClass(), "foo_float", new Integer(42) };
-        list.add(o7);
+        private int cost;
+        private Object castedObject;
 
-        // foo_multiprim(float) is what this should convert
-        String s8 = "foo_float(S)";
-        Object[] o8 = { fc.getClass(), "foo_float", "42" };
-        list.add(o8);
-
-        // foo_class is overloaded with BarClass 2 and 3
-        String s9 = "foo_class(LLowCostSignatureComputer/BarClass3;)";
-        Object[] o9 = { fc.getClass(), "foo_class", new BarClass3() };
-        list.add(o9);
-
-        // foo_strandbyteonly takes string and byte
-        String s10 = "foo_strandbyteonly(I)";
-        Object[] o10 = { fc.getClass(), "foo_strandbyteonly", 42 };
-        list.add(o10);
-
-        // JSOBject to string
-        String s11 = "foo_strandbyteonly(LLowCostSignatureComputer/JSObject;)";
-        Object[] o11 = { fc.getClass(), "foo_strandbyteonly", new JSObject() };
-        list.add(o11);
+        public WeightedCast(int cost, Object castedObject) {
+            this.cost = cost;
+            this.castedObject = castedObject;
+        }
 
-        // jsobject to string and int to float
-        String s12 = "foo_str_and_float(S,I)";
-        Object[] o12 = { fc.getClass(), "foo_str_and_float", new JSObject(), new Integer(42) };
-        list.add(o12);
-
-        // call for which no match will be found
-        String s13 = "foo_int_only(JSObject)";
-        Object[] o13 = { fc.getClass(), "foo_int_only", new JSObject() };
-        list.add(o13);
-
-        // method with no args
-        String s14 = "foo_noargs()";
-        Object[] o14 = { fc.getClass(), "foo_noargs" };
-        list.add(o14);
-
-        // method which takes a primitive bool, given a Boolean
-        String s15 = "foo_boolonly()";
-        Object[] o15 = { fc.getClass(), "foo_boolonly", new Boolean(true) };
-        list.add(o15);
-
-        for (Object[] o : list) {
-            Object[] methodAndArgs = getMatchingMethod(o);
-            if (debugging)
-                if (methodAndArgs != null)
-                    System.out.println("Best match: " + methodAndArgs[0] + "\n");
-                else
-                    System.out.println("No match found.\n");
-
+        public Object getCastedObject() {
+            return castedObject;
         }
 
+        public int getCost() {
+            return cost;
+        }
+    }
+
+
+    public static ResolvedMethod getBestMatchMethod(Class<?> c, String methodName, Object[] args) {
+        Method[] matchingMethods = getMatchingMethods(c, methodName, args.length);
+
+        if (PluginDebug.DEBUG) { /* avoid toString if not needed */
+            PluginDebug.debug("getMatchingMethod called with: "
+                    + Arrays.toString(args));
+        } 
+
+        return getBestOverloadMatch(c, args, matchingMethods);
+    }
+
+    public static ResolvedMethod getBestMatchConstructor(Class<?> c, Object[] args) {
+        Constructor<?>[] matchingConstructors = getMatchingConstructors(c, args.length);
+
+        if (PluginDebug.DEBUG) { /* avoid toString if not needed */
+            PluginDebug.debug("getMatchingConstructor called with: "
+                    + Arrays.toString(args));
+        }
+
+        return getBestOverloadMatch(c, args, matchingConstructors);
     }
 
     /*
-     * Cost based overload resolution algorithm based on cost rules specified here:
-     *
-     * http://java.sun.com/javase/6/webnotes/6u10/plugin2/liveconnect/#OVERLOADED_METHODS
+     * Get best-matching method based on a cost based overload resolution
+     * algorithm is used, described here:
+     * 
+     * http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+     * 
+     * Note that we consider Constructor's to be 'methods' for convenience. We
+     * use the common parent class of Method/Constructor, 'AccessibleObject'
+     * 
+     * NB: Although the spec specifies that ambiguous method calls (ie, same
+     * cost) should throw errors, we simply pick the first overload for
+     * simplicity. Method overrides should not be doing wildly different things
+     * anyway.
      */
-
-    public static Object[] getMatchingMethod(Object[] callList) {
-        Object[] ret = null;
-        Class<?> c = (Class<?>) callList[0];
-        String methodName = (String) callList[1];
-
-        Method[] matchingMethods = getMatchingMethods(c, methodName, callList.length - 2);
-
-        if (debugging)
-            System.out.println("getMatchingMethod called with: " + printList(callList));
+    static ResolvedMethod getBestOverloadMatch(Class<?> c, Object[] args,
+            java.lang.reflect.AccessibleObject[] candidates) {
 
         int lowestCost = Integer.MAX_VALUE;
-
-        for (Method matchingMethod : matchingMethods) {
-
-            int methodCost = 0;
-            Class[] paramTypes = matchingMethod.getParameterTypes();
-            Object[] methodAndArgs = new Object[paramTypes.length + 1];
-            methodAndArgs[0] = matchingMethod;
-
-            // Figure out which of the matched methods best represents what we
-            // want
-            for (int i = 0; i < paramTypes.length; i++) {
-                Class<?> paramTypeClass = paramTypes[i];
-                Object suppliedParam = callList[i + 2];
-                Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam
-                        .getClass()
-                        : null;
-
-                Object[] costAndCastedObj = getCostAndCastedObject(
-                        suppliedParam, paramTypeClass);
-                methodCost += (Integer) costAndCastedObj[0];
-
-                if ((Integer) costAndCastedObj[0] < 0)
-                    break;
-
-                Object castedObj = paramTypeClass.isPrimitive() ? costAndCastedObj[1]
-                        : paramTypeClass.cast(costAndCastedObj[1]);
-                methodAndArgs[i + 1] = castedObj;
-
-                Class<?> castedObjClass = castedObj == null ? null : castedObj
-                        .getClass();
-                Boolean castedObjIsPrim = castedObj == null ? null : castedObj
-                        .getClass().isPrimitive();
+        java.lang.reflect.AccessibleObject cheapestMethod = null;
+        Object[] cheapestArgs = null;
+        boolean ambiguous = false;
 
-                if (debugging)
-                    System.out.println("Param " + i + " of method "
-                            + matchingMethod + " has cost "
-                            + (Integer) costAndCastedObj[0]
-                            + " original param type " + suppliedParamClass
-                            + " casted to " + castedObjClass + " isPrimitive="
-                            + castedObjIsPrim + " value " + castedObj);
-            }
-
-            if ((methodCost > 0 && methodCost < lowestCost) ||
-                    paramTypes.length == 0) {
-                ret = methodAndArgs;
-                lowestCost = methodCost;
-            }
-
-        }
+        methodLoop:
+        for (java.lang.reflect.AccessibleObject candidate : candidates) {
+            int methodCost = 0;
 
-        return ret;
-    }
-
-    public static Object[] getMatchingConstructor(Object[] callList) {
-        Object[] ret = null;
-        Class<?> c = (Class<?>) callList[0];
-
-        Constructor[] matchingConstructors = getMatchingConstructors(c, callList.length - 1);
-
-        if (debugging)
-            System.out.println("getMatchingConstructor called with: " + printList(callList));
-
-        int lowestCost = Integer.MAX_VALUE;
-
-        for (Constructor matchingConstructor : matchingConstructors) {
-
-            int constructorCost = 0;
-            Class<?>[] paramTypes = matchingConstructor.getParameterTypes();
-            Object[] constructorAndArgs = new Object[paramTypes.length + 1];
-            constructorAndArgs[0] = matchingConstructor;
+            Class<?>[] paramTypes = getParameterTypesFor(candidate);
+            Object[] castedArgs = new Object[paramTypes.length];
 
             // Figure out which of the matched methods best represents what we
             // want
             for (int i = 0; i < paramTypes.length; i++) {
                 Class<?> paramTypeClass = paramTypes[i];
-                Object suppliedParam = callList[i + 1];
-                Class suppliedParamClass = suppliedParam != null ? suppliedParam
-                        .getClass()
-                        : null;
+                Object suppliedParam = args[i];
+                Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam
+                        .getClass() : null;
 
-                Object[] costAndCastedObj = getCostAndCastedObject(
+                WeightedCast weightedCast = getCostAndCastedObject(
                         suppliedParam, paramTypeClass);
-                constructorCost += (Integer) costAndCastedObj[0];
 
-                if ((Integer) costAndCastedObj[0] < 0)
-                    break;
+                if (weightedCast == null) {
+                    continue methodLoop; // Cannot call this constructor!
+                }
+
+                methodCost += weightedCast.getCost();
 
-                Object castedObj = paramTypeClass.isPrimitive() ? costAndCastedObj[1]
-                        : paramTypeClass.cast(costAndCastedObj[1]);
-                constructorAndArgs[i + 1] = castedObj;
+                Object castedObj = paramTypeClass.isPrimitive() ? 
+                            weightedCast.getCastedObject() 
+                          : paramTypeClass.cast(weightedCast.getCastedObject());
+
+                castedArgs[i] = castedObj;
 
-                Class<?> castedObjClass = castedObj == null ? null : castedObj
-                        .getClass();
-                Boolean castedObjIsPrim = castedObj == null ? null : castedObj
-                        .getClass().isPrimitive();
+                Class<?> castedObjClass = castedObj == null ? null : castedObj.getClass();
+                boolean castedObjIsPrim = castedObj == null ? false : castedObj.getClass().isPrimitive();
 
-                if (debugging)
-                    System.out.println("Param " + i + " of constructor "
-                            + matchingConstructor + " has cost "
-                            + (Integer) costAndCastedObj[0]
+                if (PluginDebug.DEBUG) { /* avoid toString if not needed */
+                    PluginDebug.debug("Param " + i + " of method " + candidate
+                            + " has cost " + weightedCast.getCost()
                             + " original param type " + suppliedParamClass
                             + " casted to " + castedObjClass + " isPrimitive="
                             + castedObjIsPrim + " value " + castedObj);
+                }
             }
 
-            if ((constructorCost > 0 && constructorCost < lowestCost) ||
-                    paramTypes.length == 0) {
-                ret = constructorAndArgs;
-                lowestCost = constructorCost;
+            if (methodCost <= lowestCost) {
+                if (methodCost < lowestCost
+                        || argumentsAreSubclassesOf(castedArgs, cheapestArgs)) {
+                    lowestCost = methodCost;
+                    cheapestArgs = castedArgs;
+                    cheapestMethod = candidate;
+                    ambiguous = false;
+                } else {
+                    ambiguous = true;
+                }
             }
+
         }
 
-        return ret;
-    }
-
-    public static Object[] getCostAndCastedObject(Object suppliedParam, Class<?> paramTypeClass) {
-
-        Object[] ret = new Object[2];
-        Integer cost = new Integer(0);
-        Object castedObj;
-
-        Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam.getClass() : null;
-
-        // Either both are an array, or neither are
-        boolean suppliedParamIsArray = suppliedParamClass != null && suppliedParamClass.isArray();
-        if (paramTypeClass.isArray() != suppliedParamIsArray &&
-                !paramTypeClass.equals(Object.class) &&
-                !paramTypeClass.equals(String.class)) {
-            ret[0] = Integer.MIN_VALUE; // Not allowed
-            ret[1] = suppliedParam;
-            return ret;
+        // The spec says we should error out if the method call is ambiguous
+        // Instead we will report it in debug output
+        if (ambiguous) {
+            PluginDebug.debug("*** Warning: Ambiguous overload of ", c.getClass(), "#", cheapestMethod, "!");
         }
 
-        // If param type is an array, supplied obj must be an array, Object or String (guaranteed by checks above)
-        // If it is an array, we need to copy/cast as we scan the array
-        // If it an object, we return "as is" [Everything can be narrowed to an object, cost=6]
-        // If it is a string, we need to convert according to the JS engine rules
+        return new ResolvedMethod(lowestCost, cheapestMethod, cheapestArgs);
+    }
 
-        if (paramTypeClass.isArray()) {
-
-            Object newArray = Array.newInstance(paramTypeClass.getComponentType(), Array.getLength(suppliedParam));
-            for (int i = 0; i < Array.getLength(suppliedParam); i++) {
-                Object original = Array.get(suppliedParam, i);
+    public static WeightedCast getCostAndCastedObject(Object suppliedParam,
+            Class<?> paramTypeClass) {
+        Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam
+                .getClass() : null;
 
-                // When dealing with arrays, we represent empty slots with
-                // null. We need to convert this to 0 before recursive
-                // calling, since normal transformation does not allow
-                // null -> primitive
-
-                if (original == null && paramTypeClass.getComponentType().isPrimitive())
-                    original = 0;
+        boolean suppliedParamIsArray = suppliedParamClass != null
+                && suppliedParamClass.isArray();
 
-                Object[] costAndCastedObject = getCostAndCastedObject(original, paramTypeClass.getComponentType());
-
-                if ((Integer) costAndCastedObject[0] < 0) {
-                    ret[0] = Integer.MIN_VALUE; // Not allowed
-                    ret[1] = suppliedParam;
-                    return ret;
-                }
-
-                Array.set(newArray, i, costAndCastedObject[1]);
+        if (suppliedParamIsArray) {
+            if (paramTypeClass.isArray()) {
+                return getArrayToArrayCastWeightedCost(suppliedParam,
+                        paramTypeClass);
             }
 
-            ret[0] = 9;
-            ret[1] = newArray;
-            return ret;
-        }
-
-        if (suppliedParamIsArray && paramTypeClass.equals(String.class)) {
-
-            ret[0] = 9;
-            ret[1] = getArrayAsString(suppliedParam);
-            return ret;
+            // Target type must be an array, Object or String
+            // If it an object, we return "as is" [Everything can be narrowed to an
+            // object, cost=CLASS_SUPERCLASS_COST]
+            // If it is a string, we need to convert according to the JS engine
+            // rules
+            if (paramTypeClass != String.class
+                    && paramTypeClass != Object.class) {
+                return null;
+            }
+            if (paramTypeClass.equals(String.class)) {
+                return new WeightedCast(ARRAY_CAST_COST,
+                        arrayToJavascriptStyleString(suppliedParam));
+            }
         }
 
         // If this is null, there are only 2 possible cases
         if (suppliedParamClass == null) {
-            castedObj = null; // if value is null.. well, it is null
-
             if (!paramTypeClass.isPrimitive()) {
-                cost += 2; // Null to any non-primitive type
-            } else {
-                cost = Integer.MIN_VALUE; // Null to primitive not allowed
+                return new WeightedCast(NULL_TO_OBJECT_COST, null); // Null to any non-primitive type
             }
-        } else if (paramTypeClass.isPrimitive() && paramTypeClass.equals(getPrimitive(suppliedParam))) {
-            cost += 1; // Numeric type to the analogous Java primitive type
-            castedObj = suppliedParam; // Let auto-boxing handle it
-        } else if (suppliedParamClass.equals(paramTypeClass)) {
-            cost += 3; // Class type to Class type where the types are equal
-            castedObj = suppliedParam;
-        } else if (isNum(suppliedParam) &&
-                       (paramTypeClass.isPrimitive() ||
-                               java.lang.Number.class.isAssignableFrom(paramTypeClass) ||
-                               java.lang.Character.class.isAssignableFrom(paramTypeClass) ||
-                        java.lang.Byte.class.isAssignableFrom(paramTypeClass)
-                       )) {
-            cost += 4; // Numeric type to a different primitive type
-
-            if (suppliedParam.toString().equals("true"))
-                suppliedParam = "1";
-            else if (suppliedParam.toString().equals("false"))
-                suppliedParam = "0";
+            return null;// Null to primitive not allowed
+        }
 
-            if (paramTypeClass.equals(Boolean.TYPE))
-                castedObj = getNum(suppliedParam.toString(), paramTypeClass).doubleValue() != 0D;
-            else if (paramTypeClass.equals(Character.TYPE))
-                castedObj = (char) Short.decode(suppliedParam.toString()).shortValue();
-            else
-                castedObj = getNum(suppliedParam.toString(), paramTypeClass);
-        } else if (suppliedParam instanceof java.lang.String &&
-                    isNum(suppliedParam) &&
-                        (paramTypeClass.isInstance(java.lang.Number.class) ||
-                                paramTypeClass.isInstance(java.lang.Character.class) ||
-                                paramTypeClass.isInstance(java.lang.Byte.class) ||
-                         paramTypeClass.isPrimitive())) {
-            cost += 5; // String to numeric type
-
-            if (suppliedParam.toString().equals("true"))
-                suppliedParam = "1";
-            else if (suppliedParam.toString().equals("false"))
-                suppliedParam = "0";
+        // Numeric type to the analogous Java primitive type
+        if (paramTypeClass.isPrimitive()
+                && paramTypeClass == getPrimitiveType(suppliedParam.getClass())) {
+            return new WeightedCast(NUMERIC_SAME_COST, suppliedParam);
 
-            if (paramTypeClass.equals(Character.TYPE))
-                castedObj = (char) Short.decode(suppliedParam.toString()).shortValue();
-            else
-                castedObj = getNum(suppliedParam.toString(), paramTypeClass);
-        } else if (suppliedParam instanceof java.lang.String &&
-                     (paramTypeClass.equals(java.lang.Boolean.class) ||
-                      paramTypeClass.equals(java.lang.Boolean.TYPE))) {
-
-            cost += 5; // Same cost as above
-            castedObj = new Boolean(suppliedParam.toString().length() > 0);
-        } else if (paramTypeClass.isAssignableFrom(suppliedParamClass)) {
-            cost += 6; // Class type to superclass type;
-            castedObj = paramTypeClass.cast(suppliedParam);
-        } else if (paramTypeClass.equals(String.class)) {
-            cost += 7; // Any Java value to String
-            castedObj = suppliedParam.toString();
-        } else if (suppliedParam instanceof JSObject &&
-                   paramTypeClass.isArray()) {
-            cost += 8; // JSObject to Java array
-            castedObj = (JSObject) suppliedParam;
-        } else {
-            cost = Integer.MIN_VALUE; // Not allowed
-            castedObj = suppliedParam;
         }
 
-        ret[0] = cost;
-        ret[1] = castedObj;
+        // Class type to Class type where the types are the same
+        if (suppliedParamClass == paramTypeClass) {
+            return new WeightedCast(CLASS_SAME_COST, suppliedParam);
+
+        } 
+
+        // Numeric type to a different primitive type
+        boolean wrapsPrimitive = (getPrimitiveType(suppliedParam.getClass()) != null);
+        if (wrapsPrimitive && paramTypeClass.isPrimitive()) {
+            double val;
+
+            // Coerce booleans
+            if (suppliedParam.equals(Boolean.TRUE)) {
+                val = 1.0;
+            } else if (suppliedParam.equals(Boolean.FALSE)){
+                val = 0.0;
+            } else if (suppliedParam instanceof Character) {
+                val = (double)(Character)suppliedParam;
+            } else {
+                val = ((Number)suppliedParam).doubleValue();
+            }
+
+            int castCost = NUMERIC_CAST_COST;
+            Object castedObj;
+            if (paramTypeClass.equals(Boolean.TYPE)) {
+                castedObj = (val != 0D && !Double.isNaN(val));
 
-        return ret;
+                if (suppliedParam.getClass() != Boolean.class) {
+                    castCost = NUMERIC_BOOLEAN_COST;
+                }
+            } else {
+                castedObj = toBoxedPrimitiveType(val, paramTypeClass);
+            }
+            return new WeightedCast(castCost, castedObj);
+        } 
+
+        // Numeric string to numeric type
+        if (isNumericString(suppliedParam) && paramTypeClass.isPrimitive()) {
+            Object castedObj;
+            if (paramTypeClass.equals(Character.TYPE)) {
+                castedObj = (char) Short.decode((String)suppliedParam).shortValue();
+            } else {
+                castedObj = stringAsPrimitiveType((String)suppliedParam, paramTypeClass);
+            }
+            return new WeightedCast(STRING_NUMERIC_CAST_COST, castedObj);
+        } 
 
+        // Same cost as above
+        if (suppliedParam instanceof java.lang.String
+                && (paramTypeClass == java.lang.Boolean.class || paramTypeClass == java.lang.Boolean.TYPE)) {
+            return new WeightedCast(STRING_NUMERIC_CAST_COST, !suppliedParam.equals(""));
+        }
+
+        // Class type to superclass type;
+        if (paramTypeClass.isAssignableFrom(suppliedParamClass)) {
+            return new WeightedCast(CLASS_SUPERCLASS_COST, paramTypeClass.cast(suppliedParam));
+        }
+
+        // Any java value to String
+        if (paramTypeClass.equals(String.class)) {
+            return new WeightedCast(CLASS_STRING_COST, suppliedParam.toString());
+        }
+
+        // JSObject to Java array
+        if (suppliedParam instanceof JSObject
+                && paramTypeClass.isArray()) {
+            return new WeightedCast(JSOBJECT_TO_ARRAY_COST, suppliedParam);
+        }
+
+        return null;
     }
 
-    private static Method[] getMatchingMethods(Class<?> c, String name, int paramCount) {
-        Method[] allMethods = c.getMethods();
-        ArrayList<Method> matchingMethods = new ArrayList<Method>(5);
+    private static WeightedCast getArrayToArrayCastWeightedCost(Object suppliedArray,
+            Class<?> paramTypeClass) {
+
+        int arrLength = Array.getLength(suppliedArray);
+        Class<?> arrType = paramTypeClass.getComponentType();
+
+        // If it is an array, we need to copy/cast as we scan the array
+        Object newArray = Array.newInstance(arrType, arrLength);
+
+        for (int i = 0; i < arrLength; i++) {
+            Object original = Array.get(suppliedArray, i);
+
+            // When dealing with arrays, we represent empty slots with
+            // null. We need to convert this to 0 before recursive
+            // calling, since normal transformation does not allow
+            // null -> primitive
+
+            if (original == null && arrType.isPrimitive()) {
+                original = 0;
+            }
 
-        for (Method m : allMethods) {
-            if (m.getName().equals(name) && m.getParameterTypes().length == paramCount)
-                matchingMethods.add(m);
+            WeightedCast costAndCastedObject = getCostAndCastedObject(original,
+                    paramTypeClass.getComponentType());
+
+            if (costAndCastedObject == null) {
+                return null;
+            }
+
+            Array.set(newArray, i, costAndCastedObject.getCastedObject());
+        }
+
+        return new WeightedCast(ARRAY_CAST_COST, newArray);
+    }
+
+    private static Method[] getMatchingMethods(Class<?> c, String name,
+            int paramCount) {
+        List<Method> matchingMethods = new ArrayList<Method>();
+
+        for (Method m : c.getMethods()) {
+            if (m.getName().equals(name)) {
+                if (m.getParameterTypes().length == paramCount) {
+                    matchingMethods.add(m);
+                }
+            }
         }
 
         return matchingMethods.toArray(new Method[0]);
     }
 
-    private static Constructor[] getMatchingConstructors(Class<?> c, int paramCount) {
-        Constructor[] allConstructors = c.getConstructors();
-        ArrayList<Constructor> matchingConstructors = new ArrayList<Constructor>(5);
+    private static Constructor<?>[] getMatchingConstructors(Class<?> c,
+            int paramCount) {
+        List<Constructor<?>> matchingConstructors = new ArrayList<Constructor<?>>();
 
-        for (Constructor cs : allConstructors) {
-            if (cs.getParameterTypes().length == paramCount)
+        for (Constructor<?> cs : c.getConstructors()) {
+            if (cs.getParameterTypes().length == paramCount) {
                 matchingConstructors.add(cs);
+            }
         }
 
         return matchingConstructors.toArray(new Constructor[0]);
     }
 
-    private static Class getPrimitive(Object o) {
-
-        if (o instanceof java.lang.Byte) {
-            return java.lang.Byte.TYPE;
-        } else if (o instanceof java.lang.Character) {
-            return java.lang.Character.TYPE;
-        } else if (o instanceof java.lang.Short) {
-            return java.lang.Short.TYPE;
-        } else if (o instanceof java.lang.Integer) {
-            return java.lang.Integer.TYPE;
-        } else if (o instanceof java.lang.Long) {
-            return java.lang.Long.TYPE;
-        } else if (o instanceof java.lang.Float) {
-            return java.lang.Float.TYPE;
-        } else if (o instanceof java.lang.Double) {
-            return java.lang.Double.TYPE;
-        } else if (o instanceof java.lang.Boolean) {
-            return java.lang.Boolean.TYPE;
+    private static Class<?> getPrimitiveType(Class<?> c) {
+        if (c.isPrimitive()) {
+            return c;
         }
 
-        return o.getClass();
+        if (c == Byte.class) {
+            return Byte.TYPE;
+        } else if (c == Character.class) {
+            return Character.TYPE;
+        } else if (c == Short.class) {
+            return Short.TYPE;
+        } else if (c == Integer.class) {
+            return Integer.TYPE;
+        } else if (c == Long.class) {
+            return Long.TYPE;
+        } else if (c == Float.class) {
+            return Float.TYPE;
+        } else if (c == Double.class) {
+            return Double.TYPE;
+        } else if (c == Boolean.class) {
+            return Boolean.TYPE;
+        } else {
+            return null;
+        }
     }
 
-    private static boolean isNum(Object o) {
-
-        if (o instanceof java.lang.Number)
-            return true;
-
-        // Boolean is changeable to number as well
-        if (o instanceof java.lang.Boolean)
-            return true;
-
+    private static boolean isNumericString(Object o) {
         // At this point, it _has_ to be a string else automatically
         // return false
         if (!(o instanceof java.lang.String))
@@ -504,239 +458,78 @@
         return false;
     }
 
-    private static Number getNum(String s, Class<?> c) throws NumberFormatException {
-
-        Number n;
-        if (s.contains("."))
-            n = new Double(s);
-        else
-            n = new Long(s);
+    private static Object toBoxedPrimitiveType(double val, Class<?> c) {
+        Class<?> prim = getPrimitiveType(c);
 
         // See if we need to collapse first
-        if (c.equals(java.lang.Integer.class) ||
-                c.equals(java.lang.Integer.TYPE)) {
-            return n.intValue();
+        if (prim == Integer.TYPE) {
+            return (int)val;
+        } else if (prim == Long.TYPE) {
+            return (long)val;
+        } else if (prim == Short.TYPE) {
+            return (short)val;
+        } else if (prim == Float.TYPE) {
+            return (float)val;
+        } else if (prim == Double.TYPE) {
+            return val;
+        } else if (prim == Byte.TYPE) {
+            return (byte)val;
+        } else if (prim == Character.TYPE) {
+            return (char)(short)val;
         }
-
-        if (c.equals(java.lang.Long.class) ||
-                c.equals(java.lang.Long.TYPE)) {
-            return n.longValue();
-        }
-
-        if (c.equals(java.lang.Short.class) ||
-                c.equals(java.lang.Short.TYPE)) {
-            return n.shortValue();
-        }
+        return val;
+    }
 
-        if (c.equals(java.lang.Float.class) ||
-                c.equals(java.lang.Float.TYPE)) {
-            return n.floatValue();
-        }
+    private static Object stringAsPrimitiveType(String s, Class<?> c)
+            throws NumberFormatException {
+        double val = Double.parseDouble(s);
+        return toBoxedPrimitiveType(val, c);
 
-        if (c.equals(java.lang.Double.class) ||
-                c.equals(java.lang.Double.TYPE)) {
-            return n.doubleValue();
-        }
-
-        if (c.equals(java.lang.Byte.class) ||
-                c.equals(java.lang.Byte.TYPE)) {
-            return n.byteValue();
-        }
-
-        return n;
     }
 
-    private static String printList(Object[] oList) {
-
-        String ret = "";
-
-        ret += "{ ";
-        for (Object o : oList) {
-
-            String oStr = o != null ? o.toString() + " [" + o.getClass() + "]" : "null";
-
-            ret += oStr;
-            ret += ", ";
+    // Test whether we can get from 'args' to 'testArgs' only by using widening conversions,
+    // eg String -> Object
+    private static boolean argumentsAreSubclassesOf(Object[] args, Object[] testArgs) {
+        for (int i = 0; i < args.length; i++) {
+            if (!testArgs[i].getClass().isAssignableFrom(args[i].getClass())) {
+                return false;
+            }
         }
-        ret = ret.substring(0, ret.length() - 2); // remove last ", "
-        ret += " }";
-
-        return ret;
+        return true;
     }
 
-    private static String getArrayAsString(Object array) {
-        // We are guaranteed that supplied object is a String
+    static Class<?>[] getParameterTypesFor(java.lang.reflect.AccessibleObject method) {
+        if (method instanceof Method) {
+            return ((Method)method).getParameterTypes();
+        } else /*m instanceof Constructor*/ {
+            return ((Constructor<?>)method).getParameterTypes();
+        }
+    }
 
-        String ret = new String();
+    private static String arrayToJavascriptStyleString(Object array) {
+        int arrLength = Array.getLength(array);
 
-        for (int i = 0; i < Array.getLength(array); i++) {
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < arrLength; i++) {
             Object element = Array.get(array, i);
 
             if (element != null) {
                 if (element.getClass().isArray()) {
-                    ret += getArrayAsString(element);
+                    sb.append(arrayToJavascriptStyleString(element));
                 } else {
-                    ret += element;
+                    sb.append(element);
                 }
             }
 
-            ret += ",";
+            sb.append(',');
         }
 
         // Trim the final ","
-        if (ret.length() > 0) {
-            ret = ret.substring(0, ret.length() - 1);
+        if (arrLength > 0) {
+            sb.setLength(sb.length() - 1);
         }
 
-        return ret;
-    }
-}
-
-/** Begin test classes **/
-
-class FooClass {
-
-    public FooClass() {
-    }
-
-    public FooClass(Boolean b, int i) {
-    }
-
-    public FooClass(Boolean b, Integer i) {
-    }
-
-    public FooClass(Boolean b, short s) {
-    }
-
-    public FooClass(String s, int i) {
-    }
-
-    public FooClass(String s, Integer i) {
-    }
-
-    public FooClass(java.lang.Number num) {
-    }
-
-    public FooClass(java.lang.Integer integer) {
-    }
-
-    public FooClass(long l) {
-    }
-
-    public FooClass(double d) {
-    }
-
-    public FooClass(float f) {
-    }
-
-    public FooClass(JSObject j) {
-    }
-
-    public FooClass(BarClass1 b) {
-    }
-
-    public FooClass(BarClass2 b) {
-    }
-
-    public FooClass(String s) {
-    }
-
-    public FooClass(byte b) {
-    }
-
-    public FooClass(String s, Float f) {
-    }
-
-    public FooClass(int i) {
-    }
-
-    public void FooClass() {
-    }
-
-    public void FooClass(boolean b) {
-    }
-
-    public void foo(Boolean b, int i) {
-    }
-
-    public void foo(Boolean b, Integer i) {
-    }
-
-    public void foo(Boolean b, short s) {
+        return sb.toString();
     }
-
-    public void foo_string_int(String s, int i) {
-    }
-
-    public void foo_string_int(String s, Integer i) {
-    }
-
-    public void foo_jsobj(JSObject j) {
-    }
-
-    public void foo_jsobj(String s) {
-    }
-
-    public void foo_classtype(java.lang.Number num) {
-    }
-
-    public void foo_classtype(java.lang.Integer integer) {
-    }
-
-    public void foo_multiprim(int i) {
-    }
-
-    public void foo_multiprim(long l) {
-    }
-
-    public void foo_multiprim(float f) {
-    }
-
-    public void foo_multiprim(double d) {
-    }
-
-    public void foo_float(float f) {
-    }
-
-    public void foo_float(String s) {
-    }
-
-    public void foo_float(JSObject j) {
-    }
-
-    public void foo_class(BarClass1 b) {
-    }
-
-    public void foo_class(BarClass2 b) {
-    }
-
-    public void foo_strandbyteonly(String s) {
-    }
-
-    public void foo_strandbyteonly(byte b) {
-    }
-
-    public void foo_str_and_float(String s, Float f) {
-    }
-
-    public void foo_int_only(int i) {
-    }
-
-    public void foo_noargs() {
-    }
-
-    public void foo_boolonly(boolean b) {
-    }
-}
-
-class BarClass1 {
-}
-
-class BarClass2 extends BarClass1 {
-}
-
-class BarClass3 extends BarClass2 {
-}
-
-class JSObject {
-}
+}
\ No newline at end of file
--- a/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java	Tue Apr 23 10:18:52 2013 -0400
+++ b/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java	Tue Apr 23 11:10:24 2013 -0400
@@ -535,7 +535,7 @@
                 final Object o = store.getObject(classOrObjectID);
                 final Field f = (Field) store.getObject(fieldID);
 
-                final Object fValue = MethodOverloadResolver.getCostAndCastedObject(value, f.getType())[1];
+                final Object fValue = MethodOverloadResolver.getCostAndCastedObject(value, f.getType()).getCastedObject();
 
                 AccessControlContext acc = callContext != null ? callContext : getClosedAccessControlContext();
                 checkPermission(src, message.startsWith("SetStaticField") ? (Class) o : o.getClass(), acc);
@@ -577,7 +577,7 @@
                 Object value = store.getObject(objectID);
 
                 // Cast the object to appropriate type before insertion
-                value = MethodOverloadResolver.getCostAndCastedObject(value, store.getObject(arrayID).getClass().getComponentType())[1];
+                value = MethodOverloadResolver.getCostAndCastedObject(value, store.getObject(arrayID).getClass().getComponentType()).getCastedObject();
 
                 Array.set(store.getObject(arrayID), index, value);
 
@@ -640,36 +640,26 @@
                     c = (Class<?>) store.getObject(objectID);
                 }
 
-                // length -3 to discard first 3, + 2 for holding object
-                // and method name
-                Object[] arguments = new Object[args.length - 1];
-                arguments[0] = c;
-                arguments[1] = methodName;
-                for (int i = 0; i < args.length - 3; i++) {
-                    arguments[i + 2] = store.getObject(parseCall(args[3 + i], null, Integer.class));
-                    PluginDebug.debug("GOT ARG: ", arguments[i + 2]);
+                // Discard first 3 parts of message
+                Object[] arguments = new Object[args.length - 3];
+                for (int i = 0; i < arguments.length; i++) {
+                    arguments[i] = store.getObject(parseCall(args[3 + i], null, Integer.class));
+                    PluginDebug.debug("GOT ARG: ", arguments[i]);
                 }
 
-                Object[] matchingMethodAndArgs = MethodOverloadResolver.getMatchingMethod(arguments);
+                MethodOverloadResolver.ResolvedMethod rm = 
+                        MethodOverloadResolver.getBestMatchMethod(c, methodName, arguments);
 
-                if (matchingMethodAndArgs == null) {
+                if (rm == null) {
                     write(reference, "Error: No suitable method named " + methodName + " with matching args found");
                     return;
                 }
 
-                final Method m = (Method) matchingMethodAndArgs[0];
-                Object[] castedArgs = new Object[matchingMethodAndArgs.length - 1];
-                for (int i = 0; i < castedArgs.length; i++) {
-                    castedArgs[i] = matchingMethodAndArgs[i + 1];
-                }
-
-                String collapsedArgs = "";
-                for (Object arg : castedArgs) {
-                    collapsedArgs += " " + arg;
-                }
+                final Method m = rm.getMethod();
+                final Object[] castedArgs = rm.getCastedParameters();
 
                 PluginDebug.debug("Calling method ", m, " on object ", o
-                                                , " (", c, ") with ", collapsedArgs);
+                                                , " (", c, ") with ", Arrays.toString(castedArgs));
 
                 AccessControlContext acc = callContext != null ? callContext : getClosedAccessControlContext();
                 checkPermission(src, c, acc);
@@ -699,9 +689,9 @@
                     retO = m.getReturnType().toString();
                 }
 
-                PluginDebug.debug("Calling ", m, " on ", o, " with "
-                                                , collapsedArgs, " and that returned: ", ret
-                                                , " of type ", retO);
+                PluginDebug.debug("Calling ", m, " on ", o, " with ", 
+                        Arrays.toString(castedArgs), " and that returned: ", ret,
+                        " of type ", retO);
 
                 String objIDStr = toObjectIDString(ret, m.getReturnType(), false /*do not unbox primitives*/);
                 write(reference, "CallMethod " + objIDStr);
@@ -944,42 +934,30 @@
             } else if (message.startsWith("NewObject")) {
                 String[] args = message.split(" ");
                 Integer classID = parseCall(args[1], null, Integer.class);
-                Class c = (Class) store.getObject(classID);
-                final Constructor cons;
-                final Object[] fArguments;
+                Class<?> c = (Class<?>) store.getObject(classID);
 
-                Object[] arguments = new Object[args.length - 1];
-                arguments[0] = c;
-                for (int i = 0; i < args.length - 2; i++) {
-                    arguments[i + 1] = store.getObject(parseCall(args[2 + i],
+                // Discard first 2 parts of message
+                Object[] arguments = new Object[args.length - 2];
+                for (int i = 0; i < arguments.length; i++) {
+                    arguments[i] = store.getObject(parseCall(args[2 + i],
                             null, Integer.class));
-                    PluginDebug.debug("GOT ARG: ", arguments[i + 1]);
+                    PluginDebug.debug("GOT ARG: ", arguments[i]);
                 }
 
-                Object[] matchingConstructorAndArgs = MethodOverloadResolver
-                        .getMatchingConstructor(arguments);
+                MethodOverloadResolver.ResolvedMethod resolvedConstructor = 
+                        MethodOverloadResolver.getBestMatchConstructor(c, arguments);
 
-                if (matchingConstructorAndArgs == null) {
+                if (resolvedConstructor == null) {
                     write(reference,
                             "Error: No suitable constructor with matching args found");
                     return;
                 }
 
-                Object[] castedArgs = new Object[matchingConstructorAndArgs.length - 1];
-                for (int i = 0; i < castedArgs.length; i++) {
-                    castedArgs[i] = matchingConstructorAndArgs[i + 1];
-                }
-
-                cons = (Constructor) matchingConstructorAndArgs[0];
-                fArguments = castedArgs;
-
-                String collapsedArgs = "";
-                for (Object arg : fArguments) {
-                    collapsedArgs += " " + arg.toString();
-                }
+                final Constructor<?> cons = resolvedConstructor.getConstructor();
+                final Object[] castedArgs = resolvedConstructor.getCastedParameters();
 
                 PluginDebug.debug("Calling constructor on class ", c,
-                                   " with ", collapsedArgs);
+                                   " with ", Arrays.toString(castedArgs));
 
                 AccessControlContext acc = callContext != null ? callContext : getClosedAccessControlContext();
                 checkPermission(src, c, acc);
@@ -987,7 +965,7 @@
                 Object ret = AccessController.doPrivileged(new PrivilegedAction<Object>() {
                     public Object run() {
                         try {
-                            return cons.newInstance(fArguments);
+                            return cons.newInstance(castedArgs);
                         } catch (Throwable t) {
                             return t;
                         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/sun/applet/MethodOverloadResolverTest.java	Tue Apr 23 11:10:24 2013 -0400
@@ -0,0 +1,383 @@
+package sun.applet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeSet;
+
+import net.sourceforge.jnlp.ServerAccess;
+import netscape.javascript.JSObject;
+
+import org.junit.Test;
+
+import sun.applet.MethodOverloadResolver.ResolvedMethod;
+import sun.applet.MethodOverloadResolver.WeightedCast;
+
+public class MethodOverloadResolverTest {
+    
+    /**************************************************************************
+     * MethodOverloadResolver.getCostAndCastedObject tests                    *
+     **************************************************************************/
+
+    // Helper methods
+
+    // Helper class for overload order tests
+    static class CandidateCast implements Comparable<CandidateCast>{
+        public CandidateCast(int cost, Class<?> candidate) {
+            this.cost = cost;
+            this.candidate = candidate;
+        }
+        public int getCost() {
+            return cost;
+        }
+        
+        public Class<?> getCandidate() {
+            return candidate;
+        }
+
+        @Override
+        public int compareTo(CandidateCast other) {
+            return cost > other.cost ? +1 : -1;
+        }
+        
+        private int cost;
+        private Class<?> candidate;
+    }
+
+    // asserts that these overloads have the given order of preference
+    // and that none of the costs are equal
+    static private void assertOverloadOrder(Object arg, Class<?>... orderedCandidates) {   
+        String argClassName = arg.getClass().getSimpleName();
+        TreeSet<CandidateCast> casts = new TreeSet<CandidateCast>();
+
+        for (Class<?> candidate : orderedCandidates) {
+            WeightedCast wc = MethodOverloadResolver.getCostAndCastedObject(arg, candidate);
+
+            assertFalse("Expected valid overload from " + argClassName + " to "
+                    + candidate.getSimpleName(), wc == null);
+
+            // Check previous candidates, _should not_ be 'ambiguous', ie this cost == other cost
+            for (CandidateCast cc : casts) {
+                String failureString = "Unexpected ambiguity overloading " 
+                        + argClassName + " between "
+                        + candidate.getSimpleName() + " and " 
+                        + cc.candidate.getSimpleName()
+                        + " with cost " + cc.cost +"!";
+
+                assertFalse(failureString, cc.cost == wc.getCost());
+            }
+
+            casts.add(new CandidateCast(wc.getCost(), candidate));
+        }
+
+        Class<?>[] actualOrder = new Class<?>[casts.size()];
+
+        int n = 0;
+        for (CandidateCast cc : casts) {
+            actualOrder[n] = cc.candidate;
+            ServerAccess.logOutputReprint(arg.getClass().getSimpleName() + " to "
+                    + cc.candidate.getSimpleName() + " has cost " + cc.cost);
+            n++;
+        }
+
+        assertArrayEquals(orderedCandidates, actualOrder);
+    }
+
+    // Asserts that the given overloads are all of same cost
+    static private void assertInvalidOverloads(Object arg, Class<?>... candidates) {   
+        for (Class<?> candidate : candidates) {
+            WeightedCast wc = MethodOverloadResolver.getCostAndCastedObject(arg, candidate);
+
+            int cost = (wc != null ? wc.getCost() : 0); // Avoid NPE on non-failure
+            String argClassName = arg == null ? "null" : arg.getClass().getSimpleName();
+
+            assertTrue("Expected to be unable to cast "
+                    + argClassName + " to "
+                    + candidate.getSimpleName()
+                    + " but was able to with cost " + cost + "!",
+                    wc == null);
+        }
+    }
+
+    static private void assertNotPrimitiveCastable(Object arg) {
+        assertInvalidOverloads(arg, Double.TYPE, Float.TYPE, Long.TYPE,
+                Short.TYPE, Byte.TYPE, Character.TYPE);
+    }
+
+    static private void assertNotNumberCastable(Object arg) {
+        assertNotPrimitiveCastable(arg);
+        assertInvalidOverloads(arg, Double.class, Float.class, Long.class,
+                Short.class, Byte.class, Character.class);
+    }
+
+    // Asserts that the given overloads are all of same cost
+    static private void assertAmbiguousOverload(Object arg, Class<?>... candidates) {  
+        String argClassName = arg == null ? "null" : arg.getClass().getSimpleName(); 
+        List<CandidateCast> casts = new ArrayList<CandidateCast>();
+
+        for (Class<?> candidate : candidates) {
+            WeightedCast wc = MethodOverloadResolver.getCostAndCastedObject(arg, candidate);
+
+            assertFalse("Expected valid overload from " + argClassName + " to "
+                    + candidate.getSimpleName(), wc == null);
+
+            // Check previous candidates, _should_ all 'ambiguous', ie this cost == other cost
+            for (CandidateCast cc : casts) {
+                String failureString = "Expected ambiguity " 
+                        + argClassName + " between "
+                        + candidate.getSimpleName() + " and " 
+                        + cc.candidate.getSimpleName() 
+                        + ", got costs " + wc.getCost() + " and " + cc.cost + "!";
+
+                assertTrue(failureString, cc.cost == wc.getCost());
+            }
+
+            casts.add(new CandidateCast(wc.getCost(), candidate));
+        }
+    }
+
+    // Test methods
+
+    
+    @Test
+    public void testBooleanOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+
+        assertOverloadOrder(new Boolean(false), Boolean.TYPE, Boolean.class,
+                Double.TYPE, Object.class, String.class);
+    }
+
+    @Test
+    public void testNumberOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+
+        assertAmbiguousOverload(new Double(0), Integer.TYPE, Long.TYPE,
+                Short.TYPE, Byte.TYPE, Character.TYPE);
+
+        assertOverloadOrder(new Double(0), Double.TYPE, Double.class,
+                Float.TYPE, Boolean.TYPE, Object.class, String.class);
+    }
+
+    @Test
+    public void testStringOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+
+        assertAmbiguousOverload("1", Double.TYPE, Float.TYPE, Integer.TYPE,
+                Long.TYPE, Short.TYPE, Byte.TYPE);
+
+        assertOverloadOrder("1.0", String.class, Double.TYPE, Object.class);
+    }
+
+    // Turned off until JSObject is unit-testable (privilege problem)
+//    @Test
+    public void testJSObjectOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+        assertOverloadOrder(new JSObject(0L), JSObject.class, String.class);
+        assertAmbiguousOverload(new JSObject(0L), Object[].class, String.class);
+    }
+
+    @Test
+    public void testNullOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+
+        assertNotPrimitiveCastable(null);
+        assertAmbiguousOverload(null, Object.class, String.class);
+    }
+
+    @Test
+    public void testInheritanceOverloading() {
+        // based on http://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
+
+        class FooParent {}
+        class FooChild extends FooParent {}
+        class FooChildOfChild extends FooChild {}
+
+        assertNotNumberCastable(new FooChildOfChild());
+
+        // NB: this is ambiguious as far as costs are concerned, however
+        // MethodOverloadResolver.getBestOverloadMatch sorts out this ambiguity
+        assertAmbiguousOverload(new FooChildOfChild(), FooChild.class,
+                FooParent.class, Object.class);
+
+        assertOverloadOrder(new FooChild(), FooChild.class, FooParent.class, String.class);
+    }
+
+    /**************************************************************************
+     * MethodOverloadResolver.getMatchingMethod tests                         *
+     **************************************************************************/
+
+    // Helper methods
+
+    // Convenient representation of resulting method signature
+    static private String simpleSignature(java.lang.reflect.AccessibleObject m) {
+        StringBuilder sb = new StringBuilder();
+
+        for (Class<?> c : MethodOverloadResolver.getParameterTypesFor(m)) {
+            sb.append(c.getSimpleName());
+            sb.append(", ");
+        }
+        sb.setLength(sb.length() - 2); // Trim last ", "
+
+        return sb.toString();
+    }
+
+    static private Object[] args(Class<?> klazz, Object... params) {
+        List<Object> objects = new ArrayList<Object>();
+        objects.add(klazz);
+        // assumes our method test name is "testmethod"
+        objects.add("testmethod");
+        objects.addAll(Arrays.asList(params));
+        return objects.toArray( new Object[0]);
+    }
+
+    static private void assertExpectedOverload(Object[] params,
+            String expectedSignature, int expectedCost) {
+        Class<?> c = (Class<?>)params[0];
+        String methodName = (String)params[1];
+        Object[] args = Arrays.copyOfRange(params, 2, params.length);
+
+        ResolvedMethod result = MethodOverloadResolver.getBestMatchMethod(c, methodName, args);
+
+        // Check signature array as string for convenience
+        assertEquals(expectedSignature, simpleSignature(result.getAccessibleObject()));
+        assertEquals(expectedCost, result.getCost());
+    }
+
+    // Test methods
+
+    @Test
+    public void testMultipleArgResolve() {
+
+        @SuppressWarnings("unused")
+        abstract class MultipleArg {
+            public abstract void testmethod(String s, int i);
+            public abstract void testmethod(String s, Integer i);
+        }
+
+        // Numeric to java primitive
+        assertExpectedOverload(
+                args( MultipleArg.class, "teststring", 1 ), 
+                "String, int",
+                MethodOverloadResolver.CLASS_SAME_COST + MethodOverloadResolver.NUMERIC_SAME_COST);
+
+        // String to java primitive
+        assertExpectedOverload(
+                args( MultipleArg.class, "teststring", "1.1" ), 
+                "String, int",
+                MethodOverloadResolver.CLASS_SAME_COST + MethodOverloadResolver.STRING_NUMERIC_CAST_COST);
+
+        // Null to non-primitive type
+        assertExpectedOverload(
+                args( MultipleArg.class, "teststring", (Object)null ), 
+                "String, Integer",
+                MethodOverloadResolver.CLASS_SAME_COST + MethodOverloadResolver.NULL_TO_OBJECT_COST);
+    }
+
+    @Test
+    public void testBoxedNumberResolve() {
+
+        @SuppressWarnings("unused")
+        abstract class BoxedNumber {
+            public abstract void testmethod(Number n);
+            public abstract void testmethod(Integer i);
+        }
+ 
+        assertExpectedOverload(
+                args( BoxedNumber.class, 1), 
+                "Integer", MethodOverloadResolver.CLASS_SAME_COST);
+
+        assertExpectedOverload(
+                args( BoxedNumber.class, (short)1), 
+                "Number", MethodOverloadResolver.CLASS_SUPERCLASS_COST);
+    }
+
+    @Test
+    public void testPrimitivesResolve() {
+
+        @SuppressWarnings("unused")
+        abstract class Primitives {
+            public abstract void testmethod(int i);
+            public abstract void testmethod(long l);
+            public abstract void testmethod(float f);
+            public abstract void testmethod(double d);
+        }
+
+        assertExpectedOverload(
+                args( Primitives.class, 1), 
+                "int", MethodOverloadResolver.NUMERIC_SAME_COST);
+ 
+
+        assertExpectedOverload(
+                args( Primitives.class, 1L), 
+                "long", MethodOverloadResolver.NUMERIC_SAME_COST);
+ 
+        assertExpectedOverload(
+                args( Primitives.class, 1.1f), 
+                "float", MethodOverloadResolver.NUMERIC_SAME_COST);
+ 
+        assertExpectedOverload(
+                args( Primitives.class, 1.1), 
+                "double", MethodOverloadResolver.NUMERIC_SAME_COST);
+    }
+
+    @Test
+    public void testComplexResolve() {
+
+        @SuppressWarnings("unused")
+        abstract class Complex {
+            public abstract void testmethod(float f);
+            public abstract void testmethod(String s);
+            public abstract void testmethod(JSObject j);
+        }
+
+        assertExpectedOverload(
+                args( Complex.class, 1), 
+                "float", MethodOverloadResolver.NUMERIC_CAST_COST);
+ 
+
+        assertExpectedOverload(
+                args( Complex.class, "1"), 
+                "String", MethodOverloadResolver.CLASS_SAME_COST);
+ 
+        assertExpectedOverload(
+                args( Complex.class, 1.1f), 
+                "float", MethodOverloadResolver.NUMERIC_SAME_COST);
+
+        // This test is commented out until JSObject can be unit tested (privilege problem)
+//        assertExpectedOverload(
+//                args( Complex.class, new JSObject(0L)), 
+//                "JSObject", MethodOverloadResolver.CLASS_SAME_COST);
+    }
+
+    @Test
+    public void testInheritanceResolve() {
+
+        class FooParent {}
+        class FooChild extends FooParent {}
+        class FooChildOfChild extends FooChild {}
+
+        abstract class Inheritance {
+            public abstract void testmethod(FooParent fp);
+            public abstract void testmethod(FooChild fc);
+        }
+
+        assertExpectedOverload(
+                args( Inheritance.class, new FooParent()), 
+                "FooParent", MethodOverloadResolver.CLASS_SAME_COST);
+ 
+
+        assertExpectedOverload(
+                args( Inheritance.class, new FooChild()), 
+                "FooChild", MethodOverloadResolver.CLASS_SAME_COST);
+ 
+        assertExpectedOverload(
+                args( Inheritance.class, new FooChildOfChild()), 
+                "FooChild", MethodOverloadResolver.CLASS_SUPERCLASS_COST);
+    }
+
+}