Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add generic type support and improve java.util.{List,Map} handling #827

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6b4b1e2
Add generic type support and improve java.util.{List,Map} handling
rPraml Jan 18, 2021
248fc41
spotlessApply
rPraml Sep 8, 2021
c21ff60
Merge branch 'tmp' into better-type-support
rPraml Sep 8, 2021
e0faf00
spotless some files
rPraml Sep 8, 2021
5db1134
Merge branch 'some-spotless' into better-type-support
rPraml Sep 8, 2021
2b4b127
Merge remote-tracking branch 'upstream/master' into better-type-support
rPraml Sep 20, 2021
532627c
Merge branch 'master' into better-type-support
rPraml Oct 1, 2021
c7c99d6
intermediate update - waiting for other PR
rPraml Oct 5, 2021
baa51c5
Merge remote-tracking branch 'upstream/master' into better-type-support
rPraml Oct 19, 2021
7308fb1
Fix: SecurityControllerTest
rPraml Oct 19, 2021
bc11347
Merge remote-tracking branch 'upstream/master' into better-type-support
rPraml Oct 22, 2021
73fe18d
Added some javadoc
rPraml Oct 22, 2021
6ca8852
Merge remote-tracking branch 'upstream/master' into better-type-support
rPraml Nov 3, 2021
8371646
Extracted key conversion to separate class
rPraml Nov 3, 2021
0765255
spotbugs
rPraml Nov 3, 2021
afa5663
Split into two methods
rPraml Nov 3, 2021
ec72919
FEATURE_ENABLE_JAVA_MAP_ACCESS supports only Map<String,?> and Map<In…
rPraml Nov 5, 2021
cd392fa
Changed to cached java type info
rPraml Nov 8, 2021
9fe2890
reviewed code & javadoc
rPraml Nov 8, 2021
bf76e9a
Fix problems with serialization
rPraml Nov 9, 2021
df90bbc
Merge remote-tracking branch 'upstream/master' into better-type-support
rPraml May 5, 2022
5e75df7
Merge branch 'master-upstream' into better-type-support
rPraml Dec 2, 2022
5b8dadc
Merge branch 'master-upstream' into better-type-support
rPraml Jul 6, 2023
104dc07
reverted changes
rPraml Jul 6, 2023
46240e2
Merge branch 'master-upstream' into better-type-support
rPraml Oct 16, 2023
ad74c0b
Merge branch 'master-upstream' into better-type-support
rPraml Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/src/main/java/PrimitiveWrapFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import java.lang.reflect.Type;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.WrapFactory;
Expand All @@ -24,7 +25,7 @@
public class PrimitiveWrapFactory extends WrapFactory {

@Override
public Object wrap(Context cx, Scriptable scope, Object obj, Class<?> staticType) {
public Object wrap(Context cx, Scriptable scope, Object obj, Type staticType) {
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
return obj;
} else if (obj instanceof Character) {
Expand Down
8 changes: 8 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ClassCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package org.mozilla.javascript;

import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -25,6 +26,7 @@ public class ClassCache implements Serializable {
private transient Map<CacheKey, JavaMembers> classTable;
private transient Map<JavaAdapter.JavaAdapterSignature, Class<?>> classAdapterCache;
private transient Map<Class<?>, Object> interfaceAdapterCache;
private transient Map<Type, JavaTypeInfo> typeCache;
private int generatedClassSerial;
private Scriptable associatedScope;

Expand Down Expand Up @@ -149,6 +151,12 @@ Map<JavaAdapter.JavaAdapterSignature, Class<?>> getInterfaceAdapterCacheMap() {
return classAdapterCache;
}

Map<Type, JavaTypeInfo> getTypeCacheMap() {
if (typeCache == null) {
typeCache = new ConcurrentHashMap<>(16, 0.75f, 1);
}
return typeCache;
}
/**
* @deprecated The method always returns false.
* @see #setInvokerOptimizationEnabled(boolean enabled)
Expand Down
4 changes: 2 additions & 2 deletions rhino/src/main/java/org/mozilla/javascript/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,7 @@ public static Scriptable toObject(Object value, Scriptable scope, Class<?> stati
* JavaScript type will be string.
*
* <p>The rest of values will be wrapped as LiveConnect objects by calling {@link
* WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Class staticType)} as in:
* WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Type staticType)} as in:
*
* <pre>
* Context cx = Context.getCurrentContext();
Expand Down Expand Up @@ -1733,7 +1733,7 @@ public static Object javaToJS(Object value, Scriptable scope) {
* JavaScript type will be string.
*
* <p>The rest of values will be wrapped as LiveConnect objects by calling {@link
* WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Class staticType)} as in:
* WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Type staticType)} as in:
*
* <pre>
* return cx.getWrapFactory().wrap(cx, scope, value, null);
Expand Down
32 changes: 26 additions & 6 deletions rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.security.AccessControlContext;
import java.security.AllPermission;
import java.security.Permission;
Expand Down Expand Up @@ -96,17 +97,17 @@ Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
}
Context cx = Context.getContext();
Object rval;
Class<?> type;
Type type;
try {
if (member instanceof BeanProperty) {
BeanProperty bp = (BeanProperty) member;
if (bp.getter == null) return Scriptable.NOT_FOUND;
rval = bp.getter.invoke(javaObject, Context.emptyArgs);
type = bp.getter.method().getReturnType();
type = bp.getter.method().getGenericReturnType();
} else {
Field field = (Field) member;
rval = field.get(isStatic ? null : javaObject);
type = field.getType();
type = field.getGenericType();
}
} catch (Exception ex) {
throw Context.throwAsScriptRuntimeEx(ex);
Expand All @@ -117,6 +118,16 @@ Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
}

void put(Scriptable scope, String name, Object javaObject, Object value, boolean isStatic) {
put(scope, name, javaObject, value, isStatic, null);
}

void put(
Scriptable scope,
String name,
Object javaObject,
Object value,
boolean isStatic,
JavaTypeResolver typeResolver) {
Map<String, Object> ht = isStatic ? staticMembers : members;
Object member = ht.get(name);
if (!isStatic && member == null) {
Expand All @@ -140,6 +151,9 @@ void put(Scriptable scope, String name, Object javaObject, Object value, boolean
// setter to use:
if (bp.setters == null || value == null) {
Class<?> setType = bp.setter.argTypes[0];
if (typeResolver != null && bp.setter.genericArgTypes != null) {
setType = typeResolver.resolve(bp.setter.genericArgTypes[0]);
}
Object[] args = {Context.jsToJava(value, setType)};
try {
bp.setter.invoke(javaObject, args);
Expand All @@ -161,7 +175,13 @@ void put(Scriptable scope, String name, Object javaObject, Object value, boolean
throw Context.reportRuntimeErrorById(str, name);
}
Field field = (Field) member;
Object javaValue = Context.jsToJava(value, field.getType());
Class<?> fieldType;
if (typeResolver != null) {
fieldType = typeResolver.resolve(field.getGenericType());
} else {
fieldType = field.getType();
}
Object javaValue = Context.jsToJava(value, fieldType);
try {
field.set(javaObject, javaValue);
} catch (IllegalAccessException accessEx) {
Expand Down Expand Up @@ -896,10 +916,10 @@ class FieldAndMethods extends NativeJavaMethod {
public Object getDefaultValue(Class<?> hint) {
if (hint == ScriptRuntime.FunctionClass) return this;
Object rval;
Class<?> type;
Type type;
try {
rval = field.get(javaObject);
type = field.getType();
type = field.getGenericType();
} catch (IllegalAccessException accEx) {
throw Context.reportRuntimeErrorById("msg.java.internal.private", field.getName());
}
Expand Down
178 changes: 178 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/JavaTypeInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript;

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.HashMap;
import java.util.Map;

/**
* Carries the reflection info per JavaObject.
*
* <p>For example, if we take <code>ArrayList</code>, we have the following inheritance
*
* <ul>
* <li><code>ArrayList&lt;E&gt;</code> extends <code>AbstractList&lt;E&gt;</code> implements
* <code>List&lt;E&gt;</code>
* <li><code>AbstractList&lt;E&gt;</code> extends <code>AbstractCollection&lt;E&gt;</code>
* implements <code>List&lt;E&gt;</code>
* <li><code>AbstractCollection&lt;E&gt;</code> implements <code>Collection&lt;E&gt;</code>
* <li><code>Collection&lt;E&gt;</code> extends <code>Iterable&lt;E&gt;</code>
* <li><code>Iterable&lt;T&gt;</code>
* </ul>
*
* we have to walk trough the inheritance tree and build a graph, how the genericTypes (here <code>E
* </code> and <code>T</code>) are linked together.
*
* <p>For the generic type declaration <code>ArrayList&gt;String&lt;</code> we can query which
* resolved type arguments each superClass or superInterface has. In this example <code>
* resolve(Iterator.class, 0)</code> will return <code>String.class</code>.
*
* <p>When determining method parameters (for example the generic parameter of <code>List.add(M)
* </code> is <code>M</code>) {@link #resolve(Type)} and {@link #reverseResolve(Type)} are used to
* determine the correct type. (in this case String.class)
*
* <p>Some ideas are taken from
* https://www.javacodegeeks.com/2013/12/advanced-java-generics-retreiving-generic-type-arguments.html
*
* @author Roland Praml, FOCONIS AG
*/
public class JavaTypeInfo {

private final Map<Class<?>, Type[]> typeCache;
private final Map<Type, Type> resolved;
private final Map<Type, Type> reverseResolved;

JavaTypeInfo(Type type) {
typeCache = new HashMap<>();
resolved = new HashMap<>();
reverseResolved = new HashMap<>();
reflect(type, null);
}

/**
* Returns the resolved type argument for <code>classOfInterest</code>.
*
* @param classOfInterest a superClass or interface of the representing <code>type</code>.
* @param index the generic index of <code>classOfIntererest</code>
*/
public Class<?> resolve(Class<?> classOfInterest, int index) {
Type[] entry = typeCache.get(classOfInterest);
if (entry == null) {
return null;
} else {
return getRawType(entry[index]);
}
}

public Type reverseResolve(Type type) {
return reverseResolved.getOrDefault(type, type);
}

public Class<?> resolve(Type type) {
Type ret = resolved.get(type);
if (ret instanceof Class) {
return (Class<?>) ret;
} else {
return null;
}
}

private void reflect(Type type, Type[] typeArgs) {
if (type == null) {
return;
} else if (type instanceof Class) {
Class<?> cls = (Class<?>) type;
TypeVariable<?>[] params = cls.getTypeParameters();
if (params.length != 0) {
Type[] resolvedParams = new Type[params.length];
for (int i = 0; i < params.length; i++) {
if (typeArgs == null) {
resolvedParams[i] = params[i];
} else {
// build a map how generic type parameters are linked over various
// subclasses.
resolvedParams[i] = resolved.getOrDefault(typeArgs[i], typeArgs[i]);
resolved.put(params[i], resolvedParams[i]);
if (resolvedParams[i] instanceof TypeVariable) {
reverseResolved.put(resolvedParams[i], params[i]);
}
}
}
typeCache.put(cls, resolvedParams);
}
for (Type iface : cls.getGenericInterfaces()) {
reflect(iface, null);
}
reflect(cls.getGenericSuperclass(), null);
} else if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
reflect(pt.getRawType(), pt.getActualTypeArguments());
}
}

public static JavaTypeInfo get(Scriptable scope, Type type) {
if (type == null) {
return null;
}
ClassCache cache = ClassCache.get(scope);
return cache.getTypeCacheMap().computeIfAbsent(type, JavaTypeInfo::new);
}

/** returns the raw type. Taken from google guice. */
public static Class<?> getRawType(Type type) {
if (type == null) {
return null;

} else if (type instanceof Class<?>) {
// Type is a normal class.
return (Class<?>) type;

} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
return (Class<?>) parameterizedType.getRawType();

} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
return Array.newInstance(getRawType(componentType), 0).getClass();

} else if (type instanceof WildcardType) {
Type[] bound = ((WildcardType) type).getLowerBounds();
if (bound.length == 1) {
return getRawType(bound[0]);
} else {
bound = ((WildcardType) type).getUpperBounds();
if (bound.length == 1) {
return getRawType(bound[0]);
} else {
return Object.class;
}
}
} else if (type instanceof TypeVariable) {
Type[] bound = ((TypeVariable<?>) type).getBounds();
if (bound.length == 1) {
return getRawType(bound[0]);
} else {
return Object.class;
}

} else {
String className = type.getClass().getName();
throw new IllegalArgumentException(
"Expected a Class, "
+ "ParameterizedType, or GenericArrayType, but <"
+ type
+ "> is of type "
+ className);
}
}
}
Loading
Loading