Skip to content

Commit

Permalink
Mostly implemented map generics
Browse files Browse the repository at this point in the history
  • Loading branch information
LatvianModder committed May 21, 2024
1 parent c1835ab commit 7be7ed3
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 137 deletions.
196 changes: 170 additions & 26 deletions src/main/java/dev/latvian/mods/rhino/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import dev.latvian.mods.rhino.util.JavaSetWrapper;
import dev.latvian.mods.rhino.util.TypeUtils;
import dev.latvian.mods.rhino.util.wrap.TypeWrapperFactory;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.Reader;
Expand All @@ -28,11 +29,15 @@
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;
import java.util.HashMap;
import java.util.LinkedHashMap;
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;

Expand Down Expand Up @@ -982,7 +987,7 @@ public Object wrap(Scriptable scope, Object obj, Class<?> staticType, Type gener
Class<?> cls = obj.getClass();

if (cls.isArray()) {
return NativeJavaArray.wrap(scope, obj, this);
return new NativeJavaArray(scope, obj, cls.getComponentType(), genericType instanceof GenericArrayType arr ? arr.getGenericComponentType() : cls.getComponentType(), this);
}

return wrapAsJavaObject(scope, obj, staticType, genericType);
Expand Down Expand Up @@ -1163,7 +1168,7 @@ public Scriptable wrapNewObject(Scriptable scope, Object obj) {
}
Class<?> cls = obj.getClass();
if (cls.isArray()) {
return NativeJavaArray.wrap(scope, obj, this);
return new NativeJavaArray(scope, obj, cls.getComponentType(), cls.getComponentType(), this);
}
return wrapAsJavaObject(scope, obj, null, null);
}
Expand Down Expand Up @@ -1203,6 +1208,134 @@ public int getMaximumInterpreterStackDepth() {
return Integer.MAX_VALUE;
}

protected ArrayValueProvider arrayValueProviderOf(Object value) {
if (value instanceof Object[] arr) {
return arr.length == 0 ? ArrayValueProvider.EMPTY : new ArrayValueProvider.FromPlainJavaArray(arr);
} else if (value != null && value.getClass().isArray()) {
int len = Array.getLength(value);
return len == 0 ? ArrayValueProvider.EMPTY : new ArrayValueProvider.FromJavaArray(value, len);
}

return switch (value) {
case NativeArray array -> ArrayValueProvider.fromNativeArray(array);
case NativeJavaList list -> ArrayValueProvider.fromJavaList(list.list, list);
case List<?> list -> ArrayValueProvider.fromJavaList(list, list);
case Iterable<?> itr -> ArrayValueProvider.fromIterable(itr);
case null, default -> value == null ? ArrayValueProvider.FromObject.FROM_NULL : new ArrayValueProvider.FromObject(value);
};
}

protected Object arrayOf(@Nullable Object from, @Nullable Class<?> target, @Nullable Type genericTarget) {
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);
} 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 arrayValueProviderOf(from).createArray(this, target, genericTarget);
}

protected Object listOf(@Nullable Object from, @Nullable Class<?> target, @Nullable Type genericTarget) {
if (from instanceof NativeJavaList n) {
if (target == null) {
// No conversion necessary
return n.list;
} else if (target == n.listType && Objects.equals(genericTarget, n.listGenericType)) {
// 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));
}

return list;
}
}

return arrayValueProviderOf(from).createList(this, target, genericTarget);
}

protected Object setOf(@Nullable Object from, @Nullable Class<?> target, @Nullable Type genericTarget) {
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)) {
// 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));
}

return set;
}
}

return arrayValueProviderOf(from).createSet(this, target, genericTarget);
}

protected Object mapOf(@Nullable Object from, @Nullable Class<?> kTarget, @Nullable Type kGenericTarget, @Nullable Class<?> vTarget, @Nullable Type vGenericTarget) {
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)) {
// No conversion necessary
return n.map;
} else {
if (n.map.isEmpty()) {
return Map.of();
}

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));
}

return map;
}
} else if (from instanceof NativeObject obj) {
var keys = obj.getIds(this);
var map = new LinkedHashMap<>(keys.length);

for (var key : keys) {
map.put(jsToJava(key, kTarget, kGenericTarget), jsToJava(obj.get(this, key), vTarget, vGenericTarget));
}

return map;
} else if (from instanceof Map<?, ?> m) {
if (kTarget == null && vTarget == null) {
// No conversion necessary
return m;
}

var map = new LinkedHashMap<>(m.size());

for (var entry : m.entrySet()) {
map.put(jsToJava(entry.getKey(), kTarget, kGenericTarget), jsToJava(entry.getValue(), vTarget, vGenericTarget));
}

return map;
} else {
return reportConversionError(from, Map.class);
}
}

public Object createInterfaceAdapter(Class<?> type, Type genericType, ScriptableObject so) {
// XXX: Currently only instances of ScriptableObject are
// supported since the resulting interface proxies should
Expand Down Expand Up @@ -1232,54 +1365,65 @@ public Object javaToJS(Object value, Scriptable scope) {
}
}

public final Object jsToJava(Object from, Class<?> target, Type genericTarget) throws EvaluatorException {
public final Object jsToJava(@Nullable Object from, @Nullable Class<?> target, @Nullable Type genericTarget) throws EvaluatorException {
if (target == null) {
return from;
} else if (target == Object.class) {
return Wrapper.unwrapped(from);
} else if (target.isArray()) {
var desiredComponentType = target.componentType();
var desiredComponentGenericType = TypeUtils.getComponentType(genericTarget, desiredComponentType);

if (from != null && from.getClass() == target) {
return new ArrayValueProvider.FromJavaArray(from).createArray(this, desiredComponentType, desiredComponentGenericType);
}

return ArrayValueProvider.of(from).createArray(this, desiredComponentType, desiredComponentGenericType);
} 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 != Object.class) {
return ArrayValueProvider.of(from).createSet(this, c, types[0]);
if (c != null && c != Object.class) {
return setOf(from, c, types[0]);
}
}

return ArrayValueProvider.of(from).createSet(this, null, null);
} else if (List.class.isAssignableFrom(target)) {
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 c = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class;
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 (c != Object.class) {
return ArrayValueProvider.of(from).createList(this, c, types[0]);
if (vRaw != null && vRaw != Object.class) {
vType = vRaw;
vGenericType = types[1];
}
}

return ArrayValueProvider.of(from).createList(this, null, null);
} else if (target == Map.class) {
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 k = types.length == 2 ? TypeUtils.getRawType(types[0]) : Object.class;
var v = types.length == 2 ? TypeUtils.getRawType(types[1]) : Object.class;
var c = types.length == 1 ? TypeUtils.getRawType(types[0]) : Object.class;

if (k != Object.class || v != Object.class) {
return ArrayValueProvider.of(from).createSet(this, k, types[0]);
if (c != null && c != Object.class) {
return listOf(from, c, types[0]);
}
}

return ArrayValueProvider.of(from).createSet(this, null, null);
return listOf(from, null, null);
}

return internalJsToJava(from, target, genericTarget);
Expand Down
14 changes: 3 additions & 11 deletions src/main/java/dev/latvian/mods/rhino/NativeJavaArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,17 @@
*/

public class NativeJavaArray extends NativeJavaObject implements SymbolScriptable {
public static NativeJavaArray wrap(Scriptable scope, Object array, Context cx) {
return new NativeJavaArray(scope, array, cx);
}

Object array;
int length;
Class<?> componentType;
Type genericComponentType;

public NativeJavaArray(Scriptable scope, Object array, Context cx) {
public NativeJavaArray(Scriptable scope, Object array, Class<?> componentType, Type genericComponentType, Context cx) {
super(scope, null, ScriptRuntime.ObjectClass, cx);
Class<?> cl = array.getClass();
if (!cl.isArray()) {
throw new RuntimeException("Array expected");
}
this.array = array;
this.length = Array.getLength(array);
this.componentType = cl.getComponentType();
this.genericComponentType = componentType; // fixme
this.componentType = componentType;
this.genericComponentType = genericComponentType;
}

@Override
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/dev/latvian/mods/rhino/NativeJavaMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@

@SuppressWarnings({"rawtypes", "unchecked"})
public class NativeJavaMap extends NativeJavaObject {
private final Map map;
private final Class<?> mapValueType;
private final Type mapValueGenericType;
public final Map map;
public final Class<?> mapKeyType;
public final Type mapKeyGenericType;
public final Class<?> mapValueType;
public final Type mapValueGenericType;
private final ValueUnwrapper valueUnwrapper;

public NativeJavaMap(Context cx, Scriptable scope, Object jo, Map map, Class<?> mapValueType, ValueUnwrapper valueUnwrapper) {
public NativeJavaMap(Context cx, Scriptable scope, Object jo, Map map, Class<?> mapKeyType, Type mapKeyGenericType, Class<?> mapValueType, Type mapValueGenericType, ValueUnwrapper valueUnwrapper) {
super(scope, jo, jo.getClass(), cx);
this.map = map;
this.mapKeyType = mapKeyType;
this.mapKeyGenericType = mapKeyGenericType;
this.mapValueType = mapValueType;
this.mapValueGenericType = mapValueType;
this.mapValueGenericType = mapValueGenericType;
this.valueUnwrapper = valueUnwrapper;
}

public NativeJavaMap(Context cx, Scriptable scope, Object jo, Map map) {
this(cx, scope, jo, map, Object.class, ValueUnwrapper.DEFAULT);
this(cx, scope, jo, map, null, null, null, null, ValueUnwrapper.DEFAULT);
}

@Override
Expand Down
34 changes: 18 additions & 16 deletions src/main/java/dev/latvian/mods/rhino/util/ArrayValueProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import dev.latvian.mods.rhino.Context;
import dev.latvian.mods.rhino.EvaluatorException;
import dev.latvian.mods.rhino.NativeArray;
import dev.latvian.mods.rhino.NativeJavaList;

import java.lang.reflect.Array;
import java.lang.reflect.Type;
Expand Down Expand Up @@ -32,16 +31,6 @@ public Object getErrorSource(Context cx) {
}
};

static ArrayValueProvider of(Object value) {
return switch (value) {
case NativeArray array -> fromNativeArray(array);
case NativeJavaList list -> fromJavaList(list.list, list);
case List<?> list -> fromJavaList(list, list);
case Iterable<?> itr -> fromIterable(itr);
case null, default -> value == null ? FromObject.FROM_NULL : new FromObject(value);
};
}

int getLength(Context cx);

Object getArrayValue(Context cx, int index);
Expand Down Expand Up @@ -155,19 +144,32 @@ public Object getErrorSource(Context cx) {
}
}

static ArrayValueProvider fromJavaArray(Object array) {
return Array.getLength(array) == 0 ? EMPTY : new FromJavaArray(array);
record FromJavaArray(Object array, int length) implements ArrayValueProvider {
@Override
public int getLength(Context cx) {
return length;
}

@Override
public Object getArrayValue(Context cx, int index) {
return Array.get(array, index);
}

@Override
public Object getErrorSource(Context cx) {
return array;
}
}

record FromJavaArray(Object array) implements ArrayValueProvider {
record FromPlainJavaArray(Object[] array) implements ArrayValueProvider {
@Override
public int getLength(Context cx) {
return Array.getLength(array);
return array.length;
}

@Override
public Object getArrayValue(Context cx, int index) {
return Array.get(array, index);
return array[index];
}

@Override
Expand Down
Loading

0 comments on commit 7be7ed3

Please sign in to comment.