aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Domurad <[email protected]>2013-04-23 11:10:24 -0400
committerAdam Domurad <[email protected]>2013-04-23 11:10:24 -0400
commit3c710de15296fd7f16b144586791be134129663f (patch)
treec1bcc1f573f2c687f6e70ceaa0b515ad1a267c3e
parent1a1586b380400ab42bc0cf52c9a240225195b95a (diff)
Rewrite of MethodOverloadResolver
-rw-r--r--ChangeLog14
-rw-r--r--plugin/icedteanp/java/sun/applet/MethodOverloadResolver.java895
-rw-r--r--plugin/icedteanp/java/sun/applet/PluginAppletSecurityContext.java80
-rw-r--r--tests/netx/unit/sun/applet/MethodOverloadResolverTest.java383
4 files changed, 770 insertions, 602 deletions
diff --git a/ChangeLog b/ChangeLog
index e1970b8..bd98264 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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);
+ }
+
+}