Skip to content

Commit e3f133a

Browse files
ZZZankgbrail
authored andcommitted
Preserve more type information in the reflection-based Java interop
This PR added "TypeInfo" as another representation of Java type, in an effort to preserve more type information than what a Class<?> can provide, especially information on type variable and parameterized type. See the details in the PR: mozilla#1849
1 parent b3498c0 commit e3f133a

43 files changed

Lines changed: 2758 additions & 184 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

rhino/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
exports org.mozilla.javascript.typedarrays;
1414
exports org.mozilla.javascript.xml;
1515
exports org.mozilla.javascript.config;
16+
exports org.mozilla.javascript.lc.type;
1617

1718
uses org.mozilla.javascript.RegExpLoader;
1819
uses org.mozilla.javascript.xml.XMLLoader;

rhino/src/main/java/org/mozilla/javascript/AccessorSlot.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,12 @@ static final class MemberBoxSetter implements Setter {
231231
@Override
232232
public boolean setValue(Object value, Scriptable owner, Scriptable start) {
233233
Context cx = Context.getContext();
234-
Class<?>[] pTypes = member.argTypes;
234+
var pTypes = member.getArgTypes();
235235
// XXX: cache tag since it is already calculated in
236236
// defineProperty ?
237-
Class<?> valueType = pTypes[pTypes.length - 1];
238-
boolean isNullable = member.argNullability[pTypes.length - 1];
239-
int tag = FunctionObject.getTypeTag(valueType);
237+
var valueType = pTypes.get(pTypes.size() - 1);
238+
boolean isNullable = member.argNullability[pTypes.size() - 1];
239+
int tag = valueType.getTypeTag();
240240
Object actualArg = FunctionObject.convertArg(cx, start, value, tag, isNullable);
241241

242242
if (member.delegateTo == null) {

rhino/src/main/java/org/mozilla/javascript/Context.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.mozilla.javascript.ast.ScriptNode;
3535
import org.mozilla.javascript.debug.DebuggableScript;
3636
import org.mozilla.javascript.debug.Debugger;
37+
import org.mozilla.javascript.lc.type.TypeInfo;
38+
import org.mozilla.javascript.lc.type.TypeInfoFactory;
3739
import org.mozilla.javascript.xml.XMLLib;
3840

3941
/**
@@ -1838,6 +1840,10 @@ public static Object javaToJS(Object value, Scriptable scope, Context cx) {
18381840
* @throws EvaluatorException if the conversion cannot be performed
18391841
*/
18401842
public static Object jsToJava(Object value, Class<?> desiredType) throws EvaluatorException {
1843+
return jsToJava(value, TypeInfoFactory.GLOBAL.create(desiredType));
1844+
}
1845+
1846+
public static Object jsToJava(Object value, TypeInfo desiredType) throws EvaluatorException {
18411847
return NativeJavaObject.coerceTypeImpl(desiredType, value);
18421848
}
18431849

rhino/src/main/java/org/mozilla/javascript/FunctionObject.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.lang.reflect.Method;
1616
import java.lang.reflect.Modifier;
1717
import org.mozilla.javascript.commonjs.module.ModuleScope;
18+
import org.mozilla.javascript.lc.type.TypeInfo;
19+
import org.mozilla.javascript.lc.type.TypeInfoFactory;
1820

1921
public class FunctionObject extends BaseFunction {
2022
private static final long serialVersionUID = -5332312783643935019L;
@@ -78,34 +80,36 @@ public class FunctionObject extends BaseFunction {
7880
* @see org.mozilla.javascript.Scriptable
7981
*/
8082
public FunctionObject(String name, Member methodOrConstructor, Scriptable scope) {
83+
var typeInfoFactory = TypeInfoFactory.get(this);
84+
8185
if (methodOrConstructor instanceof Constructor) {
82-
member = new MemberBox((Constructor<?>) methodOrConstructor);
86+
member = new MemberBox((Constructor<?>) methodOrConstructor, typeInfoFactory);
8387
isStatic = true; // well, doesn't take a 'this'
8488
} else {
85-
member = new MemberBox((Method) methodOrConstructor);
89+
member = new MemberBox((Method) methodOrConstructor, typeInfoFactory);
8690
isStatic = member.isStatic();
8791
}
8892
String methodName = member.getName();
8993
this.functionName = name;
90-
Class<?>[] types = member.argTypes;
91-
int arity = types.length;
92-
if (arity == 4 && (types[1].isArray() || types[2].isArray())) {
94+
var types = member.getArgTypes();
95+
int arity = types.size();
96+
if (arity == 4 && (types.get(1).isArray() || types.get(2).isArray())) {
9397
// Either variable args or an error.
94-
if (types[1].isArray()) {
98+
if (types.get(1).isArray()) {
9599
if (!isStatic
96-
|| types[0] != ScriptRuntime.ContextClass
97-
|| types[1].getComponentType() != ScriptRuntime.ObjectClass
98-
|| types[2] != ScriptRuntime.FunctionClass
99-
|| types[3] != Boolean.TYPE) {
100+
|| types.get(0).isNot(Context.class)
101+
|| types.get(1).isNot(Object[].class)
102+
|| types.get(2).isNot(Function.class)
103+
|| types.get(3).isNot(boolean.class)) {
100104
throw Context.reportRuntimeErrorById("msg.varargs.ctor", methodName);
101105
}
102106
parmsLength = VARARGS_CTOR;
103107
} else {
104108
if (!isStatic
105-
|| types[0] != ScriptRuntime.ContextClass
106-
|| types[1] != ScriptRuntime.ScriptableClass
107-
|| types[2].getComponentType() != ScriptRuntime.ObjectClass
108-
|| types[3] != ScriptRuntime.FunctionClass) {
109+
|| types.get(0).isNot(Context.class)
110+
|| types.get(1).isNot(Scriptable.class)
111+
|| types.get(2).isNot(Object[].class)
112+
|| types.get(3).isNot(Function.class)) {
109113
throw Context.reportRuntimeErrorById("msg.varargs.fun", methodName);
110114
}
111115
parmsLength = VARARGS_METHOD;
@@ -115,10 +119,10 @@ public FunctionObject(String name, Member methodOrConstructor, Scriptable scope)
115119
if (arity > 0) {
116120
typeTags = new byte[arity];
117121
for (int i = 0; i != arity; ++i) {
118-
int tag = getTypeTag(types[i]);
122+
int tag = types.get(i).getTypeTag();
119123
if (tag == JAVA_UNSUPPORTED_TYPE) {
120124
throw Context.reportRuntimeErrorById(
121-
"msg.bad.parms", types[i].getName(), methodName);
125+
"msg.bad.parms", types.get(i).asClass().getName(), methodName);
122126
}
123127
typeTags[i] = (byte) tag;
124128
}
@@ -146,6 +150,7 @@ public FunctionObject(String name, Member methodOrConstructor, Scriptable scope)
146150
/**
147151
* @return One of <code>JAVA_*_TYPE</code> constants to indicate desired type or {@link
148152
* #JAVA_UNSUPPORTED_TYPE} if the conversion is not possible
153+
* @see TypeInfo#getTypeTag()
149154
*/
150155
public static int getTypeTag(Class<?> type) {
151156
if (type == ScriptRuntime.StringClass) return JAVA_STRING_TYPE;
@@ -488,10 +493,10 @@ boolean isVarArgsConstructor() {
488493
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
489494
in.defaultReadObject();
490495
if (parmsLength > 0) {
491-
Class<?>[] types = member.argTypes;
496+
var types = member.getArgTypes();
492497
typeTags = new byte[parmsLength];
493498
for (int i = 0; i != parmsLength; ++i) {
494-
typeTags[i] = (byte) getTypeTag(types[i]);
499+
typeTags[i] = (byte) types.get(i).getTypeTag();
495500
}
496501
}
497502
if (member.isMethod()) {

rhino/src/main/java/org/mozilla/javascript/JavaMembers.java

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.util.List;
2525
import java.util.Locale;
2626
import java.util.Map;
27+
import org.mozilla.javascript.lc.type.TypeInfo;
28+
import org.mozilla.javascript.lc.type.TypeInfoFactory;
2729

2830
/**
2931
* @author Mike Shaver
@@ -102,7 +104,7 @@ Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
102104
BeanProperty bp = (BeanProperty) member;
103105
if (bp.getter == null) return Scriptable.NOT_FOUND;
104106
rval = bp.getter.invoke(javaObject, ScriptRuntime.emptyArgs);
105-
type = bp.getter.method().getReturnType();
107+
type = bp.getter.getReturnType().asClass();
106108
} else {
107109
Field field = (Field) member;
108110
rval = field.get(isStatic ? null : javaObject);
@@ -139,7 +141,7 @@ void put(Scriptable scope, String name, Object javaObject, Object value, boolean
139141
// main setter. Otherwise, let the NativeJavaMethod decide which
140142
// setter to use:
141143
if (bp.setters == null || value == null) {
142-
Class<?> setType = bp.setter.argTypes[0];
144+
var setType = bp.setter.getArgTypes().get(0);
143145
Object[] args = {Context.jsToJava(value, setType)};
144146
try {
145147
bp.setter.invoke(javaObject, args);
@@ -209,21 +211,24 @@ static String javaSignature(Class<?> type) {
209211
return sb.toString();
210212
}
211213

212-
static String liveConnectSignature(Class<?>[] argTypes) {
213-
int N = argTypes.length;
214-
if (N == 0) {
214+
static String liveConnectSignature(List<TypeInfo> argTypes) {
215+
if (argTypes.isEmpty()) {
215216
return "()";
216217
}
217-
StringBuilder sb = new StringBuilder();
218-
sb.append('(');
219-
for (int i = 0; i != N; ++i) {
220-
if (i != 0) {
221-
sb.append(',');
218+
219+
var builder = new StringBuilder();
220+
221+
builder.append('(');
222+
var iter = argTypes.iterator();
223+
if (iter.hasNext()) {
224+
builder.append(javaSignature(iter.next().asClass()));
225+
while (iter.hasNext()) {
226+
builder.append(',').append(javaSignature(iter.next().asClass()));
222227
}
223-
sb.append(javaSignature(argTypes[i]));
224228
}
225-
sb.append(')');
226-
return sb.toString();
229+
builder.append(')');
230+
231+
return builder.toString();
227232
}
228233

229234
private MemberBox findExplicitFunction(String name, boolean isStatic) {
@@ -255,8 +260,7 @@ private MemberBox findExplicitFunction(String name, boolean isStatic) {
255260

256261
if (methodsOrCtors != null) {
257262
for (MemberBox methodsOrCtor : methodsOrCtors) {
258-
Class<?>[] type = methodsOrCtor.argTypes;
259-
String sig = liveConnectSignature(type);
263+
String sig = liveConnectSignature(methodsOrCtor.getArgTypes());
260264
if (sigStart + sig.length() == name.length()
261265
&& name.regionMatches(sigStart, sig, 0, sig.length())) {
262266
return methodsOrCtor;
@@ -433,6 +437,7 @@ private void reflect(
433437
// We reflect methods first, because we want overloaded field/method
434438
// names to be allocated to the NativeJavaMethod before the field
435439
// gets in the way.
440+
var typeFactory = TypeInfoFactory.get(scope);
436441

437442
Method[] methods = discoverAccessibleMethods(cl, includeProtected, includePrivate);
438443
for (Method method : methods) {
@@ -469,15 +474,15 @@ private void reflect(
469474
Object value = entry.getValue();
470475
if (value instanceof Method) {
471476
methodBoxes = new MemberBox[1];
472-
methodBoxes[0] = new MemberBox((Method) value);
477+
methodBoxes[0] = new MemberBox((Method) value, typeFactory);
473478
} else {
474479
ArrayList<Object> overloadedMethods = (ArrayList<Object>) value;
475480
int N = overloadedMethods.size();
476481
if (N < 2) Kit.codeBug();
477482
methodBoxes = new MemberBox[N];
478483
for (int i = 0; i != N; ++i) {
479484
Method method = (Method) overloadedMethods.get(i);
480-
methodBoxes[i] = new MemberBox(method);
485+
methodBoxes[i] = new MemberBox(method, typeFactory);
481486
}
482487
}
483488
NativeJavaMethod fun = new NativeJavaMethod(methodBoxes);
@@ -609,7 +614,7 @@ private void reflect(
609614
if (getter != null) {
610615
// We have a getter. Now, do we have a matching
611616
// setter?
612-
Class<?> type = getter.method().getReturnType();
617+
var type = getter.getReturnType();
613618
setter = extractSetMethod(type, njmSet.methods, isStatic);
614619
} else {
615620
// No getter, find any set method
@@ -633,7 +638,7 @@ private void reflect(
633638
Constructor<?>[] constructors = getAccessibleConstructors(includePrivate);
634639
MemberBox[] ctorMembers = new MemberBox[constructors.length];
635640
for (int i = 0; i != constructors.length; ++i) {
636-
ctorMembers[i] = new MemberBox(constructors[i]);
641+
ctorMembers[i] = new MemberBox(constructors[i], typeFactory);
637642
}
638643
ctors = new NativeJavaMethod(ctorMembers, cl.getSimpleName());
639644
}
@@ -708,9 +713,9 @@ private static MemberBox extractGetMethod(MemberBox[] methods, boolean isStatic)
708713
for (MemberBox method : methods) {
709714
// Does getter method have an empty parameter list with a return
710715
// value (eg. a getSomething() or isSomething())?
711-
if (method.argTypes.length == 0 && (!isStatic || method.isStatic())) {
712-
Class<?> type = method.method().getReturnType();
713-
if (type != Void.TYPE) {
716+
if (method.getArgTypes().isEmpty() && (!isStatic || method.isStatic())) {
717+
var type = method.getReturnType();
718+
if (!type.isVoid()) {
714719
return method;
715720
}
716721
break;
@@ -720,7 +725,7 @@ private static MemberBox extractGetMethod(MemberBox[] methods, boolean isStatic)
720725
}
721726

722727
private static MemberBox extractSetMethod(
723-
Class<?> type, MemberBox[] methods, boolean isStatic) {
728+
TypeInfo type, MemberBox[] methods, boolean isStatic) {
724729
//
725730
// Note: it may be preferable to allow NativeJavaMethod.findFunction()
726731
// to find the appropriate setter; unfortunately, it requires an
@@ -730,13 +735,14 @@ private static MemberBox extractSetMethod(
730735
MemberBox acceptableMatch = null;
731736
for (MemberBox method : methods) {
732737
if (!isStatic || method.isStatic()) {
733-
Class<?>[] params = method.argTypes;
734-
if (params.length == 1) {
735-
if (params[0] == type) {
738+
var argTypes = method.getArgTypes();
739+
if (argTypes.size() == 1) {
740+
if (type.is(argTypes.get(0).asClass())) {
736741
// perfect match, no need to continue scanning
737742
return method;
738743
}
739-
if (acceptableMatch == null && params[0].isAssignableFrom(type)) {
744+
if (acceptableMatch == null
745+
&& argTypes.get(0).asClass().isAssignableFrom(type.asClass())) {
740746
// do not return at this point, there can still be perfect match
741747
acceptableMatch = method;
742748
}
@@ -750,8 +756,8 @@ private static MemberBox extractSetMethod(MemberBox[] methods, boolean isStatic)
750756

751757
for (MemberBox method : methods) {
752758
if (!isStatic || method.isStatic()) {
753-
if (method.method().getReturnType() == Void.TYPE) {
754-
if (method.argTypes.length == 1) {
759+
if (method.getReturnType().isVoid()) {
760+
if (method.getArgTypes().size() == 1) {
755761
return method;
756762
}
757763
}

0 commit comments

Comments
 (0)