diff options
author | Adam Domurad <[email protected]> | 2013-04-23 11:10:24 -0400 |
---|---|---|
committer | Adam Domurad <[email protected]> | 2013-04-23 11:10:24 -0400 |
commit | 3c710de15296fd7f16b144586791be134129663f (patch) | |
tree | c1bcc1f573f2c687f6e70ceaa0b515ad1a267c3e | |
parent | 1a1586b380400ab42bc0cf52c9a240225195b95a (diff) |
Rewrite of MethodOverloadResolver
-rw-r--r-- | ChangeLog | 14 | ||||
-rw-r--r-- | plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java | 895 | ||||
-rw-r--r-- | plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java | 80 | ||||
-rw-r--r-- | tests/netx/unit/sun/applet/MethodOverloadResolverTest.java | 383 |
4 files changed, 770 insertions, 602 deletions
@@ -1,3 +1,17 @@ +2013-04-23 Adam Domurad <[email protected]> + + 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 <[email protected]> PR1299 diff --git a/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java b/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java index f4e6850..8fb2989 100644 --- a/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java +++ b/plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java @@ -41,449 +41,403 @@ import java.lang.reflect.Array; 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; - public static void main(String[] args) { - testMethodResolver(); - } + 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 { - 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); - - // 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); - - // 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); - - // 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"); + private java.lang.reflect.AccessibleObject method; + private Object[] castedParameters; + private int cost; + 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; + } - /* - * Cost based overload resolution algorithm based on cost rules specified here: - * - * http://java.sun.com/javase/6/webnotes/6u10/plugin2/liveconnect/#OVERLOADED_METHODS - */ + public Method getMethod() { + return (Method)method; + } - public static Object[] getMatchingMethod(Object[] callList) { - Object[] ret = null; - Class<?> c = (Class<?>) callList[0]; - String methodName = (String) callList[1]; + public Constructor<?> getConstructor() { + return (Constructor<?>)method; + } - Method[] matchingMethods = getMatchingMethods(c, methodName, callList.length - 2); + public Object[] getCastedParameters() { + return castedParameters; + } - if (debugging) - System.out.println("getMatchingMethod called with: " + printList(callList)); + public int getCost() { + return cost; + } + } - int lowestCost = Integer.MAX_VALUE; + /* A cast with an associated 'cost', used for picking method overloads */ + static class WeightedCast { - for (Method matchingMethod : matchingMethods) { + private int cost; + private Object castedObject; - int methodCost = 0; - Class[] paramTypes = matchingMethod.getParameterTypes(); - Object[] methodAndArgs = new Object[paramTypes.length + 1]; - methodAndArgs[0] = matchingMethod; + public WeightedCast(int cost, Object castedObject) { + this.cost = cost; + this.castedObject = castedObject; + } - // 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; + public Object getCastedObject() { + return castedObject; + } - Object[] costAndCastedObj = getCostAndCastedObject( - suppliedParam, paramTypeClass); - methodCost += (Integer) costAndCastedObj[0]; + public int getCost() { + return cost; + } + } - if ((Integer) costAndCastedObj[0] < 0) - break; - Object castedObj = paramTypeClass.isPrimitive() ? costAndCastedObj[1] - : paramTypeClass.cast(costAndCastedObj[1]); - methodAndArgs[i + 1] = castedObj; + public static ResolvedMethod getBestMatchMethod(Class<?> c, String methodName, Object[] args) { + Method[] matchingMethods = getMatchingMethods(c, methodName, args.length); - Class<?> castedObjClass = castedObj == null ? null : castedObj - .getClass(); - Boolean castedObjIsPrim = castedObj == null ? null : castedObj - .getClass().isPrimitive(); + if (PluginDebug.DEBUG) { /* avoid toString if not needed */ + PluginDebug.debug("getMatchingMethod called with: " + + Arrays.toString(args)); + } - 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); - } + return getBestOverloadMatch(c, args, matchingMethods); + } - if ((methodCost > 0 && methodCost < lowestCost) || - paramTypes.length == 0) { - ret = methodAndArgs; - lowestCost = methodCost; - } + 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 ret; + return getBestOverloadMatch(c, args, matchingConstructors); } - 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)); + /* + * 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. + */ + static ResolvedMethod getBestOverloadMatch(Class<?> c, Object[] args, + java.lang.reflect.AccessibleObject[] candidates) { int lowestCost = Integer.MAX_VALUE; + java.lang.reflect.AccessibleObject cheapestMethod = null; + Object[] cheapestArgs = null; + boolean ambiguous = false; - for (Constructor matchingConstructor : matchingConstructors) { + methodLoop: + for (java.lang.reflect.AccessibleObject candidate : candidates) { + int methodCost = 0; - 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()); - Class<?> castedObjClass = castedObj == null ? null : castedObj - .getClass(); - Boolean castedObjIsPrim = castedObj == null ? null : castedObj - .getClass().isPrimitive(); + castedArgs[i] = castedObj; - if (debugging) - System.out.println("Param " + i + " of constructor " - + matchingConstructor + " has cost " - + (Integer) costAndCastedObj[0] + Class<?> castedObjClass = castedObj == null ? null : castedObj.getClass(); + boolean castedObjIsPrim = castedObj == null ? false : castedObj.getClass().isPrimitive(); + + 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; + } } + + } + + // 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, "!"); } - return ret; + return new ResolvedMethod(lowestCost, cheapestMethod, cheapestArgs); } - public static Object[] getCostAndCastedObject(Object suppliedParam, Class<?> paramTypeClass) { + public static WeightedCast getCostAndCastedObject(Object suppliedParam, + Class<?> paramTypeClass) { + Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam + .getClass() : null; - Object[] ret = new Object[2]; - Integer cost = new Integer(0); - Object castedObj; + boolean suppliedParamIsArray = suppliedParamClass != null + && suppliedParamClass.isArray(); - Class<?> suppliedParamClass = suppliedParam != null ? suppliedParam.getClass() : null; + if (suppliedParamIsArray) { + if (paramTypeClass.isArray()) { + return getArrayToArrayCastWeightedCost(suppliedParam, + paramTypeClass); + } - // 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; + // 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 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 + // If this is null, there are only 2 possible cases + if (suppliedParamClass == null) { + if (!paramTypeClass.isPrimitive()) { + return new WeightedCast(NULL_TO_OBJECT_COST, null); // Null to any non-primitive type + } + return null;// Null to primitive not allowed + } - if (paramTypeClass.isArray()) { + // Numeric type to the analogous Java primitive type + if (paramTypeClass.isPrimitive() + && paramTypeClass == getPrimitiveType(suppliedParam.getClass())) { + return new WeightedCast(NUMERIC_SAME_COST, suppliedParam); - Object newArray = Array.newInstance(paramTypeClass.getComponentType(), Array.getLength(suppliedParam)); - for (int i = 0; i < Array.getLength(suppliedParam); i++) { - Object original = Array.get(suppliedParam, 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 + // Class type to Class type where the types are the same + if (suppliedParamClass == paramTypeClass) { + return new WeightedCast(CLASS_SAME_COST, suppliedParam); - if (original == null && paramTypeClass.getComponentType().isPrimitive()) - original = 0; + } - Object[] costAndCastedObject = getCostAndCastedObject(original, paramTypeClass.getComponentType()); + // Numeric type to a different primitive type + boolean wrapsPrimitive = (getPrimitiveType(suppliedParam.getClass()) != null); + if (wrapsPrimitive && paramTypeClass.isPrimitive()) { + double val; - if ((Integer) costAndCastedObject[0] < 0) { - ret[0] = Integer.MIN_VALUE; // Not allowed - ret[1] = suppliedParam; - return ret; - } + // 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)); - Array.set(newArray, i, costAndCastedObject[1]); + 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); + } - ret[0] = 9; - ret[1] = newArray; - return ret; + // 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("")); } - if (suppliedParamIsArray && paramTypeClass.equals(String.class)) { + // Class type to superclass type; + if (paramTypeClass.isAssignableFrom(suppliedParamClass)) { + return new WeightedCast(CLASS_SUPERCLASS_COST, paramTypeClass.cast(suppliedParam)); + } - ret[0] = 9; - ret[1] = getArrayAsString(suppliedParam); - return ret; + // Any java value to String + if (paramTypeClass.equals(String.class)) { + return new WeightedCast(CLASS_STRING_COST, suppliedParam.toString()); } - // If this is null, there are only 2 possible cases - if (suppliedParamClass == null) { - castedObj = null; // if value is null.. well, it is null + // JSObject to Java array + if (suppliedParam instanceof JSObject + && paramTypeClass.isArray()) { + return new WeightedCast(JSOBJECT_TO_ARRAY_COST, suppliedParam); + } - if (!paramTypeClass.isPrimitive()) { - cost += 2; // Null to any non-primitive type - } else { - cost = Integer.MIN_VALUE; // Null to primitive not allowed + return null; + } + + 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; } - } 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"; - - 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"; - - 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; + WeightedCast costAndCastedObject = getCostAndCastedObject(original, + paramTypeClass.getComponentType()); - return ret; + 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) { - Method[] allMethods = c.getMethods(); - ArrayList<Method> matchingMethods = new ArrayList<Method>(5); + private static Method[] getMatchingMethods(Class<?> c, String name, + int paramCount) { + List<Method> matchingMethods = new ArrayList<Method>(); - for (Method m : allMethods) { - if (m.getName().equals(name) && m.getParameterTypes().length == paramCount) - matchingMethods.add(m); + 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 @@ public class MethodOverloadResolver { 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 (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(); + 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; } + 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(); + // 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; + } } - - return n; + return true; } - 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 += ", "; + static Class<?>[] getParameterTypesFor(java.lang.reflect.AccessibleObject method) { + if (method instanceof Method) { + return ((Method)method).getParameterTypes(); + } else /*m instanceof Constructor*/ { + return ((Constructor<?>)method).getParameterTypes(); } - ret = ret.substring(0, ret.length() - 2); // remove last ", " - ret += " }"; - - return ret; } - private static String getArrayAsString(Object array) { - // We are guaranteed that supplied object is a String + private static String arrayToJavascriptStyleString(Object array) { + int arrLength = Array.getLength(array); - String ret = new String(); + StringBuilder sb = new StringBuilder(); - for (int i = 0; i < Array.getLength(array); i++) { + 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) { + return sb.toString(); } - - public void foo(Boolean b, Integer i) { - } - - public void foo(Boolean b, short s) { - } - - 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 diff --git a/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java b/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java index ec4dd47..697833d 100644 --- a/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java +++ b/plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java @@ -535,7 +535,7 @@ public class PluginAppletSecurityContext { 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 @@ public class PluginAppletSecurityContext { 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 @@ public class PluginAppletSecurityContext { 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 @@ public class PluginAppletSecurityContext { 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 @@ public class PluginAppletSecurityContext { } 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 @@ public class PluginAppletSecurityContext { Object ret = AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { try { - return cons.newInstance(fArguments); + return cons.newInstance(castedArgs); } catch (Throwable t) { return t; } diff --git a/tests/netx/unit/sun/applet/MethodOverloadResolverTest.java b/tests/netx/unit/sun/applet/MethodOverloadResolverTest.java new file mode 100644 index 0000000..8fefe26 --- /dev/null +++ b/tests/netx/unit/sun/applet/MethodOverloadResolverTest.java @@ -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); + } + +} |