Skip to content

Commit b3c7a63

Browse files
authored
cleanup JavaAdapter
* migrate JavaAdapter away from IdFunctionCall * expand JavaAdapterTest * extract the process of scanning args for inheritance data to another method * reuse JavaAdapterSignature object * clarify logic by moving data collection out of `getAdapterClass` * simplify JavaAdapterSignature * add test for JavaAdapterSignature
1 parent 2f70c62 commit b3c7a63

2 files changed

Lines changed: 167 additions & 103 deletions

File tree

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

Lines changed: 90 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package org.mozilla.javascript;
88

9+
import static org.mozilla.javascript.ScriptableObject.DONTENUM;
10+
911
import java.io.IOException;
1012
import java.io.ObjectInputStream;
1113
import java.io.ObjectOutputStream;
@@ -27,68 +29,44 @@
2729
import org.mozilla.classfile.ClassFileWriter;
2830
import org.mozilla.javascript.lc.type.TypeInfo;
2931

30-
public final class JavaAdapter implements IdFunctionCall {
32+
public final class JavaAdapter {
3133
/**
3234
* Provides a key with which to distinguish previously generated adapter classes stored in a
3335
* hash table.
36+
*
37+
* <p>JavaAdapter should cache the adapter class when input classes are the same, and
38+
* implementation object are of "same shape", which means having same number of function
39+
* properties, and same length for each function
3440
*/
3541
static class JavaAdapterSignature {
3642
Class<?> superClass;
3743
Class<?>[] interfaces;
44+
// name -> function arg length
3845
Map<String, Integer> names;
3946

40-
JavaAdapterSignature(
41-
Class<?> superClass, Class<?>[] interfaces, Map<String, Integer> names) {
42-
this.superClass = superClass;
43-
this.interfaces = interfaces;
44-
this.names = names;
45-
}
46-
4747
@Override
4848
public boolean equals(Object obj) {
4949
if (!(obj instanceof JavaAdapterSignature)) return false;
5050
JavaAdapterSignature sig = (JavaAdapterSignature) obj;
51-
if (superClass != sig.superClass) return false;
52-
if (interfaces != sig.interfaces) {
53-
if (interfaces.length != sig.interfaces.length) return false;
54-
for (int i = 0; i < interfaces.length; i++)
55-
if (interfaces[i] != sig.interfaces[i]) return false;
56-
}
57-
if (names.size() != sig.names.size()) return false;
58-
for (Map.Entry<String, Integer> e : names.entrySet()) {
59-
String name = e.getKey();
60-
int arity = e.getValue();
61-
if (arity != sig.names.getOrDefault(name, arity + 1)) return false;
62-
}
63-
return true;
51+
return superClass == sig.superClass
52+
&& Arrays.equals(interfaces, sig.interfaces)
53+
&& names.equals(sig.names);
6454
}
6555

6656
@Override
6757
public int hashCode() {
68-
return (superClass.hashCode() + Arrays.hashCode(interfaces)) ^ names.size();
58+
return (superClass.hashCode() * 31 + Arrays.hashCode(interfaces)) ^ names.size();
6959
}
7060
}
7161

7262
public static void init(Context cx, Scriptable scope, boolean sealed) {
73-
JavaAdapter obj = new JavaAdapter();
74-
IdFunctionObject ctor =
75-
new IdFunctionObject(obj, FTAG, Id_JavaAdapter, "JavaAdapter", 1, scope);
76-
ctor.markAsConstructor(null);
63+
var ctor = new LambdaConstructor(scope, "JavaAdapter", 1, JavaAdapter::js_createAdapter);
64+
7765
if (sealed) {
7866
ctor.sealObject();
7967
}
80-
ctor.exportAsScopeProperty();
81-
}
8268

83-
@Override
84-
public Object execIdCall(
85-
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
86-
if (f.hasTag(FTAG)) {
87-
if (f.methodId() == Id_JavaAdapter) {
88-
return js_createAdapter(cx, scope, args);
89-
}
90-
}
91-
throw f.unknown();
69+
ScriptableObject.defineProperty(scope, "JavaAdapter", ctor, DONTENUM);
9270
}
9371

9472
public static Object convertResult(Object result, Class<?> c) {
@@ -113,7 +91,7 @@ public static Object getAdapterSelf(Class<?> adapterClass, Object adapter)
11391
return self.get(adapter);
11492
}
11593

116-
static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) {
94+
static Scriptable js_createAdapter(Context cx, Scriptable scope, Object[] args) {
11795
int N = args.length;
11896
if (N == 0) {
11997
throw ScriptRuntime.typeErrorById("msg.adapter.zero.args");
@@ -123,51 +101,21 @@ static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) {
123101
// Any number of NativeJavaClass objects representing the super-class
124102
// and/or interfaces to implement, followed by one NativeObject providing
125103
// the implementation, followed by any number of arguments to pass on
126-
// to the (super-class) constructor.
127-
128-
int classCount;
129-
for (classCount = 0; classCount < N - 1; classCount++) {
130-
Object arg = args[classCount];
131-
// We explicitly test for NativeObject here since checking for
132-
// instanceof ScriptableObject or !(instanceof NativeJavaClass)
133-
// would fail for a Java class that isn't found in the class path
134-
// as NativeJavaPackage extends ScriptableObject.
135-
if (arg instanceof NativeObject) {
136-
break;
137-
}
138-
if (!(arg instanceof NativeJavaClass)) {
139-
throw ScriptRuntime.typeErrorById(
140-
"msg.not.java.class.arg",
141-
String.valueOf(classCount),
142-
ScriptRuntime.toString(arg));
143-
}
144-
}
145-
Class<?> superClass = null;
146-
Class<?>[] intfs = new Class[classCount];
147-
int interfaceCount = 0;
148-
for (int i = 0; i < classCount; ++i) {
149-
Class<?> c = ((NativeJavaClass) args[i]).getClassObject();
150-
if (!c.isInterface()) {
151-
if (superClass != null) {
152-
throw ScriptRuntime.typeErrorById(
153-
"msg.only.one.super", superClass.getName(), c.getName());
154-
}
155-
superClass = c;
156-
} else {
157-
intfs[interfaceCount++] = c;
158-
}
159-
}
104+
// to the (super-class) constructor:
105+
// new JavaAdapter(
106+
// AbstractClazz, Interface1, Interface2,
107+
// { someMethod: function(args) {}, someOtherMethod: function(args) {} },
108+
// ["args for AbstractClazz ctor"]
109+
// )
160110

161-
if (superClass == null) {
162-
superClass = ScriptRuntime.ObjectClass;
163-
}
111+
var sig = new JavaAdapterSignature();
112+
var classCount = fillAdapterInheritanceData(args, sig);
164113

165-
Class<?>[] interfaces = new Class[interfaceCount];
166-
System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
167114
// next argument is implementation, must be scriptable
168115
Scriptable obj = ScriptableObject.ensureScriptable(args[classCount]);
116+
sig.names = getObjectFunctionNames(obj);
169117

170-
Class<?> adapterClass = getAdapterClass(scope, superClass, interfaces, obj);
118+
Class<?> adapterClass = getAdapterClass(scope, sig);
171119
Object adapter;
172120

173121
int argsCount = N - classCount - 1;
@@ -185,9 +133,10 @@ static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) {
185133
NativeJavaMethod ctors = classWrapper.members.ctors;
186134
int index = ctors.findCachedFunction(cx, ctorArgs);
187135
if (index < 0) {
188-
String sig = NativeJavaMethod.scriptSignature(args);
189136
throw Context.reportRuntimeErrorById(
190-
"msg.no.java.ctor", adapterClass.getName(), sig);
137+
"msg.no.java.ctor",
138+
adapterClass.getName(),
139+
NativeJavaMethod.scriptSignature(args));
191140
}
192141

193142
// Found the constructor, so try invoking it.
@@ -200,15 +149,15 @@ static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) {
200149
adapter = adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
201150
}
202151

203-
Object self = getAdapterSelf(adapterClass, adapter);
152+
var self = (Scriptable) getAdapterSelf(adapterClass, adapter);
204153
// Return unwrapped JavaAdapter if it implements Scriptable
205154
if (self instanceof Wrapper) {
206155
Object unwrapped = ((Wrapper) self).unwrap();
207156
if (unwrapped instanceof Scriptable) {
208157
if (unwrapped instanceof ScriptableObject) {
209158
ScriptRuntime.setObjectProtoAndParent((ScriptableObject) unwrapped, scope);
210159
}
211-
return unwrapped;
160+
return (Scriptable) unwrapped;
212161
}
213162
}
214163
return self;
@@ -217,6 +166,56 @@ static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) {
217166
}
218167
}
219168

169+
/**
170+
* @param args JavaAdapter args to scan
171+
* @param signature holder of collected information, {@link JavaAdapterSignature#superClass} and
172+
* {@link JavaAdapterSignature#interfaces} will be overwritten
173+
* @return the index of JS implementation object
174+
*/
175+
private static int fillAdapterInheritanceData(Object[] args, JavaAdapterSignature signature) {
176+
var len = args.length;
177+
178+
int classCount;
179+
for (classCount = 0; classCount < len - 1; classCount++) {
180+
Object arg = args[classCount];
181+
// We explicitly test for NativeObject here since checking for
182+
// instanceof ScriptableObject or !(instanceof NativeJavaClass)
183+
// would fail for a Java class that isn't found in the class path
184+
// as NativeJavaPackage extends ScriptableObject.
185+
if (arg instanceof NativeObject) {
186+
break;
187+
}
188+
if (!(arg instanceof NativeJavaClass)) {
189+
throw ScriptRuntime.typeErrorById(
190+
"msg.not.java.class.arg",
191+
String.valueOf(classCount),
192+
ScriptRuntime.toString(arg));
193+
}
194+
}
195+
Class<?> superClass = null;
196+
Class<?>[] interfaces = new Class[classCount];
197+
int interfaceCount = 0;
198+
for (int i = 0; i < classCount; ++i) {
199+
Class<?> c = ((NativeJavaClass) args[i]).getClassObject();
200+
if (c.isInterface()) {
201+
interfaces[interfaceCount++] = c;
202+
} else {
203+
if (superClass != null) {
204+
throw ScriptRuntime.typeErrorById(
205+
"msg.only.one.super", superClass.getName(), c.getName());
206+
}
207+
superClass = c;
208+
}
209+
}
210+
211+
signature.superClass = superClass == null ? Object.class : superClass;
212+
signature.interfaces =
213+
interfaceCount == interfaces.length
214+
? interfaces
215+
: Arrays.copyOf(interfaces, interfaceCount);
216+
return classCount;
217+
}
218+
220219
// Needed by NativeJavaObject serializer
221220
public static void writeAdapterObject(Object javaObject, ObjectOutputStream out)
222221
throws IOException {
@@ -250,17 +249,19 @@ public static Object readAdapterObject(Scriptable self, ObjectInputStream in)
250249
factory = null;
251250
}
252251

253-
Class<?> superClass = Class.forName((String) in.readObject());
252+
var sig = new JavaAdapterSignature();
253+
sig.superClass = Class.forName((String) in.readObject());
254254

255255
String[] interfaceNames = (String[]) in.readObject();
256-
Class<?>[] interfaces = new Class[interfaceNames.length];
256+
sig.interfaces = new Class[interfaceNames.length];
257257

258258
for (int i = 0; i < interfaceNames.length; i++)
259-
interfaces[i] = Class.forName(interfaceNames[i]);
259+
sig.interfaces[i] = Class.forName(interfaceNames[i]);
260260

261261
Scriptable delegee = (Scriptable) in.readObject();
262+
sig.names = getObjectFunctionNames(delegee);
262263

263-
Class<?> adapterClass = getAdapterClass(self, superClass, interfaces, delegee);
264+
Class<?> adapterClass = getAdapterClass(self, sig);
264265

265266
Class<?>[] ctorParms = {
266267
ScriptRuntime.ContextFactoryClass,
@@ -298,18 +299,15 @@ private static Map<String, Integer> getObjectFunctionNames(Scriptable obj) {
298299
return map;
299300
}
300301

301-
private static Class<?> getAdapterClass(
302-
Scriptable scope, Class<?> superClass, Class<?>[] interfaces, Scriptable obj) {
302+
private static Class<?> getAdapterClass(Scriptable scope, JavaAdapterSignature sig) {
303303
ClassCache cache = ClassCache.get(scope);
304304
Map<JavaAdapterSignature, Class<?>> generated = cache.getInterfaceAdapterCacheMap();
305305

306-
Map<String, Integer> names = getObjectFunctionNames(obj);
307-
JavaAdapterSignature sig;
308-
sig = new JavaAdapterSignature(superClass, interfaces, names);
309306
Class<?> adapterClass = generated.get(sig);
310307
if (adapterClass == null) {
311308
String adapterName = "adapter" + cache.newClassSerialNumber();
312-
byte[] code = createAdapterCode(names, adapterName, superClass, interfaces, null);
309+
byte[] code =
310+
createAdapterCode(sig.names, adapterName, sig.superClass, sig.interfaces, null);
313311

314312
adapterClass = loadAdapterClass(adapterName, code);
315313
if (cache.isCachingEnabled()) {
@@ -1130,7 +1128,4 @@ static int[] getArgsToConvert(Class<?>[] argTypes) {
11301128
}
11311129
return array;
11321130
}
1133-
1134-
private static final Object FTAG = "JavaAdapter";
1135-
private static final int Id_JavaAdapter = 1;
11361131
}

0 commit comments

Comments
 (0)