/** Type-munging for field setting and method invocation. Conforms to LC3 specification */ static Object coerceTypeImpl(Class<?> type, Object value) { if (value != null && value.getClass() == type) { return value; } switch (getJSTypeCode(value)) { case JSTYPE_NULL: // raise error if type.isPrimitive() if (type.isPrimitive()) { reportConversionError(value, type); } return null; case JSTYPE_UNDEFINED: if (type == ScriptRuntime.StringClass || type == ScriptRuntime.ObjectClass) { return "undefined"; } else { reportConversionError("undefined", type); } break; case JSTYPE_BOOLEAN: // Under LC3, only JS Booleans can be coerced into a Boolean value if (type == Boolean.TYPE || type == ScriptRuntime.BooleanClass || type == ScriptRuntime.ObjectClass) { return value; } else if (type == ScriptRuntime.StringClass) { return value.toString(); } else { reportConversionError(value, type); } break; case JSTYPE_NUMBER: if (type == ScriptRuntime.StringClass) { return ScriptRuntime.toString(value); } else if (type == ScriptRuntime.ObjectClass) { return coerceToNumber(Double.TYPE, value); } else if ((type.isPrimitive() && type != Boolean.TYPE) || ScriptRuntime.NumberClass.isAssignableFrom(type)) { return coerceToNumber(type, value); } else { reportConversionError(value, type); } break; case JSTYPE_STRING: if (type == ScriptRuntime.StringClass || type.isInstance(value)) { return value; } else if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) { // Special case for converting a single char string to a // character // Placed here because it applies *only* to JS strings, // not other JS objects converted to strings if (((String) value).length() == 1) { return new Character(((String) value).charAt(0)); } else { return coerceToNumber(type, value); } } else if ((type.isPrimitive() && type != Boolean.TYPE) || ScriptRuntime.NumberClass.isAssignableFrom(type)) { return coerceToNumber(type, value); } else { reportConversionError(value, type); } break; case JSTYPE_JAVA_CLASS: if (value instanceof Wrapper) { value = ((Wrapper) value).unwrap(); } if (type == ScriptRuntime.ClassClass || type == ScriptRuntime.ObjectClass) { return value; } else if (type == ScriptRuntime.StringClass) { return value.toString(); } else { reportConversionError(value, type); } break; case JSTYPE_JAVA_OBJECT: case JSTYPE_JAVA_ARRAY: if (value instanceof Wrapper) { value = ((Wrapper) value).unwrap(); } if (type.isPrimitive()) { if (type == Boolean.TYPE) { reportConversionError(value, type); } return coerceToNumber(type, value); } else { if (type == ScriptRuntime.StringClass) { return value.toString(); } else { if (type.isInstance(value)) { return value; } else { reportConversionError(value, type); } } } break; case JSTYPE_OBJECT: if (type == ScriptRuntime.StringClass) { return ScriptRuntime.toString(value); } else if (type.isPrimitive()) { if (type == Boolean.TYPE) { reportConversionError(value, type); } return coerceToNumber(type, value); } else if (type.isInstance(value)) { return value; } else if (type == ScriptRuntime.DateClass && value instanceof NativeDate) { double time = ((NativeDate) value).getJSTimeValue(); // XXX: This will replace NaN by 0 return new Date((long) time); } else if (type.isArray() && value instanceof NativeArray) { // Make a new java array, and coerce the JS array components // to the target (component) type. NativeArray array = (NativeArray) value; long length = array.getLength(); Class<?> arrayType = type.getComponentType(); Object Result = Array.newInstance(arrayType, (int) length); for (int i = 0; i < length; ++i) { try { Array.set(Result, i, coerceType(arrayType, array.get(i, array))); } catch (EvaluatorException ee) { reportConversionError(value, type); } } return Result; } else if (value instanceof Wrapper) { value = ((Wrapper) value).unwrap(); if (type.isInstance(value)) return value; reportConversionError(value, type); } else if (type.isInterface() && value instanceof Callable) { // Try to use function as implementation of Java interface. // // XXX: Currently only instances of ScriptableObject are // supported since the resulting interface proxies should // be reused next time conversion is made and generic // Callable has no storage for it. Weak references can // address it but for now use this restriction. if (value instanceof ScriptableObject) { ScriptableObject so = (ScriptableObject) value; Object key = Kit.makeHashKeyFromPair(COERCED_INTERFACE_KEY, type); Object old = so.getAssociatedValue(key); if (old != null) { // Function was already wrapped return old; } Context cx = Context.getContext(); Object glue = InterfaceAdapter.create(cx, type, (Callable) value); // Store for later retrival glue = so.associateValue(key, glue); return glue; } reportConversionError(value, type); } else { reportConversionError(value, type); } break; } return value; }
/** * Derive a ranking based on how "natural" the conversion is. The special value CONVERSION_NONE * means no conversion is possible, and CONVERSION_NONTRIVIAL signals that more type conformance * testing is required. Based on <a * href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">"preferred method * conversions" from Live Connect 3</a> */ static int getConversionWeight(Object fromObj, Class<?> to) { int fromCode = getJSTypeCode(fromObj); switch (fromCode) { case JSTYPE_UNDEFINED: if (to == ScriptRuntime.StringClass || to == ScriptRuntime.ObjectClass) { return 1; } break; case JSTYPE_NULL: if (!to.isPrimitive()) { return 1; } break; case JSTYPE_BOOLEAN: // "boolean" is #1 if (to == Boolean.TYPE) { return 1; } else if (to == ScriptRuntime.BooleanClass) { return 2; } else if (to == ScriptRuntime.ObjectClass) { return 3; } else if (to == ScriptRuntime.StringClass) { return 4; } break; case JSTYPE_NUMBER: if (to.isPrimitive()) { if (to == Double.TYPE) { return 1; } else if (to != Boolean.TYPE) { return 1 + getSizeRank(to); } } else { if (to == ScriptRuntime.StringClass) { // native numbers are #1-8 return 9; } else if (to == ScriptRuntime.ObjectClass) { return 10; } else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) { // "double" is #1 return 2; } } break; case JSTYPE_STRING: if (to == ScriptRuntime.StringClass) { return 1; } else if (to.isInstance(fromObj)) { return 2; } else if (to.isPrimitive()) { if (to == Character.TYPE) { return 3; } else if (to != Boolean.TYPE) { return 4; } } break; case JSTYPE_JAVA_CLASS: if (to == ScriptRuntime.ClassClass) { return 1; } else if (to == ScriptRuntime.ObjectClass) { return 3; } else if (to == ScriptRuntime.StringClass) { return 4; } break; case JSTYPE_JAVA_OBJECT: case JSTYPE_JAVA_ARRAY: Object javaObj = fromObj; if (javaObj instanceof Wrapper) { javaObj = ((Wrapper) javaObj).unwrap(); } if (to.isInstance(javaObj)) { return CONVERSION_NONTRIVIAL; } if (to == ScriptRuntime.StringClass) { return 2; } else if (to.isPrimitive() && to != Boolean.TYPE) { return (fromCode == JSTYPE_JAVA_ARRAY) ? CONVERSION_NONE : 2 + getSizeRank(to); } break; case JSTYPE_OBJECT: // Other objects takes #1-#3 spots if (to == fromObj.getClass()) { // No conversion required return 1; } if (to.isArray()) { if (fromObj instanceof NativeArray) { // This is a native array conversion to a java array // Array conversions are all equal, and preferable to object // and string conversion, per LC3. return 1; } } else if (to == ScriptRuntime.ObjectClass) { return 2; } else if (to == ScriptRuntime.StringClass) { return 3; } else if (to == ScriptRuntime.DateClass) { if (fromObj instanceof NativeDate) { // This is a native date to java date conversion return 1; } } else if (to.isInterface()) { if (fromObj instanceof Function) { // See comments in coerceType if (to.getMethods().length == 1) { return 1; } } return 11; } else if (to.isPrimitive() && to != Boolean.TYPE) { return 3 + getSizeRank(to); } break; } return CONVERSION_NONE; }