From c2dea59bbde65bb4f076ee770b835c3516ddae6e Mon Sep 17 00:00:00 2001 From: LatvianModder Date: Wed, 22 May 2024 19:30:22 +0300 Subject: [PATCH] Added TypeInfo, replacing Class+Type pairs everywhere --- .../java/dev/latvian/mods/rhino/Context.java | 426 +++++++----------- .../latvian/mods/rhino/CustomFunction.java | 30 +- .../dev/latvian/mods/rhino/CustomMember.java | 6 + .../latvian/mods/rhino/FieldAndMethods.java | 14 +- .../latvian/mods/rhino/FunctionObject.java | 2 +- .../latvian/mods/rhino/InterfaceAdapter.java | 12 +- .../dev/latvian/mods/rhino/JavaAdapter.java | 11 +- .../dev/latvian/mods/rhino/JavaMembers.java | 18 +- .../dev/latvian/mods/rhino/MemberBox.java | 13 +- .../latvian/mods/rhino/NativeIterator.java | 4 +- .../latvian/mods/rhino/NativeJavaArray.java | 18 +- .../latvian/mods/rhino/NativeJavaClass.java | 27 +- .../latvian/mods/rhino/NativeJavaList.java | 69 +-- .../dev/latvian/mods/rhino/NativeJavaMap.java | 28 +- .../latvian/mods/rhino/NativeJavaMethod.java | 50 +- .../latvian/mods/rhino/NativeJavaObject.java | 55 +-- .../dev/latvian/mods/rhino/ScriptRuntime.java | 33 +- .../java/dev/latvian/mods/rhino/VMBridge.java | 27 +- .../mods/rhino/type/ArrayTypeInfo.java | 46 ++ .../mods/rhino/type/ClassTypeInfo.java | 60 +++ .../latvian/mods/rhino/type/NoTypeInfo.java | 38 ++ .../rhino/type/ParameterizedTypeInfo.java | 50 ++ .../dev/latvian/mods/rhino/type/TypeInfo.java | 170 +++++++ .../mods/rhino/util/ArrayValueProvider.java | 20 +- .../rhino/util/CustomJavaToJsWrapper.java | 5 +- .../mods/rhino/util/EnumTypeWrapper.java | 4 +- .../util/wrap/DirectTypeWrapperFactory.java | 5 +- .../rhino/util/wrap/TypeWrapperFactory.java | 5 +- .../rhino/util/wrap/TypeWrapperValidator.java | 6 +- .../mods/rhino/util/wrap/TypeWrappers.java | 42 +- .../dev/latvian/mods/rhino/test/Holder.java | 15 +- .../latvian/mods/rhino/test/TestContext.java | 10 +- .../latvian/mods/rhino/test/WithContext.java | 14 +- 33 files changed, 773 insertions(+), 560 deletions(-) create mode 100644 src/main/java/dev/latvian/mods/rhino/CustomMember.java create mode 100644 src/main/java/dev/latvian/mods/rhino/type/ArrayTypeInfo.java create mode 100644 src/main/java/dev/latvian/mods/rhino/type/ClassTypeInfo.java create mode 100644 src/main/java/dev/latvian/mods/rhino/type/NoTypeInfo.java create mode 100644 src/main/java/dev/latvian/mods/rhino/type/ParameterizedTypeInfo.java create mode 100644 src/main/java/dev/latvian/mods/rhino/type/TypeInfo.java diff --git a/src/main/java/dev/latvian/mods/rhino/Context.java b/src/main/java/dev/latvian/mods/rhino/Context.java index 6f2e5012..eceb7581 100644 --- a/src/main/java/dev/latvian/mods/rhino/Context.java +++ b/src/main/java/dev/latvian/mods/rhino/Context.java @@ -12,7 +12,8 @@ import dev.latvian.mods.rhino.ast.ScriptNode; import dev.latvian.mods.rhino.classfile.ClassFileWriter.ClassFileFormatException; import dev.latvian.mods.rhino.regexp.RegExp; -import dev.latvian.mods.rhino.type.TypeUtils; +import dev.latvian.mods.rhino.type.ArrayTypeInfo; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.ArrayValueProvider; import dev.latvian.mods.rhino.util.ClassVisibilityContext; import dev.latvian.mods.rhino.util.CustomJavaToJsWrapper; @@ -24,11 +25,8 @@ import java.io.Reader; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -37,7 +35,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -973,32 +970,24 @@ public boolean visibleToScripts(String fullClassName, ClassVisibilityContext typ return true; } - public Object wrap(Scriptable scope, Object obj, Class staticType, Type genericType) { + public Object wrap(Scriptable scope, Object obj, TypeInfo target) { if (obj == null || obj == Undefined.INSTANCE || obj instanceof Scriptable) { return obj; - } else if (staticType == Void.TYPE) { + } else if (target == TypeInfo.VOID) { return Undefined.INSTANCE; - } else if (staticType == Character.TYPE) { + } else if (target == TypeInfo.CHARACTER) { return (int) (Character) obj; - } else if (staticType != null && staticType.isPrimitive()) { + } else if (target != null && target.isPrimitive()) { return obj; + } else if (target instanceof ArrayTypeInfo array) { + return new NativeJavaArray(scope, obj, array, this); + } else { + return wrapAsJavaObject(scope, obj, target); } - - Class cls = obj.getClass(); - - if (cls.isArray()) { - return new NativeJavaArray(scope, obj, cls.getComponentType(), genericType instanceof GenericArrayType arr ? arr.getGenericComponentType() : cls.getComponentType(), this); - } - - return wrapAsJavaObject(scope, obj, staticType, genericType); - } - - public Object wrap(Scriptable scope, Object obj, Class staticType) { - return wrap(scope, obj, staticType, staticType); } public Object wrap(Scriptable scope, Object obj) { - return wrap(scope, obj, null, null); + return wrap(scope, obj, TypeInfo.NONE); } public boolean hasTopCallScope() { @@ -1118,8 +1107,8 @@ public Scriptable wrapJavaClass(Scriptable scope, Class javaClass) { * Wrap Java object as Scriptable instance to allow full access to its * methods and fields from JavaScript. *

- * {@link #wrap(Scriptable, Object, Class, Type)} and - * {@link #wrapNewObject(Scriptable, Object)} call this method + * {@link #wrap(Scriptable, Object, TypeInfo)} and + * {@link #wrapNewObject(Scriptable, Object, TypeInfo)} call this method * when they can not convert javaObject to JavaScript primitive * value or JavaScript array. *

@@ -1128,72 +1117,25 @@ public Scriptable wrapJavaClass(Scriptable scope, Class javaClass) { * * @param scope the scope of the executing script * @param javaObject the object to be wrapped - * @param staticType type hint. If security restrictions prevent to wrap + * @param target type hint. If security restrictions prevent to wrap * object based on its class, staticType will be used instead. * @return the wrapped value which shall not be null */ - public Scriptable wrapAsJavaObject(Scriptable scope, Object javaObject, Class staticType, Type genericType) { + public Scriptable wrapAsJavaObject(Scriptable scope, Object javaObject, TypeInfo target) { if (javaObject instanceof CustomJavaToJsWrapper w) { - return w.convertJavaToJs(this, scope, staticType, genericType); + return w.convertJavaToJs(this, scope, target); } if (javaObject instanceof Map map) { - Class kType = null; - Type kGenericType = null; - Class vType = null; - Type vGenericType = null; - - if (staticType != null && genericType instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var kRaw = types.length == 2 ? TypeUtils.getRawType(types[0]) : Object.class; - var vRaw = types.length == 2 ? TypeUtils.getRawType(types[1]) : Object.class; - - if (kRaw != null && kRaw != Object.class) { - kType = kRaw; - kGenericType = types[0]; - } - - if (vRaw != null && vRaw != Object.class) { - vType = vRaw; - vGenericType = types[1]; - } - } - - return new NativeJavaMap(this, scope, map, map, kType, kGenericType, vType, vGenericType); + return new NativeJavaMap(this, scope, map, map, target); } else if (javaObject instanceof List list) { - Class lType = null; - Type lGenericType = null; - - if (staticType != null && genericType instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var raw = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class; - - if (raw != null && raw != Object.class) { - lType = raw; - lGenericType = pt.getActualTypeArguments()[0]; - } - } - - return new NativeJavaList(this, scope, list, list, lType, lGenericType); + return new NativeJavaList(this, scope, list, list, target); } else if (javaObject instanceof Set set) { - Class lType = null; - Type lGenericType = null; - - if (staticType != null && genericType instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var raw = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class; - - if (raw != null && raw != Object.class) { - lType = raw; - lGenericType = pt.getActualTypeArguments()[0]; - } - } - - return new NativeJavaList(this, scope, set, new JavaSetWrapper<>(set), lType, lGenericType); + return new NativeJavaList(this, scope, set, new JavaSetWrapper<>(set), target); } // TODO: Wrap Gson - return new NativeJavaObject(scope, javaObject, staticType, this); + return new NativeJavaObject(scope, javaObject, target, this); } /** @@ -1203,19 +1145,18 @@ public Scriptable wrapAsJavaObject(Scriptable scope, Object javaObject, Class * @param obj the object to be wrapped * @return the wrapped value. */ - public Scriptable wrapNewObject(Scriptable scope, Object obj) { + public Scriptable wrapNewObject(Scriptable scope, Object obj, TypeInfo objType) { if (obj instanceof Scriptable) { return (Scriptable) obj; } - Class cls = obj.getClass(); - if (cls.isArray()) { - return new NativeJavaArray(scope, obj, cls.getComponentType(), cls.getComponentType(), this); + if (objType instanceof ArrayTypeInfo) { + return new NativeJavaArray(scope, obj, objType, this); } - return wrapAsJavaObject(scope, obj, null, null); + return wrapAsJavaObject(scope, obj, TypeInfo.NONE); } - public int internalConversionWeight(Object fromObj, Class target, Type genericTarget) { - if (factory.getTypeWrappers().hasWrapper(fromObj, target, genericTarget)) { + public int internalConversionWeight(Object fromObj, TypeInfo target) { + if (factory.getTypeWrappers().hasWrapper(fromObj, target)) { return CONVERSION_NONTRIVIAL; } @@ -1266,75 +1207,75 @@ protected ArrayValueProvider arrayValueProviderOf(Object value) { }; } - protected Object arrayOf(@Nullable Object from, @Nullable Class target, @Nullable Type genericTarget) { + protected Object arrayOf(@Nullable Object from, TypeInfo target) { if (from instanceof Object[] arr) { if (target == null) { return from; } - return arr.length == 0 ? Array.newInstance(target, 0) : new ArrayValueProvider.FromPlainJavaArray(arr).createArray(this, target, genericTarget); + return arr.length == 0 ? target.newArray(0) : new ArrayValueProvider.FromPlainJavaArray(arr).createArray(this, target); } else if (from != null && from.getClass().isArray()) { if (target == null) { return from; } int len = Array.getLength(from); - return len == 0 ? Array.newInstance(target, 0) : new ArrayValueProvider.FromJavaArray(from, len).createArray(this, target, genericTarget); + return len == 0 ? target.newArray(0) : new ArrayValueProvider.FromJavaArray(from, len).createArray(this, target); } - return arrayValueProviderOf(from).createArray(this, target, genericTarget); + return arrayValueProviderOf(from).createArray(this, target); } - protected Object listOf(@Nullable Object from, @Nullable Class target, @Nullable Type genericTarget) { + protected Object listOf(@Nullable Object from, TypeInfo target) { if (from instanceof NativeJavaList n) { if (target == null) { // No conversion necessary return n.list; - } else if (target == n.listType && Objects.equals(genericTarget, n.listGenericType)) { + } else if (target.equals(n.listType)) { // No conversion necessary return n.list; } else { var list = new ArrayList<>(n.list.size()); for (var o : n.list) { - list.add(jsToJava(o, target, genericTarget)); + list.add(jsToJava(o, target)); } return list; } } - return arrayValueProviderOf(from).createList(this, target, genericTarget); + return arrayValueProviderOf(from).createList(this, target); } - protected Object setOf(@Nullable Object from, @Nullable Class target, @Nullable Type genericTarget) { + protected Object setOf(@Nullable Object from, TypeInfo target) { if (from instanceof NativeJavaList n) { if (target == null) { // No conversion necessary return new LinkedHashSet<>(n.list); - } else if (target == n.listType && Objects.equals(genericTarget, n.listGenericType)) { + } else if (target.equals(n.listType)) { // No conversion necessary return new LinkedHashSet<>(n.list); } else { var set = new LinkedHashSet<>(n.list.size()); for (var o : n.list) { - set.add(jsToJava(o, target, genericTarget)); + set.add(jsToJava(o, target)); } return set; } } - return arrayValueProviderOf(from).createSet(this, target, genericTarget); + return arrayValueProviderOf(from).createSet(this, target); } - protected Object mapOf(@Nullable Object from, @Nullable Class kTarget, @Nullable Type kGenericTarget, @Nullable Class vTarget, @Nullable Type vGenericTarget) { + protected Object mapOf(@Nullable Object from, TypeInfo kTarget, TypeInfo vTarget) { if (from instanceof NativeJavaMap n) { if (kTarget == null && vTarget == null) { // No conversion necessary return n.map; - } else if (kTarget == n.mapKeyType && Objects.equals(kGenericTarget, n.mapKeyGenericType) && vTarget == n.mapValueType && Objects.equals(vGenericTarget, n.mapValueGenericType)) { + } else if (kTarget == n.mapKeyType && kTarget.equals(n.mapKeyType) && vTarget.equals(n.mapValueType)) { // No conversion necessary return n.map; } else { @@ -1345,7 +1286,7 @@ protected Object mapOf(@Nullable Object from, @Nullable Class kTarget, @Nulla var map = new LinkedHashMap<>(n.map.size()); for (var entry : ((Map) n.map).entrySet()) { - map.put(jsToJava(entry.getKey(), kTarget, kGenericTarget), jsToJava(entry.getValue(), vTarget, vGenericTarget)); + map.put(jsToJava(entry.getKey(), kTarget), jsToJava(entry.getValue(), vTarget)); } return map; @@ -1355,7 +1296,7 @@ protected Object mapOf(@Nullable Object from, @Nullable Class kTarget, @Nulla var map = new LinkedHashMap<>(keys.length); for (var key : keys) { - map.put(jsToJava(key, kTarget, kGenericTarget), jsToJava(obj.get(this, key), vTarget, vGenericTarget)); + map.put(jsToJava(key, kTarget), jsToJava(obj.get(this, key), vTarget)); } return map; @@ -1368,114 +1309,74 @@ protected Object mapOf(@Nullable Object from, @Nullable Class kTarget, @Nulla var map = new LinkedHashMap<>(m.size()); for (var entry : m.entrySet()) { - map.put(jsToJava(entry.getKey(), kTarget, kGenericTarget), jsToJava(entry.getValue(), vTarget, vGenericTarget)); + map.put(jsToJava(entry.getKey(), kTarget), jsToJava(entry.getValue(), vTarget)); } return map; } else { - return reportConversionError(from, Map.class); + return reportConversionError(from, TypeInfo.RAW_MAP); } } - public Object createInterfaceAdapter(Class type, Type genericType, ScriptableObject so) { + public Object createInterfaceAdapter(TypeInfo type, ScriptableObject so) { + var cl = type.asClass(); + + if (cl == null) { + throw new NullPointerException("type.asClass() must not be null"); + } + // 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. - Object key = Kit.makeHashKeyFromPair("Coerced Interface", type); + Object key = Kit.makeHashKeyFromPair("Coerced Interface", cl); Object old = so.getAssociatedValue(key); if (old != null) { // Function was already wrapped return old; } - Object glue = InterfaceAdapter.create(this, type, so); + Object glue = InterfaceAdapter.create(this, cl, so); // Store for later retrieval glue = so.associateValue(key, glue); return glue; } public Object javaToJS(Object value, Scriptable scope) { - return javaToJS(value, scope, null, null); + return javaToJS(value, scope, TypeInfo.NONE); } - public Object javaToJS(Object value, Scriptable scope, @Nullable Class target, @Nullable Type genericTarget) { + public Object javaToJS(Object value, Scriptable scope, TypeInfo target) { if (value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Scriptable) { return value; } else if (value instanceof Character) { return String.valueOf(((Character) value).charValue()); } else { - return wrap(scope, value, target, genericTarget); + return wrap(scope, value, target); } } - public final Object jsToJava(@Nullable Object from, @Nullable Class target, @Nullable Type genericTarget) throws EvaluatorException { + public final Object jsToJava(@Nullable Object from, TypeInfo target) throws EvaluatorException { if (target == null) { return from; - } else if (target == Object.class) { - return Wrapper.unwrapped(from); - } else if (target == Set.class) { - if (genericTarget instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var c = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class; - - if (c != null && c != Object.class) { - return setOf(from, c, types[0]); - } - } - - return setOf(from, null, null); - } else if (target == Map.class) { - Class kType = null; - Type kGenericType = null; - Class vType = null; - Type vGenericType = null; - - if (genericTarget instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var kRaw = types.length == 2 ? TypeUtils.getRawType(types[0]) : Object.class; - var vRaw = types.length == 2 ? TypeUtils.getRawType(types[1]) : Object.class; - - if (kRaw != null && kRaw != Object.class) { - kType = kRaw; - kGenericType = types[0]; - } - - if (vRaw != null && vRaw != Object.class) { - vType = vRaw; - vGenericType = types[1]; - } - } - - return mapOf(from, kType, kGenericType, vType, vGenericType); - } else if (target.isArray()) { - var desiredComponentType = target.componentType(); - var desiredComponentGenericType = TypeUtils.getComponentType(genericTarget, desiredComponentType); - - if (from != null && from.getClass() == target) { - return arrayOf(from, desiredComponentType, desiredComponentGenericType); - } + } - return arrayOf(from, desiredComponentType, desiredComponentGenericType); - } else if (List.class.isAssignableFrom(target)) { - if (genericTarget instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); - var c = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class; + var cl = target.asClass(); - if (c != null && c != Object.class) { - return listOf(from, c, types[0]); - } - } - - return listOf(from, null, null); + if (cl == Object.class) { + return Wrapper.unwrapped(from); + } else if (cl == Set.class) { + return setOf(from, target.param(0)); + } else if (cl == Map.class) { + return mapOf(from, target.param(0), target.param(1)); + } else if (target instanceof ArrayTypeInfo) { + return arrayOf(from, target.componentType()); + } else if (List.class.isAssignableFrom(cl)) { + return listOf(from, target.param(0)); } - return internalJsToJava(from, target, genericTarget); - } - - public final Object jsToJava(Object value, Class desiredType) throws EvaluatorException { - return jsToJava(value, desiredType, desiredType); + return internalJsToJava(from, target); } private static int getJSTypeCode(Object value) { @@ -1507,25 +1408,24 @@ private static int getJSTypeCode(Object value) { } } - protected Object internalJsToJava(Object from, Class target, Type genericTarget) { + protected Object internalJsToJava(Object from, TypeInfo target) { var typeWrappers = factory.getTypeWrappers(); - if (from == null || from.getClass() == target) { + if (from == null || from.getClass() == target.asClass()) { return from; } - if (target.isArray()) { + if (target instanceof ArrayTypeInfo) { // Make a new java array, and coerce the JS array components to the target (component) type. - Class arrayType = target.getComponentType(); - var arrayGenericType = genericTarget instanceof GenericArrayType arr ? arr.getGenericComponentType() : arrayType; + var arrayType = target.componentType(); if (from instanceof NativeArray array) { long length = array.getLength(); - Object result = Array.newInstance(arrayType, (int) length); + Object result = arrayType.newArray((int) length); for (int i = 0; i < length; ++i) { try { - Array.set(result, i, jsToJava(array.get(this, i, array), arrayType, arrayGenericType)); + Array.set(result, i, jsToJava(array.get(this, i, array), arrayType)); } catch (EvaluatorException ee) { return reportConversionError(from, target); } @@ -1534,8 +1434,8 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg return result; } else { // Convert a single value to an array - Object result = Array.newInstance(arrayType, 1); - Array.set(result, 0, jsToJava(from, arrayType, arrayGenericType)); + Object result = arrayType.newArray(1); + Array.set(result, 0, jsToJava(from, arrayType)); return result; } } @@ -1543,13 +1443,13 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg Object unwrappedValue = Wrapper.unwrapped(from); if (unwrappedValue instanceof TypeWrapperFactory f) { - return f.wrap(this, unwrappedValue, target, genericTarget); + return f.wrap(this, unwrappedValue, target); } - TypeWrapperFactory typeWrapper = typeWrappers == null ? null : typeWrappers.getWrapperFactory(unwrappedValue, target, genericTarget); + TypeWrapperFactory typeWrapper = typeWrappers == null ? null : typeWrappers.getWrapperFactory(unwrappedValue, target); if (typeWrapper != null) { - return typeWrapper.wrap(this, unwrappedValue, target, genericTarget); + return typeWrapper.wrap(this, unwrappedValue, target); } switch (getJSTypeCode(from)) { @@ -1561,25 +1461,25 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg return null; } case JSTYPE_UNDEFINED -> { - if (target == ScriptRuntime.StringClass || target == ScriptRuntime.ObjectClass) { + if (target == TypeInfo.STRING || target == TypeInfo.OBJECT) { return "undefined"; } return reportConversionError("undefined", target, from); } case JSTYPE_BOOLEAN -> { // Under LC3, only JS Booleans can be coerced into a Boolean value - if (target == Boolean.TYPE || target == ScriptRuntime.BooleanClass || target == ScriptRuntime.ObjectClass) { + if (target == TypeInfo.BOOLEAN || target == TypeInfo.OBJECT) { return from; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return from.toString(); } else { return reportConversionError(from, target); } } case JSTYPE_NUMBER -> { - if (target == ScriptRuntime.StringClass) { + if (target == TypeInfo.STRING) { return ScriptRuntime.toString(this, from); - } else if (target == ScriptRuntime.ObjectClass) { + } else if (target == TypeInfo.OBJECT) { /* if (cx.hasFeature(Context.FEATURE_INTEGER_WITHOUT_DECIMAL_PLACE)) { //to process numbers like 2.0 as 2 without decimal place @@ -1589,17 +1489,17 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg } } */ - return coerceToNumber(Double.TYPE, from); - } else if ((target.isPrimitive() && target != Boolean.TYPE) || ScriptRuntime.NumberClass.isAssignableFrom(target)) { + return coerceToNumber(TypeInfo.DOUBLE, from); + } else if ((target.isPrimitive() && target != TypeInfo.BOOLEAN) || ScriptRuntime.NumberClass.isAssignableFrom(target.asClass())) { return coerceToNumber(target, from); } else { return reportConversionError(from, target); } } case JSTYPE_STRING -> { - if (target == ScriptRuntime.StringClass || target.isInstance(from)) { + if (target == TypeInfo.STRING || target.asClass().isInstance(from)) { return from.toString(); - } else if (target == Character.TYPE || target == ScriptRuntime.CharacterClass) { + } else if (target == TypeInfo.CHARACTER) { // Special case for converting a single char string to a // character // Placed here because it applies *only* to JS strings, @@ -1608,16 +1508,16 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg return ((CharSequence) from).charAt(0); } return coerceToNumber(target, from); - } else if ((target.isPrimitive() && target != Boolean.TYPE) || ScriptRuntime.NumberClass.isAssignableFrom(target)) { + } else if ((target.isPrimitive() && target != TypeInfo.BOOLEAN) || ScriptRuntime.NumberClass.isAssignableFrom(target.asClass())) { return coerceToNumber(target, from); } else { return reportConversionError(from, target); } } case JSTYPE_JAVA_CLASS -> { - if (target == ScriptRuntime.ClassClass || target == ScriptRuntime.ObjectClass) { + if (target == TypeInfo.CLASS || target == TypeInfo.OBJECT) { return unwrappedValue; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return unwrappedValue.toString(); } else { return reportConversionError(unwrappedValue, target); @@ -1625,41 +1525,41 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg } case JSTYPE_JAVA_OBJECT, JSTYPE_JAVA_ARRAY -> { if (target.isPrimitive()) { - if (target == Boolean.TYPE) { + if (target == TypeInfo.BOOLEAN) { return reportConversionError(unwrappedValue, target); } return coerceToNumber(target, unwrappedValue); } - if (target == ScriptRuntime.StringClass) { + if (target == TypeInfo.STRING) { return unwrappedValue.toString(); } - if (target.isInstance(unwrappedValue)) { + if (target.asClass().isInstance(unwrappedValue)) { return unwrappedValue; } return reportConversionError(unwrappedValue, target); } case JSTYPE_OBJECT -> { - if (target == ScriptRuntime.StringClass) { + if (target == TypeInfo.STRING) { return ScriptRuntime.toString(this, from); } else if (target.isPrimitive()) { - if (target == Boolean.TYPE) { + if (target == TypeInfo.BOOLEAN) { return reportConversionError(from, target); } return coerceToNumber(target, from); - } else if (target.isInstance(from)) { + } else if (target.asClass().isInstance(from)) { return from; - } else if (target == ScriptRuntime.DateClass && from instanceof NativeDate) { + } else if (target == TypeInfo.DATE && from instanceof NativeDate) { double time = ((NativeDate) from).getJSTimeValue(); // XXX: This will replace NaN by 0 return new Date((long) time); } else if (from instanceof Wrapper) { - if (target.isInstance(unwrappedValue)) { + if (target.asClass().isInstance(unwrappedValue)) { return unwrappedValue; } return reportConversionError(unwrappedValue, target); - } else if (target.isInterface() && (from instanceof NativeObject || from instanceof NativeFunction || from instanceof ArrowFunction)) { + } else if (target.asClass().isInterface() && (from instanceof NativeObject || from instanceof NativeFunction || from instanceof ArrowFunction)) { // Try to use function/object as implementation of Java interface. - return createInterfaceAdapter(target, genericTarget, (ScriptableObject) from); + return createInterfaceAdapter(target, (ScriptableObject) from); } else { return reportConversionError(from, target); } @@ -1669,18 +1569,18 @@ protected Object internalJsToJava(Object from, Class target, Type genericTarg return from; } - public final boolean canConvert(Object from, Class target, Type genericTarget) { - return getConversionWeight(from, target, genericTarget) < CONVERSION_NONE; + public final boolean canConvert(Object from, TypeInfo target) { + return getConversionWeight(from, target) < CONVERSION_NONE; } - public final int getConversionWeight(Object from, Class target, Type genericTarget) { - int fcw = internalConversionWeight(from, target, genericTarget); + public final int getConversionWeight(Object from, TypeInfo target) { + int fcw = internalConversionWeight(from, target); if (fcw != CONVERSION_NONE) { return fcw; } - if (target.isArray() || Collection.class.isAssignableFrom(target)) { + if (target instanceof ArrayTypeInfo || Collection.class.isAssignableFrom(target.asClass())) { return CONVERSION_NONTRIVIAL; } @@ -1688,7 +1588,7 @@ public final int getConversionWeight(Object from, Class target, Type genericT switch (fromCode) { case JSTYPE_UNDEFINED -> { - if (target == ScriptRuntime.StringClass || target == ScriptRuntime.ObjectClass) { + if (target == TypeInfo.STRING || target == TypeInfo.OBJECT) { return 1; } } @@ -1699,66 +1599,64 @@ public final int getConversionWeight(Object from, Class target, Type genericT } case JSTYPE_BOOLEAN -> { // "boolean" is #1 - if (target == Boolean.TYPE) { + if (target == TypeInfo.BOOLEAN) { return 1; - } else if (target == ScriptRuntime.BooleanClass) { - return 2; - } else if (target == ScriptRuntime.ObjectClass) { + } else if (target == TypeInfo.OBJECT) { return 3; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return 4; } } case JSTYPE_NUMBER -> { if (target.isPrimitive()) { - if (target == Double.TYPE) { + if (target == TypeInfo.DOUBLE) { return 1; - } else if (target != Boolean.TYPE) { + } else if (target != TypeInfo.BOOLEAN) { return 1 + getSizeRank(target); } } else { - if (target == ScriptRuntime.StringClass) { + if (target == TypeInfo.STRING) { // native numbers are #1-8 return 9; - } else if (target == ScriptRuntime.ObjectClass) { + } else if (target == TypeInfo.OBJECT) { return 10; - } else if (ScriptRuntime.NumberClass.isAssignableFrom(target)) { + } else if (ScriptRuntime.NumberClass.isAssignableFrom(target.asClass())) { // "double" is #1 return 2; } } } case JSTYPE_STRING -> { - if (target == ScriptRuntime.StringClass) { + if (target == TypeInfo.STRING) { return 1; - } else if (target.isInstance(from)) { + } else if (target.asClass().isInstance(from)) { return 2; } else if (target.isPrimitive()) { - if (target == Character.TYPE) { + if (target == TypeInfo.CHARACTER) { return 3; - } else if (target != Boolean.TYPE) { + } else if (target != TypeInfo.BOOLEAN) { return 4; } } } case JSTYPE_JAVA_CLASS -> { - if (target == ScriptRuntime.ClassClass) { + if (target == TypeInfo.CLASS) { return 1; - } else if (target == ScriptRuntime.ObjectClass) { + } else if (target == TypeInfo.OBJECT) { return 3; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return 4; } } case JSTYPE_JAVA_OBJECT, JSTYPE_JAVA_ARRAY -> { Object javaObj = Wrapper.unwrapped(from); - if (target.isInstance(javaObj)) { + if (target.asClass().isInstance(javaObj)) { return CONVERSION_NONTRIVIAL; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return 2; - } else if (target.isPrimitive() && target != Boolean.TYPE) { + } else if (target.isPrimitive() && target != TypeInfo.BOOLEAN) { return (fromCode == JSTYPE_JAVA_ARRAY) ? CONVERSION_NONE : 2 + getSizeRank(target); - } else if (target.isArray()) { + } else if (target instanceof ArrayTypeInfo) { return 3; } else { return CONVERSION_NONE; @@ -1766,11 +1664,11 @@ public final int getConversionWeight(Object from, Class target, Type genericT } case JSTYPE_OBJECT -> { // Other objects takes #1-#3 spots - if (target != ScriptRuntime.ObjectClass && target.isInstance(from)) { + if (target != TypeInfo.OBJECT && target.asClass().isInstance(from)) { // No conversion required, but don't apply for java.lang.Object return 1; } - if (target.isArray()) { + if (target instanceof ArrayTypeInfo) { if (from instanceof NativeArray) { // This is a native array conversion to a java array // Array conversions are all equal, and preferable to object @@ -1779,16 +1677,16 @@ public final int getConversionWeight(Object from, Class target, Type genericT } else { return 1; } - } else if (target == ScriptRuntime.ObjectClass) { + } else if (target == TypeInfo.OBJECT) { return 3; - } else if (target == ScriptRuntime.StringClass) { + } else if (target == TypeInfo.STRING) { return 4; - } else if (target == ScriptRuntime.DateClass) { + } else if (target == TypeInfo.DATE) { if (from instanceof NativeDate) { // This is a native date to java date conversion return 1; } - } else if (target.isInterface()) { + } else if (target.asClass().isInterface()) { if (from instanceof NativeFunction) { // See comments in createInterfaceAdapter @@ -1798,7 +1696,7 @@ public final int getConversionWeight(Object from, Class target, Type genericT return 2; } return 12; - } else if (target.isPrimitive() && target != Boolean.TYPE) { + } else if (target.isPrimitive() && target != TypeInfo.BOOLEAN) { return 4 + getSizeRank(target); } } @@ -1807,45 +1705,45 @@ public final int getConversionWeight(Object from, Class target, Type genericT return CONVERSION_NONE; } - public static int getSizeRank(Class aType) { - if (aType == Double.TYPE) { + public static int getSizeRank(TypeInfo aType) { + if (aType == TypeInfo.DOUBLE) { return 1; - } else if (aType == Float.TYPE) { + } else if (aType == TypeInfo.FLOAT) { return 2; - } else if (aType == Long.TYPE) { + } else if (aType == TypeInfo.LONG) { return 3; - } else if (aType == Integer.TYPE) { + } else if (aType == TypeInfo.INT) { return 4; - } else if (aType == Short.TYPE) { + } else if (aType == TypeInfo.SHORT) { return 5; - } else if (aType == Character.TYPE) { + } else if (aType == TypeInfo.CHARACTER) { return 6; - } else if (aType == Byte.TYPE) { + } else if (aType == TypeInfo.BYTE) { return 7; - } else if (aType == Boolean.TYPE) { + } else if (aType == TypeInfo.BOOLEAN) { return CONVERSION_NONE; } else { return 8; } } - protected Object coerceToNumber(Class type, Object value) { + protected Object coerceToNumber(TypeInfo target, Object value) { Class valueClass = value.getClass(); // Character - if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) { + if (target == TypeInfo.CHARACTER) { if (valueClass == ScriptRuntime.CharacterClass) { return value; } - return (char) toInteger(value, ScriptRuntime.CharacterClass, Character.MIN_VALUE, Character.MAX_VALUE); + return (char) toInteger(value, TypeInfo.CHARACTER, Character.MIN_VALUE, Character.MAX_VALUE); } // Double, Float - if (type == ScriptRuntime.ObjectClass || type == ScriptRuntime.DoubleClass || type == Double.TYPE) { + if (target == TypeInfo.OBJECT || target == TypeInfo.DOUBLE) { return valueClass == ScriptRuntime.DoubleClass ? value : Double.valueOf(toDouble(value)); } - if (type == ScriptRuntime.FloatClass || type == Float.TYPE) { + if (target == TypeInfo.FLOAT) { if (valueClass == ScriptRuntime.FloatClass) { return value; } @@ -1865,14 +1763,14 @@ protected Object coerceToNumber(Class type, Object value) { } // Integer, Long, Short, Byte - if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) { + if (target == TypeInfo.INT) { if (valueClass == ScriptRuntime.IntegerClass) { return value; } - return (int) toInteger(value, ScriptRuntime.IntegerClass, Integer.MIN_VALUE, Integer.MAX_VALUE); + return (int) toInteger(value, TypeInfo.INT, Integer.MIN_VALUE, Integer.MAX_VALUE); } - if (type == ScriptRuntime.LongClass || type == Long.TYPE) { + if (target == TypeInfo.LONG) { if (valueClass == ScriptRuntime.LongClass) { return value; } @@ -1885,21 +1783,21 @@ protected Object coerceToNumber(Class type, Object value) { */ final double max = Double.longBitsToDouble(0x43dfffffffffffffL); final double min = Double.longBitsToDouble(0xc3e0000000000000L); - return toInteger(value, ScriptRuntime.LongClass, min, max); + return toInteger(value, TypeInfo.LONG, min, max); } - if (type == ScriptRuntime.ShortClass || type == Short.TYPE) { + if (target == TypeInfo.SHORT) { if (valueClass == ScriptRuntime.ShortClass) { return value; } - return (short) toInteger(value, ScriptRuntime.ShortClass, Short.MIN_VALUE, Short.MAX_VALUE); + return (short) toInteger(value, TypeInfo.SHORT, Short.MIN_VALUE, Short.MAX_VALUE); } - if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) { + if (target == TypeInfo.BYTE) { if (valueClass == ScriptRuntime.ByteClass) { return value; } - return (byte) toInteger(value, ScriptRuntime.ByteClass, Byte.MIN_VALUE, Byte.MAX_VALUE); + return (byte) toInteger(value, TypeInfo.BYTE, Byte.MIN_VALUE, Byte.MAX_VALUE); } return toDouble(value); @@ -1928,14 +1826,14 @@ protected double toDouble(Object value) { return ((Number) meth.invoke(value, (Object[]) null)).doubleValue(); } catch (IllegalAccessException | InvocationTargetException e) { // XXX: ignore, or error message? - reportConversionError(value, Double.TYPE); + reportConversionError(value, TypeInfo.DOUBLE); } } return ScriptRuntime.toNumber(this, value.toString()); } } - protected long toInteger(Object value, Class type, double min, double max) { + protected long toInteger(Object value, TypeInfo type, double min, double max) { double d = toDouble(value); if (Double.isInfinite(d) || Double.isNaN(d)) { @@ -1956,14 +1854,14 @@ protected long toInteger(Object value, Class type, double min, double max) { return (long) d; } - public Object reportConversionError(Object value, Class type) { + public Object reportConversionError(Object value, TypeInfo type) { return reportConversionError(value, type, value); } - public Object reportConversionError(Object value, Class type, Object stringValue) { + public Object reportConversionError(Object value, TypeInfo type, Object stringValue) { // It uses String.valueOf(value), not value.toString() since // value can be null, bug 282447. - throw Context.reportRuntimeError2("msg.conversion.not.allowed", String.valueOf(stringValue), JavaMembers.javaSignature(type), this); + throw Context.reportRuntimeError2("msg.conversion.not.allowed", String.valueOf(stringValue), type.signature(), this); } public String defaultObjectToSource(Scriptable scope, Scriptable thisObj, Object[] args) { diff --git a/src/main/java/dev/latvian/mods/rhino/CustomFunction.java b/src/main/java/dev/latvian/mods/rhino/CustomFunction.java index fb4acf32..6c376907 100644 --- a/src/main/java/dev/latvian/mods/rhino/CustomFunction.java +++ b/src/main/java/dev/latvian/mods/rhino/CustomFunction.java @@ -1,23 +1,16 @@ package dev.latvian.mods.rhino; -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; public class CustomFunction extends BaseFunction { - public static final Class[] NO_ARGS = new Class[0]; private final String functionName; private final Func func; - private final Class[] argTypes; - private final Type[] genericArgTypes; + private final TypeInfo[] argTypes; - public CustomFunction(String functionName, Func func, Class[] argTypes, Type[] genericArgTypes) { + public CustomFunction(String functionName, Func func, TypeInfo[] argTypes) { this.functionName = functionName; this.func = func; - this.argTypes = argTypes; - this.genericArgTypes = genericArgTypes == null || genericArgTypes.length == 0 || genericArgTypes.length != argTypes.length ? null : genericArgTypes; - } - - public CustomFunction(String functionName, Func func, Class[] argTypes) { - this(functionName, func, argTypes, null); + this.argTypes = argTypes.length == 0 ? TypeInfo.EMPTY_ARRAY : argTypes; } @Override @@ -31,7 +24,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar Object[] origArgs = args; for (int i = 0; i < args.length; i++) { Object arg = args[i]; - Object coerced = cx.jsToJava(arg, argTypes[i], genericArgTypes == null ? argTypes[i] : genericArgTypes[i]); + Object coerced = cx.jsToJava(arg, argTypes[i]); if (coerced != arg) { if (origArgs == args) { @@ -41,18 +34,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar } } - Object retval = func.call(cx, args); - - if (retval == null) { - return Undefined.INSTANCE; - } - - Object wrapped = cx.wrap(scope, retval, retval.getClass()); - - if (wrapped == null) { - wrapped = Undefined.INSTANCE; - } - return wrapped; + return func.call(cx, args); } @FunctionalInterface diff --git a/src/main/java/dev/latvian/mods/rhino/CustomMember.java b/src/main/java/dev/latvian/mods/rhino/CustomMember.java new file mode 100644 index 00000000..62e121ee --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/CustomMember.java @@ -0,0 +1,6 @@ +package dev.latvian.mods.rhino; + +import dev.latvian.mods.rhino.type.TypeInfo; + +public record CustomMember(String name, TypeInfo type, Object value) { +} diff --git a/src/main/java/dev/latvian/mods/rhino/FieldAndMethods.java b/src/main/java/dev/latvian/mods/rhino/FieldAndMethods.java index 2cc72956..f5b63e61 100644 --- a/src/main/java/dev/latvian/mods/rhino/FieldAndMethods.java +++ b/src/main/java/dev/latvian/mods/rhino/FieldAndMethods.java @@ -1,12 +1,13 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.DefaultValueTypeHint; import java.lang.reflect.Field; -import java.lang.reflect.Type; public class FieldAndMethods extends NativeJavaMethod { public transient Field field; + public transient TypeInfo fieldType; public transient Object javaObject; FieldAndMethods(Scriptable scope, MemberBox[] methods, Field field, Context cx) { @@ -22,16 +23,17 @@ public Object getDefaultValue(Context cx, DefaultValueTypeHint hint) { return this; } Object rval; - Class type; - Type genericType; try { rval = field.get(javaObject); - type = field.getType(); - genericType = field.getGenericType(); } catch (IllegalAccessException accEx) { throw Context.reportRuntimeError1("msg.java.internal.private", field.getName(), cx); } - rval = cx.wrap(this, rval, type, genericType); + + if (fieldType == null) { + this.fieldType = TypeInfo.of(field.getGenericType()); + } + + rval = cx.wrap(this, rval, fieldType); if (rval instanceof Scriptable) { rval = ((Scriptable) rval).getDefaultValue(cx, hint); } diff --git a/src/main/java/dev/latvian/mods/rhino/FunctionObject.java b/src/main/java/dev/latvian/mods/rhino/FunctionObject.java index 93d604de..b140c070 100644 --- a/src/main/java/dev/latvian/mods/rhino/FunctionObject.java +++ b/src/main/java/dev/latvian/mods/rhino/FunctionObject.java @@ -419,7 +419,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar if (hasVoidReturn) { result = Undefined.INSTANCE; } else if (returnTypeTag == JAVA_UNSUPPORTED_TYPE) { - result = cx.wrap(scope, result); + result = cx.wrap(scope, result, member.returnType); } // XXX: the code assumes that if returnTypeTag == JAVA_OBJECT_TYPE // then the Java method did a proper job of converting the diff --git a/src/main/java/dev/latvian/mods/rhino/InterfaceAdapter.java b/src/main/java/dev/latvian/mods/rhino/InterfaceAdapter.java index cb3ee7a6..4dc0de3a 100644 --- a/src/main/java/dev/latvian/mods/rhino/InterfaceAdapter.java +++ b/src/main/java/dev/latvian/mods/rhino/InterfaceAdapter.java @@ -6,6 +6,8 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; + import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -101,14 +103,14 @@ public Object invoke(Context cx, Object target, Scriptable topScope, Object this if (resultType == Void.TYPE) { return null; } - return cx.jsToJava(null, resultType, method.getGenericReturnType()); + return cx.jsToJava(null, TypeInfo.of(method.getGenericReturnType())); } if (!(value instanceof Callable)) { throw Context.reportRuntimeError1("msg.not.function.interface", methodName, cx); } function = (Callable) value; } - if (args == null) { + if (args == null || args.length == 0) { args = ScriptRuntime.EMPTY_OBJECTS; } else { for (int i = 0, N = args.length; i != N; ++i) { @@ -119,14 +121,14 @@ public Object invoke(Context cx, Object target, Scriptable topScope, Object this } } } - Scriptable thisObj = cx.wrapAsJavaObject(topScope, thisObject, null, null); + Scriptable thisObj = cx.wrapAsJavaObject(topScope, thisObject, TypeInfo.NONE); Object result = cx.callSync(function, topScope, thisObj, args); - Class javaResultType = method.getReturnType(); + var javaResultType = method.getGenericReturnType(); if (javaResultType == Void.TYPE) { result = null; } else { - result = cx.jsToJava(result, javaResultType, method.getGenericReturnType()); + result = cx.jsToJava(result, TypeInfo.of(javaResultType)); } return result; } diff --git a/src/main/java/dev/latvian/mods/rhino/JavaAdapter.java b/src/main/java/dev/latvian/mods/rhino/JavaAdapter.java index bf3bc4ca..60fc92ac 100644 --- a/src/main/java/dev/latvian/mods/rhino/JavaAdapter.java +++ b/src/main/java/dev/latvian/mods/rhino/JavaAdapter.java @@ -8,6 +8,7 @@ import dev.latvian.mods.rhino.classfile.ByteCode; import dev.latvian.mods.rhino.classfile.ClassFileWriter; +import dev.latvian.mods.rhino.type.TypeInfo; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -85,17 +86,13 @@ public static void init(Context cx, Scriptable scope, boolean sealed) { ctor.exportAsScopeProperty(cx); } - public static Object convertResult(Context cx, Object result, Class c) { - if (result == Undefined.INSTANCE && (c != ScriptRuntime.ObjectClass && c != ScriptRuntime.StringClass)) { + public static Object convertResult(Context cx, Object result, TypeInfo c) { + if (result == Undefined.INSTANCE && (c != TypeInfo.OBJECT && c != TypeInfo.STRING)) { // Avoid an error for an undefined value; return null instead. return null; } - if (c == null) { - return result; - } - - return cx.jsToJava(result, c); + return c == null ? result : cx.jsToJava(result, c); } public static Scriptable createAdapterWrapper(Scriptable obj, Object adapter, Context cx) { diff --git a/src/main/java/dev/latvian/mods/rhino/JavaMembers.java b/src/main/java/dev/latvian/mods/rhino/JavaMembers.java index ef9a45f9..dc640d22 100644 --- a/src/main/java/dev/latvian/mods/rhino/JavaMembers.java +++ b/src/main/java/dev/latvian/mods/rhino/JavaMembers.java @@ -6,6 +6,7 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.ClassVisibilityContext; import dev.latvian.mods.rhino.util.HideFromJS; import dev.latvian.mods.rhino.util.RemapForJS; @@ -15,7 +16,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.Type; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -288,28 +288,25 @@ public Object get(Scriptable scope, String name, Object javaObject, boolean isSt return member; } Object rval; - Class type; - Type genericType; + TypeInfo type; try { if (member instanceof BeanProperty bp) { if (bp.getter == null) { return Scriptable.NOT_FOUND; } rval = bp.getter.invoke(javaObject, ScriptRuntime.EMPTY_OBJECTS, cx, scope); - type = bp.getter.getReturnType(); - genericType = bp.getter.getGenericReturnType(); + type = TypeInfo.of(bp.getter.getGenericReturnType()); } else { Field field = (Field) member; rval = field.get(isStatic ? null : javaObject); - type = field.getType(); - genericType = field.getGenericType(); + type = TypeInfo.of(field.getGenericType()); } } catch (Exception ex) { throw Context.throwAsScriptRuntimeEx(ex, cx); } // Need to wrap the object before we return it. scope = ScriptableObject.getTopLevelScope(scope); - return cx.wrap(scope, rval, type, genericType); + return cx.wrap(scope, rval, type); } public void put(Scriptable scope, String name, Object javaObject, Object value, boolean isStatic, Context cx) { @@ -336,7 +333,7 @@ public void put(Scriptable scope, String name, Object javaObject, Object value, // main setter. Otherwise, let the NativeJavaMethod decide which // setter to use: if (bp.setters == null || value == null) { - Object[] args = {cx.jsToJava(value, bp.setter.argTypes[0], bp.setter.genericArgTypes[0])}; + Object[] args = {cx.jsToJava(value, bp.setter.argTypeInfos[0])}; try { bp.setter.invoke(javaObject, args, cx, scope); } catch (Exception ex) { @@ -358,7 +355,8 @@ public void put(Scriptable scope, String name, Object javaObject, Object value, throw Context.throwAsScriptRuntimeEx(new IllegalAccessException("Can't modify final field " + field.getName()), cx); } - Object javaValue = cx.jsToJava(value, field.getType(), field.getGenericType()); + // This definitely could use some cache + Object javaValue = cx.jsToJava(value, TypeInfo.of(field.getGenericType())); try { field.set(javaObject, javaValue); } catch (IllegalAccessException accessEx) { diff --git a/src/main/java/dev/latvian/mods/rhino/MemberBox.java b/src/main/java/dev/latvian/mods/rhino/MemberBox.java index fdc9a290..79569d15 100644 --- a/src/main/java/dev/latvian/mods/rhino/MemberBox.java +++ b/src/main/java/dev/latvian/mods/rhino/MemberBox.java @@ -6,6 +6,8 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; + import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; @@ -62,8 +64,9 @@ private static Method searchAccessibleMethod(Method method, Class[] params) { return null; } - transient Class[] argTypes; - transient Type[] genericArgTypes; + transient Class[] argTypes; + transient TypeInfo[] argTypeInfos; + transient TypeInfo returnType; transient Object delegateTo; transient boolean vararg; public transient Executable executable; @@ -72,7 +75,8 @@ private static Method searchAccessibleMethod(Method method, Class[] params) { MemberBox(Executable executable) { this.executable = executable; this.argTypes = executable.getParameterTypes(); - this.genericArgTypes = executable.getGenericParameterTypes(); + this.argTypeInfos = TypeInfo.ofArray(executable.getGenericParameterTypes()); + this.returnType = executable instanceof Method m ? TypeInfo.of(m.getGenericReturnType()) : executable instanceof Constructor c ? TypeInfo.of(c.getDeclaringClass()) : TypeInfo.NONE; this.vararg = executable.isVarArgs(); } @@ -82,7 +86,8 @@ private static Method searchAccessibleMethod(Method method, Class[] params) { if (executable != null) { this.executable = executable; this.argTypes = executable.getParameterTypes(); - this.genericArgTypes = executable.getGenericParameterTypes(); + this.argTypeInfos = TypeInfo.ofArray(executable.getGenericParameterTypes()); + this.returnType = executable instanceof Method m ? TypeInfo.of(m.getGenericReturnType()) : executable instanceof Constructor c ? TypeInfo.of(c.getDeclaringClass()) : TypeInfo.NONE; this.vararg = executable.isVarArgs(); } else { this.wrappedExecutable = wrappedExecutable; diff --git a/src/main/java/dev/latvian/mods/rhino/NativeIterator.java b/src/main/java/dev/latvian/mods/rhino/NativeIterator.java index b54c93fc..9e95bdb4 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeIterator.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeIterator.java @@ -6,6 +6,8 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; + import java.util.Iterator; /** @@ -130,7 +132,7 @@ private static Object jsConstructor(Context cx, Scriptable scope, Scriptable thi Iterator iterator = getJavaIterator(obj); if (iterator != null) { scope = getTopLevelScope(scope); - return cx.wrap(scope, new WrappedJavaIterator(cx, iterator, scope), WrappedJavaIterator.class); + return cx.wrap(scope, new WrappedJavaIterator(cx, iterator, scope), TypeInfo.of(WrappedJavaIterator.class)); } // Otherwise, just call the runtime routine diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaArray.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaArray.java index 4c9f4235..9afef64f 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaArray.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaArray.java @@ -6,10 +6,10 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.DefaultValueTypeHint; import java.lang.reflect.Array; -import java.lang.reflect.Type; /** * This class reflects Java arrays into the JavaScript environment. @@ -22,15 +22,13 @@ public class NativeJavaArray extends NativeJavaObject implements SymbolScriptable { Object array; int length; - Class componentType; - Type genericComponentType; + TypeInfo componentType; - public NativeJavaArray(Scriptable scope, Object array, Class componentType, Type genericComponentType, Context cx) { - super(scope, null, ScriptRuntime.ObjectClass, cx); + public NativeJavaArray(Scriptable scope, Object array, TypeInfo type, Context cx) { + super(scope, null, type, cx); this.array = array; this.length = Array.getLength(array); - this.componentType = componentType; - this.genericComponentType = genericComponentType; + this.componentType = type.componentType(); } @Override @@ -74,7 +72,7 @@ public Object get(Context cx, String id, Scriptable start) { public Object get(Context cx, int index, Scriptable start) { if (0 <= index && index < length) { Object obj = Array.get(array, index); - return cx.wrap(this, obj, componentType, genericComponentType); + return cx.wrap(this, obj, componentType); } return Undefined.INSTANCE; } @@ -98,7 +96,7 @@ public void put(Context cx, String id, Scriptable start, Object value) { @Override public void put(Context cx, int index, Scriptable start, Object value) { if (0 <= index && index < length) { - Array.set(array, index, cx.jsToJava(value, componentType, genericComponentType)); + Array.set(array, index, cx.jsToJava(value, componentType)); } else { throw Context.reportRuntimeError2("msg.java.array.index.out.of.bounds", String.valueOf(index), String.valueOf(length - 1), cx); } @@ -139,7 +137,7 @@ public boolean hasInstance(Context cx, Scriptable value) { return false; } Object instance = ((Wrapper) value).unwrap(); - return componentType.isInstance(instance); + return componentType.asClass().isInstance(instance); } @Override diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaClass.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaClass.java index ecf105c4..45fedae1 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaClass.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaClass.java @@ -6,6 +6,7 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.DefaultValueTypeHint; import java.lang.reflect.Array; @@ -36,18 +37,17 @@ static Scriptable constructSpecific(Context cx, Scriptable scope, Object[] args, // we need to force this to be wrapped, because construct _has_ // to return a scriptable Scriptable topLevel = ScriptableObject.getTopLevelScope(scope); - return cx.wrapNewObject(topLevel, instance); + return cx.wrapNewObject(topLevel, instance, ctor.returnType); } static Object constructInternal(Context cx, Scriptable scope, Object[] args, MemberBox ctor) { - var argTypes = ctor.argTypes; - var genericArgTypes = ctor.genericArgTypes; + var argTypes = ctor.argTypeInfos; if (ctor.vararg) { // marshall the explicit parameter Object[] newArgs = new Object[argTypes.length]; for (int i = 0; i < argTypes.length - 1; i++) { - newArgs[i] = cx.jsToJava(args[i], argTypes[i], genericArgTypes[i]); + newArgs[i] = cx.jsToJava(args[i], argTypes[i]); } Object varArgs; @@ -56,12 +56,13 @@ static Object constructInternal(Context cx, Scriptable scope, Object[] args, Mem // is given and it is a Java or ECMA array. if (args.length == argTypes.length && (args[args.length - 1] == null || args[args.length - 1] instanceof NativeArray || args[args.length - 1] instanceof NativeJavaArray)) { // convert the ECMA array into a native array - varArgs = cx.jsToJava(args[args.length - 1], argTypes[argTypes.length - 1], genericArgTypes[argTypes.length - 1]); + varArgs = cx.jsToJava(args[args.length - 1], argTypes[argTypes.length - 1]); } else { // marshall the variable parameter - Class componentType = argTypes[argTypes.length - 1].getComponentType(); - varArgs = Array.newInstance(componentType, args.length - argTypes.length + 1); - for (int i = 0; i < Array.getLength(varArgs); i++) { + var componentType = argTypes[argTypes.length - 1].componentType(); + varArgs = componentType.newArray(args.length - argTypes.length + 1); + int len = Array.getLength(varArgs); + for (int i = 0; i < len; i++) { Object value = cx.jsToJava(args[argTypes.length - 1 + i], componentType); Array.set(varArgs, i, value); } @@ -75,7 +76,7 @@ static Object constructInternal(Context cx, Scriptable scope, Object[] args, Mem Object[] origArgs = args; for (int i = 0; i < args.length; i++) { Object arg = args[i]; - Object x = cx.jsToJava(arg, argTypes[i], genericArgTypes[i]); + Object x = cx.jsToJava(arg, argTypes[i]); if (x != arg) { if (args == origArgs) { args = origArgs.clone(); @@ -152,7 +153,7 @@ public Object get(Context cx, String name, Scriptable start) { Scriptable scope = ScriptableObject.getTopLevelScope(start); if (javaClassPropertyName.equals(name)) { - return cx.wrap(scope, javaObject, ScriptRuntime.ClassClass); + return cx.wrap(scope, javaObject, TypeInfo.CLASS); } // experimental: look for nested classes by appending $name to @@ -236,12 +237,6 @@ public Scriptable construct(Context cx, Scriptable scope, Object[] args) { Scriptable topLevel = ScriptableObject.getTopLevelScope(this); String msg = ""; try { - // When running on Android create an InterfaceAdapter since our - // bytecode generation won't work on Dalvik VM. - if ("Dalvik".equals(System.getProperty("java.vm.name")) && classObject.isInterface()) { - Object obj = cx.createInterfaceAdapter(classObject, classObject, ScriptableObject.ensureScriptableObject(args[0], cx)); - return cx.wrapAsJavaObject(scope, obj, null, null); - } // use JavaAdapter to construct a new class on the fly that // implements/extends this interface/abstract class. Object v = topLevel.get(cx, "JavaAdapter", topLevel); diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaList.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaList.java index 0fd1a465..68c9e7d2 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaList.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaList.java @@ -5,10 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.Deletable; -import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -19,15 +18,15 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class NativeJavaList extends NativeJavaObject { + private static final TypeInfo REDUCE_FUNC_ARG = TypeInfo.of(BinaryOperator.class); + public final List list; - public final Class listType; - public final Type listGenericType; + public final TypeInfo listType; - public NativeJavaList(Context cx, Scriptable scope, Object jo, List list, @Nullable Class listType, @Nullable Type listGenericType) { - super(scope, jo, jo.getClass(), cx); + public NativeJavaList(Context cx, Scriptable scope, Object jo, List list, TypeInfo type) { + super(scope, jo, type, cx); this.list = list; - this.listType = listType; - this.listGenericType = listGenericType; + this.listType = type.param(0); } @Override @@ -54,7 +53,7 @@ public boolean has(Context cx, Symbol key, Scriptable start) { @Override public Object get(Context cx, int index, Scriptable start) { if (isWithValidIndex(index)) { - return cx.javaToJS(list.get(index), start, listType, listGenericType); + return cx.javaToJS(list.get(index), start, listType); } return Undefined.INSTANCE; @@ -71,7 +70,7 @@ public Object get(Context cx, Symbol key, Scriptable start) { @Override public void put(Context cx, int index, Scriptable start, Object value) { if (isWithValidIndex(index)) { - list.set(index, cx.jsToJava(value, listType, listGenericType)); + list.set(index, cx.jsToJava(value, listType)); return; } super.put(cx, index, start, value); @@ -102,26 +101,28 @@ public void delete(Context cx, int index) { @Override protected void initMembers(Context cx, Scriptable scope) { super.initMembers(cx, scope); - addCustomProperty("length", this::getLength); - addCustomFunction("push", this::push, Object.class); - addCustomFunction("pop", this::pop); - addCustomFunction("shift", this::shift); - addCustomFunction("unshift", this::unshift, Object.class); - addCustomFunction("concat", this::concat, List.class); - addCustomFunction("join", this::join, String.class); - addCustomFunction("reverse", this::reverse); - addCustomFunction("slice", this::slice, Object.class); - addCustomFunction("splice", this::splice, Object.class); - addCustomFunction("every", this::every, Predicate.class); - addCustomFunction("some", this::some, Predicate.class); - addCustomFunction("filter", this::filter, Predicate.class); - addCustomFunction("map", this::map, Function.class); - addCustomFunction("reduce", this::reduce, BinaryOperator.class); - addCustomFunction("reduceRight", this::reduceRight, BinaryOperator.class); - addCustomFunction("find", this::find, Predicate.class); - addCustomFunction("findIndex", this::findIndex, Predicate.class); - addCustomFunction("findLast", this::findLast, Predicate.class); - addCustomFunction("findLastIndex", this::findLastIndex, Predicate.class); + var reduceFuncArg = REDUCE_FUNC_ARG.withParams(listType); + + addCustomProperty("length", TypeInfo.INT, this::getLength); + addCustomFunction("push", TypeInfo.INT, this::push, TypeInfo.OBJECT); + addCustomFunction("pop", listType, this::pop); + addCustomFunction("shift", listType, this::shift); + addCustomFunction("unshift", TypeInfo.INT, this::unshift, TypeInfo.OBJECT); + addCustomFunction("concat", typeInfo, this::concat, TypeInfo.RAW_LIST); + addCustomFunction("join", TypeInfo.STRING, this::join, TypeInfo.STRING); + addCustomFunction("reverse", TypeInfo.NONE, this::reverse); + addCustomFunction("slice", TypeInfo.NONE, this::slice, TypeInfo.OBJECT); + addCustomFunction("splice", TypeInfo.NONE, this::splice, TypeInfo.OBJECT); + addCustomFunction("every", TypeInfo.BOOLEAN, this::every, TypeInfo.RAW_PREDICATE); + addCustomFunction("some", TypeInfo.BOOLEAN, this::some, TypeInfo.RAW_PREDICATE); + addCustomFunction("filter", typeInfo, this::filter, TypeInfo.RAW_PREDICATE); + addCustomFunction("map", TypeInfo.RAW_LIST, this::map, TypeInfo.RAW_FUNCTION); + addCustomFunction("reduce", listType, this::reduce, reduceFuncArg); + addCustomFunction("reduceRight", listType, this::reduceRight, reduceFuncArg); + addCustomFunction("find", listType, this::find, TypeInfo.RAW_PREDICATE); + addCustomFunction("findIndex", TypeInfo.NONE, this::findIndex, TypeInfo.RAW_PREDICATE); + addCustomFunction("findLast", listType, this::findLast, TypeInfo.RAW_PREDICATE); + addCustomFunction("findLastIndex", TypeInfo.NONE, this::findLastIndex, TypeInfo.RAW_PREDICATE); } private int getLength(Context cx) { @@ -130,12 +131,12 @@ private int getLength(Context cx) { private int push(Context cx, Object[] args) { if (args.length == 1) { - list.add(cx.jsToJava(args[0], listType, listGenericType)); + list.add(cx.jsToJava(args[0], listType)); } else if (args.length > 1) { Object[] args1 = new Object[args.length]; for (int i = 0; i < args.length; i++) { - args1[i] = cx.jsToJava(args[i], listType, listGenericType); + args1[i] = cx.jsToJava(args[i], listType); } list.addAll(Arrays.asList(args1)); @@ -162,7 +163,7 @@ private Object shift(Context cx) { private int unshift(Context cx, Object[] args) { for (int i = args.length - 1; i >= 0; i--) { - list.add(0, cx.jsToJava(args[i], listType, listGenericType)); + list.add(0, cx.jsToJava(args[i], listType)); } return list.size(); @@ -172,7 +173,7 @@ private Object concat(Context cx, Object[] args) { List list1 = new ArrayList<>(list); if (args.length > 0 && args[0] instanceof List) { - list1.addAll((List) args[0]); + list1.addAll((List) cx.jsToJava(args[0], typeInfo)); } return list1; diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaMap.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaMap.java index a925594a..b2f99c19 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaMap.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaMap.java @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.Deletable; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -15,18 +15,14 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class NativeJavaMap extends NativeJavaObject { public final Map map; - public final Class mapKeyType; - public final Type mapKeyGenericType; - public final Class mapValueType; - public final Type mapValueGenericType; + public final TypeInfo mapKeyType; + public final TypeInfo mapValueType; - public NativeJavaMap(Context cx, Scriptable scope, Object jo, Map map, Class mapKeyType, Type mapKeyGenericType, Class mapValueType, Type mapValueGenericType) { - super(scope, jo, jo.getClass(), cx); + public NativeJavaMap(Context cx, Scriptable scope, Object jo, Map map, TypeInfo type) { + super(scope, jo, type, cx); this.map = map; - this.mapKeyType = mapKeyType; - this.mapKeyGenericType = mapKeyGenericType; - this.mapValueType = mapValueType; - this.mapValueGenericType = mapValueGenericType; + this.mapKeyType = type.param(0); + this.mapValueType = type.param(1); } @Override @@ -53,7 +49,7 @@ public boolean has(Context cx, int index, Scriptable start) { @Override public Object get(Context cx, String name, Scriptable start) { if (map.containsKey(name)) { - return cx.javaToJS(map.get(cx.jsToJava(name, mapKeyType, mapKeyGenericType)), start, mapValueType, mapValueGenericType); + return cx.javaToJS(map.get(cx.jsToJava(name, mapKeyType)), start, mapValueType); } return super.get(cx, name, start); } @@ -61,19 +57,19 @@ public Object get(Context cx, String name, Scriptable start) { @Override public Object get(Context cx, int index, Scriptable start) { if (map.containsKey(index)) { - return cx.javaToJS(map.get(cx.jsToJava(index, mapKeyType, mapKeyGenericType)), start, mapValueType, mapValueGenericType); + return cx.javaToJS(map.get(cx.jsToJava(index, mapKeyType)), start, mapValueType); } return super.get(cx, index, start); } @Override public void put(Context cx, String name, Scriptable start, Object value) { - map.put(name, cx.jsToJava(value, mapValueType, mapValueGenericType)); + map.put(name, cx.jsToJava(value, mapValueType)); } @Override public void put(Context cx, int index, Scriptable start, Object value) { - map.put(index, cx.jsToJava(value, mapValueType, mapValueGenericType)); + map.put(index, cx.jsToJava(value, mapValueType)); } @Override @@ -102,7 +98,7 @@ public void delete(Context cx, int index) { @Override protected void initMembers(Context cx, Scriptable scope) { super.initMembers(cx, scope); - addCustomFunction("hasOwnProperty", this::hasOwnProperty, String.class); + addCustomFunction("hasOwnProperty", TypeInfo.BOOLEAN, this::hasOwnProperty, TypeInfo.STRING); } private boolean hasOwnProperty(Context cx, Object[] args) { diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaMethod.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaMethod.java index eed3ec48..c7cd4a45 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaMethod.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaMethod.java @@ -6,9 +6,10 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; + import java.lang.reflect.Array; import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -93,7 +94,7 @@ static int findFunction(Context cx, MemberBox[] methodsOrCtors, Object[] args) { } } for (int j = 0; j != alength; ++j) { - if (!cx.canConvert(args[j], member.argTypes[j], member.genericArgTypes[j])) { + if (!cx.canConvert(args[j], member.argTypeInfos[j])) { if (debug) { printDebug("Rejecting (args can't convert) ", member, args); } @@ -125,7 +126,7 @@ static int findFunction(Context cx, MemberBox[] methodsOrCtors, Object[] args) { } } for (int j = 0; j < alength; j++) { - if (!cx.canConvert(args[j], member.argTypes[j], member.genericArgTypes[j])) { + if (!cx.canConvert(args[j], member.argTypeInfos[j])) { if (debug) { printDebug("Rejecting (args can't convert) ", member, args); } @@ -154,7 +155,7 @@ static int findFunction(Context cx, MemberBox[] methodsOrCtors, Object[] args) { bestFitIndex = extraBestFits[j]; } MemberBox bestFit = methodsOrCtors[bestFitIndex]; - int preference = preferSignature(cx, args, member.argTypes, member.genericArgTypes, member.vararg, bestFit.argTypes, bestFit.genericArgTypes, bestFit.vararg); + int preference = preferSignature(cx, args, member.argTypeInfos, member.vararg, bestFit.argTypeInfos, bestFit.vararg); if (preference == PREFERENCE_AMBIGUOUS) { break; } else if (preference == PREFERENCE_FIRST_ARG) { @@ -254,25 +255,22 @@ static int findFunction(Context cx, MemberBox[] methodsOrCtors, Object[] args) { * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS. */ - private static int preferSignature(Context cx, Object[] args, Class[] sig1, Type[] gsig1, boolean vararg1, Class[] sig2, Type[] gsig2, boolean vararg2) { + private static int preferSignature(Context cx, Object[] args, TypeInfo[] sig1, boolean vararg1, TypeInfo[] sig2, boolean vararg2) { int totalPreference = 0; for (int j = 0; j < args.length; j++) { - Class type1 = vararg1 && j >= sig1.length ? sig1[sig1.length - 1] : sig1[j]; - Class type2 = vararg2 && j >= sig2.length ? sig2[sig2.length - 1] : sig2[j]; + var type1 = vararg1 && j >= sig1.length ? sig1[sig1.length - 1] : sig1[j]; + var type2 = vararg2 && j >= sig2.length ? sig2[sig2.length - 1] : sig2[j]; - if (type1 == type2) { + if (type1.equals(type2)) { continue; } - Type gType1 = vararg1 && j >= gsig1.length ? gsig1[gsig1.length - 1] : gsig1[j]; - Type gType2 = vararg2 && j >= gsig2.length ? gsig2[gsig2.length - 1] : gsig2[j]; - Object arg = args[j]; // Determine which of type1, type2 is easier to convert from arg. - int rank1 = cx.getConversionWeight(arg, type1, gType1); - int rank2 = cx.getConversionWeight(arg, type2, gType2); + int rank1 = cx.getConversionWeight(arg, type1); + int rank2 = cx.getConversionWeight(arg, type2); int preference; if (rank1 < rank2) { @@ -282,9 +280,9 @@ private static int preferSignature(Context cx, Object[] args, Class[] sig1, T } else { // Equal ranks if (rank1 == Context.CONVERSION_NONTRIVIAL) { - if (type1.isAssignableFrom(type2)) { + if (type1.asClass().isAssignableFrom(type2.asClass())) { preference = PREFERENCE_SECOND_ARG; - } else if (type2.isAssignableFrom(type1)) { + } else if (type2.asClass().isAssignableFrom(type1.asClass())) { preference = PREFERENCE_FIRST_ARG; } else { preference = PREFERENCE_AMBIGUOUS; @@ -386,14 +384,13 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar } MemberBox meth = methods[index]; - var argTypes = meth.argTypes; - var genericArgTypes = meth.genericArgTypes; + var argTypes = meth.argTypeInfos; if (meth.vararg) { // marshall the explicit parameters Object[] newArgs = new Object[argTypes.length]; for (int i = 0; i < argTypes.length - 1; i++) { - newArgs[i] = cx.jsToJava(args[i], argTypes[i], genericArgTypes[i]); + newArgs[i] = cx.jsToJava(args[i], argTypes[i]); } Object varArgs; @@ -402,12 +399,13 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar // is given and it is a Java or ECMA array or is null. if (args.length == argTypes.length && (args[args.length - 1] == null || args[args.length - 1] instanceof NativeArray || args[args.length - 1] instanceof NativeJavaArray)) { // convert the ECMA array into a native array - varArgs = cx.jsToJava(args[args.length - 1], argTypes[argTypes.length - 1], genericArgTypes[argTypes.length - 1]); + varArgs = cx.jsToJava(args[args.length - 1], argTypes[argTypes.length - 1]); } else { // marshall the variable parameters - Class componentType = argTypes[argTypes.length - 1].getComponentType(); - varArgs = Array.newInstance(componentType, args.length - argTypes.length + 1); - for (int i = 0; i < Array.getLength(varArgs); i++) { + var componentType = argTypes[argTypes.length - 1].componentType(); + varArgs = Array.newInstance(componentType.asClass(), args.length - argTypes.length + 1); + int len = Array.getLength(varArgs); + for (int i = 0; i < len; i++) { Object value = cx.jsToJava(args[argTypes.length - 1 + i], componentType); Array.set(varArgs, i, value); } @@ -434,7 +432,7 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar } */ - coerced = cx.jsToJava(coerced, argTypes[i], genericArgTypes[i]); + coerced = cx.jsToJava(coerced, argTypes[i]); if (coerced != arg) { if (origArgs == args) { @@ -468,20 +466,20 @@ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] ar } Object retval = meth.invoke(javaObject, args, cx, scope); - Class staticType = meth.getReturnType(); + var staticType = meth.returnType; if (debug) { Class actualType = (retval == null) ? null : retval.getClass(); System.err.println(" ----- Returned " + retval + " actual = " + actualType + " expect = " + staticType); } - Object wrapped = cx.wrap(scope, retval, staticType, meth.getGenericReturnType()); + Object wrapped = cx.wrap(scope, retval, staticType); if (debug) { Class actualType = (wrapped == null) ? null : wrapped.getClass(); System.err.println(" ----- Wrapped as " + wrapped + " class = " + actualType); } - if (wrapped == null && staticType == Void.TYPE) { + if (wrapped == null && staticType == TypeInfo.VOID) { wrapped = Undefined.INSTANCE; } return wrapped; diff --git a/src/main/java/dev/latvian/mods/rhino/NativeJavaObject.java b/src/main/java/dev/latvian/mods/rhino/NativeJavaObject.java index 09dd4f35..7d6eaab7 100644 --- a/src/main/java/dev/latvian/mods/rhino/NativeJavaObject.java +++ b/src/main/java/dev/latvian/mods/rhino/NativeJavaObject.java @@ -6,6 +6,7 @@ package dev.latvian.mods.rhino; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.DefaultValueTypeHint; import dev.latvian.mods.rhino.util.Deletable; import dev.latvian.mods.rhino.util.JavaIteratorWrapper; @@ -35,20 +36,20 @@ public class NativeJavaObject implements Scriptable, SymbolScriptable, Wrapper { */ protected Scriptable parent; protected transient Object javaObject; - protected transient Class staticType; + protected transient TypeInfo typeInfo; protected transient JavaMembers members; protected transient Map fieldAndMethods; - protected transient Map customMembers; + protected transient Map customMembers; protected transient boolean isAdapter; - public NativeJavaObject(Scriptable scope, Object javaObject, Class staticType, Context cx) { - this(scope, javaObject, staticType, false, cx); + public NativeJavaObject(Scriptable scope, Object javaObject, TypeInfo typeInfo, Context cx) { + this(scope, javaObject, typeInfo, false, cx); } - public NativeJavaObject(Scriptable scope, Object javaObject, Class staticType, boolean isAdapter, Context cx) { + public NativeJavaObject(Scriptable scope, Object javaObject, TypeInfo typeInfo, boolean isAdapter, Context cx) { this.parent = scope; this.javaObject = javaObject; - this.staticType = staticType; + this.typeInfo = typeInfo; this.isAdapter = isAdapter; initMembers(cx, scope); } @@ -58,31 +59,31 @@ protected void initMembers(Context cx, Scriptable scope) { if (javaObject != null) { dynamicType = javaObject.getClass(); } else { - dynamicType = staticType; + dynamicType = typeInfo.asClass(); } - members = JavaMembers.lookupClass(cx, scope, dynamicType, staticType, isAdapter); + members = JavaMembers.lookupClass(cx, scope, dynamicType, typeInfo.asClass(), isAdapter); fieldAndMethods = members.getFieldAndMethodsObjects(this, javaObject, false, cx); customMembers = null; } - protected void addCustomMember(String name, Object fm) { + public void addCustomMember(CustomMember member) { if (customMembers == null) { customMembers = new HashMap<>(); } - customMembers.put(name, fm); + customMembers.put(member.name(), member); } - protected void addCustomFunction(String name, CustomFunction.Func func, Class... argTypes) { - addCustomMember(name, new CustomFunction(name, func, argTypes)); + protected void addCustomFunction(String name, TypeInfo returnType, CustomFunction.Func func, TypeInfo... argTypes) { + addCustomMember(new CustomMember(name, returnType, new CustomFunction(name, func, argTypes))); } - protected void addCustomFunction(String name, CustomFunction.NoArgFunc func) { - addCustomFunction(name, func, CustomFunction.NO_ARGS); + protected void addCustomFunction(String name, TypeInfo returnType, CustomFunction.NoArgFunc func) { + addCustomFunction(name, returnType, func, TypeInfo.EMPTY_ARRAY); } - public void addCustomProperty(String name, CustomProperty getter) { - addCustomMember(name, getter); + public void addCustomProperty(String name, TypeInfo type, CustomProperty getter) { + addCustomMember(new CustomMember(name, type, getter)); } @Override @@ -110,26 +111,16 @@ public Object get(Context cx, String name, Scriptable start) { } if (customMembers != null) { - Object result = customMembers.get(name); - - if (result != null) { - if (result instanceof CustomProperty) { - Object r = ((CustomProperty) result).get(cx); + var member = customMembers.get(name); - if (r == null) { - return Undefined.INSTANCE; - } + if (member != null) { + var value = member.value(); - Object r1 = cx.wrap(this, r, r.getClass()); - - if (r1 instanceof Scriptable) { - return ((Scriptable) r1).getDefaultValue(cx, null); - } - - return r1; + if (value instanceof CustomProperty p) { + value = p.get(cx); } - return result; + return cx.javaToJS(value, start, member.type()); } } diff --git a/src/main/java/dev/latvian/mods/rhino/ScriptRuntime.java b/src/main/java/dev/latvian/mods/rhino/ScriptRuntime.java index 6de928bd..04ad520c 100644 --- a/src/main/java/dev/latvian/mods/rhino/ScriptRuntime.java +++ b/src/main/java/dev/latvian/mods/rhino/ScriptRuntime.java @@ -931,42 +931,35 @@ public static Scriptable toObjectOrNull(Context cx, Object obj, Scriptable scope public static Scriptable toObject(Context cx, Scriptable scope, Object val) { if (val == null) { throw typeError0(cx, "msg.null.to.object"); - } - if (Undefined.isUndefined(val)) { + } else if (Undefined.isUndefined(val)) { throw typeError0(cx, "msg.undef.to.object"); - } - - if (isSymbol(val)) { + } else if (isSymbol(val)) { NativeSymbol result = new NativeSymbol((NativeSymbol) val); setBuiltinProtoAndParent(cx, scope, result, TopLevel.Builtins.Symbol); return result; - } - if (val instanceof Scriptable) { + } else if (val instanceof Scriptable) { return (Scriptable) val; - } - if (val instanceof CharSequence) { + } else if (val instanceof CharSequence) { // FIXME we want to avoid toString() here, especially for concat() NativeString result = new NativeString((CharSequence) val); setBuiltinProtoAndParent(cx, scope, result, TopLevel.Builtins.String); return result; - } - if (val instanceof Number) { + } else if (val instanceof Number) { NativeNumber result = new NativeNumber(cx, ((Number) val).doubleValue()); setBuiltinProtoAndParent(cx, scope, result, TopLevel.Builtins.Number); return result; - } - if (val instanceof Boolean) { + } else if (val instanceof Boolean) { NativeBoolean result = new NativeBoolean((Boolean) val); setBuiltinProtoAndParent(cx, scope, result, TopLevel.Builtins.Boolean); return result; + } else { + // Extension: Wrap as a LiveConnect object. + Object wrapped = cx.wrap(scope, val); + if (wrapped instanceof Scriptable) { + return (Scriptable) wrapped; + } + throw errorWithClassName("msg.invalid.type", val, cx); } - - // Extension: Wrap as a LiveConnect object. - Object wrapped = cx.wrap(scope, val); - if (wrapped instanceof Scriptable) { - return (Scriptable) wrapped; - } - throw errorWithClassName("msg.invalid.type", val, cx); } public static Scriptable newObject(Context cx, Scriptable scope, String constructorName, Object[] args) { diff --git a/src/main/java/dev/latvian/mods/rhino/VMBridge.java b/src/main/java/dev/latvian/mods/rhino/VMBridge.java index 9b557416..440e9c50 100644 --- a/src/main/java/dev/latvian/mods/rhino/VMBridge.java +++ b/src/main/java/dev/latvian/mods/rhino/VMBridge.java @@ -52,19 +52,20 @@ public static Object newInterfaceProxy(Object proxyHelper, final InterfaceAdapte // invocation handler. if (method.getDeclaringClass() == Object.class) { String methodName = method.getName(); - if (methodName.equals("equals")) { - Object other = args[0]; - // Note: we could compare a proxy and its wrapped function - // as equal here but that would break symmetry of equal(). - // The reason == suffices here is that proxies are cached - // in ScriptableObject (see NativeJavaObject.coerceType()) - return proxy == other; - } - if (methodName.equals("hashCode")) { - return target.hashCode(); - } - if (methodName.equals("toString")) { - return "Proxy[" + target.toString() + "]"; + switch (methodName) { + case "equals" -> { + // Note: we could compare a proxy and its wrapped function + // as equal here but that would break symmetry of equal(). + // The reason == suffices here is that proxies are cached + // in ScriptableObject (see NativeJavaObject.coerceType()) + return proxy == args[0]; + } + case "hashCode" -> { + return target.hashCode(); + } + case "toString" -> { + return "Proxy[" + target + "]"; + } } } diff --git a/src/main/java/dev/latvian/mods/rhino/type/ArrayTypeInfo.java b/src/main/java/dev/latvian/mods/rhino/type/ArrayTypeInfo.java new file mode 100644 index 00000000..7cfc864e --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/type/ArrayTypeInfo.java @@ -0,0 +1,46 @@ +package dev.latvian.mods.rhino.type; + +import java.lang.reflect.Array; + +public final class ArrayTypeInfo implements TypeInfo { + private final TypeInfo component; + private Class asClass; + + public ArrayTypeInfo(TypeInfo component) { + this.component = component; + } + + @Override + public Class asClass() { + if (asClass == null) { + asClass = Array.newInstance(component.asClass(), 0).getClass(); + } + + return asClass; + } + + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof ArrayTypeInfo t && component.equals(t.component); + } + + @Override + public int hashCode() { + return component.hashCode(); + } + + @Override + public String toString() { + return component + "[]"; + } + + @Override + public String signature() { + return component.signature() + "[]"; + } + + @Override + public TypeInfo componentType() { + return component; + } +} diff --git a/src/main/java/dev/latvian/mods/rhino/type/ClassTypeInfo.java b/src/main/java/dev/latvian/mods/rhino/type/ClassTypeInfo.java new file mode 100644 index 00000000..dbf2774c --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/type/ClassTypeInfo.java @@ -0,0 +1,60 @@ +package dev.latvian.mods.rhino.type; + +import java.util.IdentityHashMap; +import java.util.Map; + +public final class ClassTypeInfo implements TypeInfo { + static final Map, ClassTypeInfo> CACHE = new IdentityHashMap<>(); + + private final Class type; + private final boolean primitive; + private TypeInfo asArray; + + public ClassTypeInfo(Class type, boolean primitive) { + this.type = type; + this.primitive = primitive; + } + + public ClassTypeInfo(Class type) { + this(type, false); + } + + @Override + public Class asClass() { + return type; + } + + @Override + public boolean isPrimitive() { + return primitive; + } + + @Override + public boolean convert() { + return type != Object.class; + } + + @Override + public int hashCode() { + return type.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o == this || o instanceof ClassTypeInfo t && type == t.type; + } + + @Override + public String toString() { + return type.getName(); + } + + @Override + public TypeInfo asArray() { + if (asArray == null) { + asArray = new ArrayTypeInfo(this); + } + + return asArray; + } +} diff --git a/src/main/java/dev/latvian/mods/rhino/type/NoTypeInfo.java b/src/main/java/dev/latvian/mods/rhino/type/NoTypeInfo.java new file mode 100644 index 00000000..3f6a8ee5 --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/type/NoTypeInfo.java @@ -0,0 +1,38 @@ +package dev.latvian.mods.rhino.type; + +public final class NoTypeInfo implements TypeInfo { + @Override + public Class asClass() { + return Object.class; + } + + @Override + public boolean convert() { + return false; + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "?"; + } + + @Override + public TypeInfo asArray() { + return this; + } + + @Override + public TypeInfo withParams(TypeInfo... params) { + return this; + } +} diff --git a/src/main/java/dev/latvian/mods/rhino/type/ParameterizedTypeInfo.java b/src/main/java/dev/latvian/mods/rhino/type/ParameterizedTypeInfo.java new file mode 100644 index 00000000..e316c4f5 --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/type/ParameterizedTypeInfo.java @@ -0,0 +1,50 @@ +package dev.latvian.mods.rhino.type; + +import java.util.Arrays; +import java.util.Objects; + +public record ParameterizedTypeInfo(TypeInfo rawType, TypeInfo[] params) implements TypeInfo { + @Override + public Class asClass() { + return rawType.asClass(); + } + + @Override + public TypeInfo param(int index) { + return index >= 0 && index < params.length && params[index] != TypeInfo.OBJECT ? params[index] : TypeInfo.NONE; + } + + @Override + public int hashCode() { + return Objects.hash(rawType, Arrays.hashCode(params)); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + ParameterizedTypeInfo that = (ParameterizedTypeInfo) object; + return Objects.equals(rawType, that.rawType) && Objects.deepEquals(params, that.params); + } + + @Override + public String toString() { + var sb = new StringBuilder(rawType.toString()); + sb.append('<'); + + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + + sb.append(params[i]); + } + + sb.append('>'); + return sb.toString(); + } +} diff --git a/src/main/java/dev/latvian/mods/rhino/type/TypeInfo.java b/src/main/java/dev/latvian/mods/rhino/type/TypeInfo.java new file mode 100644 index 00000000..727056ff --- /dev/null +++ b/src/main/java/dev/latvian/mods/rhino/type/TypeInfo.java @@ -0,0 +1,170 @@ +package dev.latvian.mods.rhino.type; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public sealed interface TypeInfo permits NoTypeInfo, ClassTypeInfo, ArrayTypeInfo, ParameterizedTypeInfo { + TypeInfo NONE = new NoTypeInfo(); + + TypeInfo[] EMPTY_ARRAY = new TypeInfo[0]; + + TypeInfo OBJECT = new ClassTypeInfo(Object.class); + TypeInfo OBJECT_ARRAY = OBJECT.asArray(); + TypeInfo VOID = new ClassTypeInfo(Void.class); + TypeInfo STRING = new ClassTypeInfo(String.class); + TypeInfo STRING_ARRAY = STRING.asArray(); + TypeInfo BOOLEAN = new ClassTypeInfo(Boolean.class, true); + TypeInfo NUMBER = new ClassTypeInfo(Number.class); + TypeInfo BYTE = new ClassTypeInfo(Byte.class, true); + TypeInfo SHORT = new ClassTypeInfo(Short.class, true); + TypeInfo INT = new ClassTypeInfo(Integer.class, true); + TypeInfo LONG = new ClassTypeInfo(Long.class, true); + TypeInfo FLOAT = new ClassTypeInfo(Float.class, true); + TypeInfo DOUBLE = new ClassTypeInfo(Double.class, true); + TypeInfo CHARACTER = new ClassTypeInfo(Character.class, true); + TypeInfo CLASS = new ClassTypeInfo(Class.class); + TypeInfo DATE = new ClassTypeInfo(Date.class); + + TypeInfo RUNNABLE = new ClassTypeInfo(Runnable.class); + TypeInfo RAW_CONSUMER = new ClassTypeInfo(Consumer.class); + TypeInfo RAW_SUPPLIER = new ClassTypeInfo(Supplier.class); + TypeInfo RAW_FUNCTION = new ClassTypeInfo(Function.class); + TypeInfo RAW_PREDICATE = new ClassTypeInfo(Predicate.class); + + TypeInfo RAW_LIST = new ClassTypeInfo(List.class); + TypeInfo RAW_SET = new ClassTypeInfo(Set.class); + TypeInfo RAW_MAP = new ClassTypeInfo(Map.class); + + Class asClass(); + + default TypeInfo param(int index) { + return NONE; + } + + default boolean isPrimitive() { + return false; + } + + default boolean convert() { + return true; + } + + static TypeInfo of(Class c) { + if (c == null || c == Object.class) { + return OBJECT; + } else if (c == Void.class || c == Void.TYPE) { + return VOID; + } else if (c == String.class) { + return STRING; + } else if (c == Boolean.class || c == Boolean.TYPE) { + return BOOLEAN; + } else if (c == Number.class) { + return NUMBER; + } else if (c == Byte.class || c == Byte.TYPE) { + return BYTE; + } else if (c == Short.class || c == Short.TYPE) { + return SHORT; + } else if (c == Integer.class || c == Integer.TYPE) { + return INT; + } else if (c == Long.class || c == Long.TYPE) { + return LONG; + } else if (c == Float.class || c == Float.TYPE) { + return FLOAT; + } else if (c == Double.class || c == Double.TYPE) { + return DOUBLE; + } else if (c == Character.class || c == Character.TYPE) { + return CHARACTER; + } else if (c == Class.class) { + return CLASS; + } else if (c == Date.class) { + return DATE; + } else if (c == Runnable.class) { + return RUNNABLE; + } else if (c == Consumer.class) { + return RAW_CONSUMER; + } else if (c == Supplier.class) { + return RAW_SUPPLIER; + } else if (c == Function.class) { + return RAW_FUNCTION; + } else if (c == Predicate.class) { + return RAW_PREDICATE; + } else if (c == List.class) { + return RAW_LIST; + } else if (c == Set.class) { + return RAW_SET; + } else if (c == Map.class) { + return RAW_MAP; + } else if (c == Object[].class) { + return OBJECT_ARRAY; + } else if (c == String[].class) { + return STRING_ARRAY; + } else if (c.isArray()) { + return of(c.getComponentType()).asArray(); + } else { + synchronized (ClassTypeInfo.CACHE) { + return ClassTypeInfo.CACHE.computeIfAbsent(c, ClassTypeInfo::new); + } + } + } + + static TypeInfo of(Type type) { + return switch (type) { + case Class clz -> of(clz); + case ParameterizedType paramType -> of(paramType.getRawType()).withParams(ofArray(paramType.getActualTypeArguments())); + case GenericArrayType arrType -> of(arrType.getGenericComponentType()).asArray(); + case TypeVariable ignore -> NONE; // ClassTypeInfo.OBJECT + case WildcardType wildcard -> of(wildcard.getUpperBounds()[0]); + case null, default -> NONE; + }; + } + + static TypeInfo[] ofArray(Type[] array) { + if (array.length == 0) { + return EMPTY_ARRAY; + } else { + var arr = new TypeInfo[array.length]; + + for (int i = 0; i < array.length; i++) { + arr[i] = of(array[i]); + } + + return arr; + } + } + + default String signature() { + return toString(); + } + + default TypeInfo componentType() { + return NONE; + } + + default Object newArray(int length) { + return Array.newInstance(asClass(), length); + } + + default TypeInfo asArray() { + return new ArrayTypeInfo(this); + } + + default TypeInfo withParams(TypeInfo... params) { + if (params.length == 0) { + return this; + } + + return new ParameterizedTypeInfo(this, params); + } +} diff --git a/src/main/java/dev/latvian/mods/rhino/util/ArrayValueProvider.java b/src/main/java/dev/latvian/mods/rhino/util/ArrayValueProvider.java index fa57a136..e799c3c4 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/ArrayValueProvider.java +++ b/src/main/java/dev/latvian/mods/rhino/util/ArrayValueProvider.java @@ -3,9 +3,9 @@ import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.EvaluatorException; import dev.latvian.mods.rhino.NativeArray; +import dev.latvian.mods.rhino.type.TypeInfo; import java.lang.reflect.Array; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -37,13 +37,13 @@ public Object getErrorSource(Context cx) { Object getErrorSource(Context cx); - default Object createArray(Context cx, Class target, Type genericTarget) { + default Object createArray(Context cx, TypeInfo target) { int len = getLength(cx); - var arr = Array.newInstance(target, len); + var arr = target.newArray(len); for (int i = 0; i < len; i++) { try { - Array.set(arr, i, cx.jsToJava(getArrayValue(cx, i), target, genericTarget)); + Array.set(arr, i, cx.jsToJava(getArrayValue(cx, i), target)); } catch (EvaluatorException ee) { return cx.reportConversionError(getErrorSource(cx), target); } @@ -52,14 +52,14 @@ default Object createArray(Context cx, Class target, Type genericTarget) { return arr; } - default Object createList(Context cx, Class target, Type genericTarget) { + default Object createList(Context cx, TypeInfo target) { int len = getLength(cx); if (len == 0) { return List.of(); } else if (len == 1) { try { - return List.of(cx.jsToJava(getArrayValue(cx, 0), target, genericTarget)); + return List.of(cx.jsToJava(getArrayValue(cx, 0), target)); } catch (EvaluatorException ee) { return cx.reportConversionError(getErrorSource(cx), target); } @@ -69,7 +69,7 @@ default Object createList(Context cx, Class target, Type genericTarget) { for (int i = 0; i < len; i++) { try { - list.add(cx.jsToJava(getArrayValue(cx, i), target, genericTarget)); + list.add(cx.jsToJava(getArrayValue(cx, i), target)); } catch (EvaluatorException ee) { return cx.reportConversionError(getErrorSource(cx), target); } @@ -78,14 +78,14 @@ default Object createList(Context cx, Class target, Type genericTarget) { return list; } - default Object createSet(Context cx, Class target, Type genericTarget) { + default Object createSet(Context cx, TypeInfo target) { int len = getLength(cx); if (len == 0) { return Set.of(); } else if (len == 1) { try { - return Set.of(cx.jsToJava(getArrayValue(cx, 0), target, genericTarget)); + return Set.of(cx.jsToJava(getArrayValue(cx, 0), target)); } catch (EvaluatorException ee) { return cx.reportConversionError(getErrorSource(cx), target); } @@ -95,7 +95,7 @@ default Object createSet(Context cx, Class target, Type genericTarget) { for (int i = 0; i < len; i++) { try { - set.add(cx.jsToJava(getArrayValue(cx, i), target, genericTarget)); + set.add(cx.jsToJava(getArrayValue(cx, i), target)); } catch (EvaluatorException ee) { return cx.reportConversionError(getErrorSource(cx), target); } diff --git a/src/main/java/dev/latvian/mods/rhino/util/CustomJavaToJsWrapper.java b/src/main/java/dev/latvian/mods/rhino/util/CustomJavaToJsWrapper.java index 0bac78c6..3c84ffff 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/CustomJavaToJsWrapper.java +++ b/src/main/java/dev/latvian/mods/rhino/util/CustomJavaToJsWrapper.java @@ -2,10 +2,9 @@ import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.Scriptable; - -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; @FunctionalInterface public interface CustomJavaToJsWrapper { - Scriptable convertJavaToJs(Context cx, Scriptable scope, Class staticType, Type genericType); + Scriptable convertJavaToJs(Context cx, Scriptable scope, TypeInfo staticType); } diff --git a/src/main/java/dev/latvian/mods/rhino/util/EnumTypeWrapper.java b/src/main/java/dev/latvian/mods/rhino/util/EnumTypeWrapper.java index d7d115fd..9af856fd 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/EnumTypeWrapper.java +++ b/src/main/java/dev/latvian/mods/rhino/util/EnumTypeWrapper.java @@ -1,9 +1,9 @@ package dev.latvian.mods.rhino.util; import dev.latvian.mods.rhino.Context; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.wrap.TypeWrapperFactory; -import java.lang.reflect.Type; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; @@ -60,7 +60,7 @@ private EnumTypeWrapper(Class enumType) { } @Override - public T wrap(Context cx, Object from, Class target, Type genericTarget) { + public T wrap(Context cx, Object from, TypeInfo target) { if (from instanceof CharSequence) { String s = from.toString().toLowerCase(); diff --git a/src/main/java/dev/latvian/mods/rhino/util/wrap/DirectTypeWrapperFactory.java b/src/main/java/dev/latvian/mods/rhino/util/wrap/DirectTypeWrapperFactory.java index a3157b92..8fa42814 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/wrap/DirectTypeWrapperFactory.java +++ b/src/main/java/dev/latvian/mods/rhino/util/wrap/DirectTypeWrapperFactory.java @@ -1,8 +1,7 @@ package dev.latvian.mods.rhino.util.wrap; import dev.latvian.mods.rhino.Context; - -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; /** * @author LatvianModder @@ -11,7 +10,7 @@ public interface DirectTypeWrapperFactory extends TypeWrapperFactory { T wrap(Object from); - default T wrap(Context cx, Object from, Class target, Type genericTarget) { + default T wrap(Context cx, Object from, TypeInfo target) { return wrap(from); } } diff --git a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperFactory.java b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperFactory.java index 003cafc1..c7835e57 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperFactory.java +++ b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperFactory.java @@ -1,13 +1,12 @@ package dev.latvian.mods.rhino.util.wrap; import dev.latvian.mods.rhino.Context; - -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; /** * @author LatvianModder */ @FunctionalInterface public interface TypeWrapperFactory { - T wrap(Context cx, Object from, Class target, Type genericTarget); + T wrap(Context cx, Object from, TypeInfo target); } diff --git a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperValidator.java b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperValidator.java index 7e4a2fa2..6c309d6a 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperValidator.java +++ b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrapperValidator.java @@ -1,10 +1,10 @@ package dev.latvian.mods.rhino.util.wrap; -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; @FunctionalInterface public interface TypeWrapperValidator { - TypeWrapperValidator ALWAYS_VALID = (from, target, genericTarget) -> true; + TypeWrapperValidator ALWAYS_VALID = (from, target) -> true; - boolean isValid(Object from, Class target, Type genericTarget); + boolean isValid(Object from, TypeInfo target); } diff --git a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrappers.java b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrappers.java index 89fc6864..9ec4ec8b 100644 --- a/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrappers.java +++ b/src/main/java/dev/latvian/mods/rhino/util/wrap/TypeWrappers.java @@ -1,9 +1,9 @@ package dev.latvian.mods.rhino.util.wrap; +import dev.latvian.mods.rhino.type.TypeInfo; import dev.latvian.mods.rhino.util.EnumTypeWrapper; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Type; import java.util.IdentityHashMap; import java.util.Map; @@ -37,36 +37,38 @@ public void registerDirect(Class target, DirectTypeWrapperFactory fact register(target, TypeWrapperValidator.ALWAYS_VALID, factory); } - public boolean hasWrapper(Object from, Class target, Type genericTarget) { - if (target.isEnum() || target.isRecord()) { + public boolean hasWrapper(Object from, TypeInfo target) { + var cl = target.asClass(); + + if (cl == null) { + return false; + } else if (cl.isEnum() || cl.isRecord()) { return true; } - var wrapper = wrappers.get(target); - return wrapper != null && wrapper.validator().isValid(from, target, genericTarget); + var wrapper = wrappers.get(cl); + return wrapper != null && wrapper.validator().isValid(from, target); } @Nullable - public TypeWrapperFactory getWrapperFactory(@Nullable Object from, Class target, Type genericTarget) { - if (target == Object.class) { + public TypeWrapperFactory getWrapperFactory(@Nullable Object from, TypeInfo target) { + if (target == TypeInfo.OBJECT) { return null; } - var wrapper = wrappers.get(target); + var cl = target.asClass(); - if (wrapper != null && wrapper.validator().isValid(from, target, genericTarget)) { - return wrapper.factory(); - } else if (target.isEnum()) { - return EnumTypeWrapper.get(target); - } else if (target.isRecord()) { + var wrapper = wrappers.get(cl); + if (wrapper != null && wrapper.validator().isValid(from, target)) { + return wrapper.factory(); + } else if (cl.isEnum()) { + return EnumTypeWrapper.get(cl); + } else if (cl.isRecord()) { + // FIXME: record type wrapper + return null; + } else { + return null; } - - //else if (from != null && target.isArray() && !from.getClass().isArray() && target.getComponentType() == from.getClass() && !target.isPrimitive()) - //{ - // return TypeWrapperFactory.OBJECT_TO_ARRAY; - //} - - return null; } } diff --git a/src/test/java/dev/latvian/mods/rhino/test/Holder.java b/src/test/java/dev/latvian/mods/rhino/test/Holder.java index c1d09868..56d37d1b 100644 --- a/src/test/java/dev/latvian/mods/rhino/test/Holder.java +++ b/src/test/java/dev/latvian/mods/rhino/test/Holder.java @@ -1,19 +1,14 @@ package dev.latvian.mods.rhino.test; import dev.latvian.mods.rhino.Context; -import dev.latvian.mods.rhino.type.TypeUtils; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; +import dev.latvian.mods.rhino.type.TypeInfo; public record Holder(T value) { - public static Holder of(Context cx, Object from, Class target, Type genericTarget) { - if (genericTarget instanceof ParameterizedType pt) { - var types = pt.getActualTypeArguments(); + public static Holder of(Context cx, Object from, TypeInfo target) { + var type = target.param(0); - if (types.length == 1) { - return new Holder(cx.jsToJava(from, TypeUtils.getRawType(types[0]), types[0])); - } + if (type.convert()) { + return new Holder(cx.jsToJava(from, type)); } return new Holder(from); diff --git a/src/test/java/dev/latvian/mods/rhino/test/TestContext.java b/src/test/java/dev/latvian/mods/rhino/test/TestContext.java index 2cb0adea..b45f97fd 100644 --- a/src/test/java/dev/latvian/mods/rhino/test/TestContext.java +++ b/src/test/java/dev/latvian/mods/rhino/test/TestContext.java @@ -2,8 +2,6 @@ import dev.latvian.mods.rhino.Context; -import java.lang.reflect.Type; - public class TestContext extends Context { public String testName = ""; @@ -12,11 +10,7 @@ public TestContext(TestContextFactory factory) { } @Override - public int internalConversionWeight(Object fromObj, Class target, Type genericTarget) { - if (target == WithContext.class) { - return CONVERSION_NONTRIVIAL; - } - - return super.internalConversionWeight(fromObj, target, genericTarget); + public String toString() { + return testName; } } diff --git a/src/test/java/dev/latvian/mods/rhino/test/WithContext.java b/src/test/java/dev/latvian/mods/rhino/test/WithContext.java index 2f64c165..9d7a7b8e 100644 --- a/src/test/java/dev/latvian/mods/rhino/test/WithContext.java +++ b/src/test/java/dev/latvian/mods/rhino/test/WithContext.java @@ -1,20 +1,16 @@ package dev.latvian.mods.rhino.test; import dev.latvian.mods.rhino.Context; -import dev.latvian.mods.rhino.type.TypeUtils; +import dev.latvian.mods.rhino.type.TypeInfo; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Objects; public record WithContext(Context cx, T value) { - public static WithContext of(Context cx, Object from, Class target, Type genericTarget) { - if (genericTarget instanceof ParameterizedType parameterizedType) { - var types = parameterizedType.getActualTypeArguments(); + public static WithContext of(Context cx, Object from, TypeInfo target) { + var type = target.param(0); - if (types.length == 1) { - return new WithContext<>(cx, cx.jsToJava(from, TypeUtils.getRawType(types[0]), types[0])); - } + if (type.convert()) { + return new WithContext<>(cx, cx.jsToJava(from, type)); } return new WithContext<>(cx, from);