diff --git a/js/rhino/build.xml b/js/rhino/build.xml
index f0adf7e3e6e3..c94a278719fe 100644
--- a/js/rhino/build.xml
+++ b/js/rhino/build.xml
@@ -89,7 +89,6 @@ Requires Ant version 1.2 or later
-
diff --git a/js/rhino/docs/rhino16R2.html b/js/rhino/docs/rhino16R2.html
index a42cac11f182..de8b03cc3f2b 100644
--- a/js/rhino/docs/rhino16R2.html
+++ b/js/rhino/docs/rhino16R2.html
@@ -319,6 +319,14 @@ rethrown erro...
+
+
+ 306308 |
+
+ JS function as Java interface via reflect.Proxy |
+
+
+
diff --git a/js/rhino/src/org/mozilla/javascript/InterfaceAdapter.java b/js/rhino/src/org/mozilla/javascript/InterfaceAdapter.java
index 1f27e65aebf2..172a7c5e0bee 100644
--- a/js/rhino/src/org/mozilla/javascript/InterfaceAdapter.java
+++ b/js/rhino/src/org/mozilla/javascript/InterfaceAdapter.java
@@ -35,31 +35,33 @@
package org.mozilla.javascript;
-import org.mozilla.classfile.*;
-import java.lang.reflect.*;
-import java.util.*;
+import java.lang.reflect.Method;
/**
- * Base class for classes that runtime will generate to allow for
- * JS function to implement Java interfaces with single method
- * or multiple methods with the same signature.
+ * Adapter to use JS function as implementation of Java interfaces with
+ * single method or multiple methods with the same signature.
*/
-public class InterfaceAdapter implements Cloneable, Callable
+public class InterfaceAdapter
{
- private Function function;
- private Class nonPrimitiveResultClass;
- private int[] argsToConvert;
+ private final Object proxyHelper;
+ private boolean[] argsToConvert;
/**
- * Make glue object implementing single-method interface cl that will
+ * Make glue object implementing interface cl that will
* call the supplied JS function when called.
+ * Only interfaces were all methods has the same signature is supported.
+ *
+ * @return The glue object or null if cl is not interface or
+ * has methods with different signatures.
*/
- public static InterfaceAdapter create(Class cl, Function f)
+ static Object create(Context cx, Class cl, Callable f)
{
- ClassCache cache = ClassCache.get(f);
- InterfaceAdapter master;
- master = (InterfaceAdapter)cache.getInterfaceAdapter(cl);
- if (master == null) {
+ Scriptable topScope = ScriptRuntime.getTopCallScope(cx);
+ ClassCache cache = ClassCache.get(topScope);
+ InterfaceAdapter adapter;
+ adapter = (InterfaceAdapter)cache.getInterfaceAdapter(cl);
+ ContextFactory cf = cx.getFactory();
+ if (adapter == null) {
if (!cl.isInterface())
return null;
Method[] methods = cl.getMethods();
@@ -76,128 +78,73 @@ public class InterfaceAdapter implements Cloneable, Callable
}
}
- String className = "iadapter"+cache.newClassSerialNumber();
- byte[] code = createCode(cl, methods, returnType, argTypes,
- className);
-
- Class iadapterClass = JavaAdapter.loadAdapterClass(className, code);
- try {
- master = (InterfaceAdapter)iadapterClass.newInstance();
- } catch (Exception ex) {
- throw Context.throwAsScriptRuntimeEx(ex);
- }
- master.initMaster(returnType, argTypes);
- cache.cacheInterfaceAdapter(cl, master);
+ adapter = new InterfaceAdapter(cf, cl, argTypes);
+ cache.cacheInterfaceAdapter(cl, adapter);
}
- return master.wrap(f);
+ return VMBridge.instance.newInterfaceProxy(
+ adapter.proxyHelper, cf, adapter, f, topScope);
}
- private static byte[] createCode(Class interfaceClass,
- Method[] methods,
- Class returnType,
- Class[] argTypes,
- String className)
+ private InterfaceAdapter(ContextFactory cf, Class cl, Class[] argTypes)
{
- String superName = "org.mozilla.javascript.InterfaceAdapter";
- ClassFileWriter cfw = new ClassFileWriter(className,
- superName,
- "");
- cfw.addInterface(interfaceClass.getName());
-
- // Generate empty constructor
- cfw.startMethod("", "()V", ClassFileWriter.ACC_PUBLIC);
- cfw.add(ByteCode.ALOAD_0); // this
- cfw.addInvoke(ByteCode.INVOKESPECIAL,
- superName, "", "()V");
- cfw.add(ByteCode.RETURN);
- cfw.stopMethod((short)1); // 1: single this argument
-
- for (int i = 0; i != methods.length; ++i) {
- Method method = methods[i];
- StringBuffer sb = new StringBuffer();
- int localsEnd = JavaAdapter.appendMethodSignature(argTypes,
- returnType, sb);
- String methodSignature = sb.toString();
- cfw.startMethod(method.getName(), methodSignature,
- ClassFileWriter.ACC_PUBLIC);
- cfw.addLoadThis();
- JavaAdapter.generatePushWrappedArgs(cfw, argTypes,
- argTypes.length + 1);
- // add method name as the last JS parameter
- cfw.add(ByteCode.DUP); // duplicate array reference
- cfw.addPush(argTypes.length);
- cfw.addPush(method.getName());
- cfw.add(ByteCode.AASTORE);
-
- cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "doCall",
- "([Ljava/lang/Object;)Ljava/lang/Object;");
- JavaAdapter.generateReturnResult(cfw, returnType, false);
-
- cfw.stopMethod((short)localsEnd);
- }
-
- return cfw.toByteArray();
- }
-
- private void initMaster(Class returnType, Class[] argTypes)
- {
- // Can only be called on master
- if (this.function != null) Kit.codeBug();
- if (!returnType.isPrimitive()) {
- nonPrimitiveResultClass = returnType;
- }
- this.argsToConvert = JavaAdapter.getArgsToConvert(argTypes);
- }
-
- private InterfaceAdapter wrap(Function function)
- {
- // Arguments can not be null
- if (function == null)
- Kit.codeBug();
- // Can only be called on master
- if (this.function != null) Kit.codeBug();
- InterfaceAdapter copy;
- try {
- copy = (InterfaceAdapter)clone();
- } catch (CloneNotSupportedException ex) {
- // Should not happen
- copy = null;
- }
- copy.function = function;
- return copy;
- }
-
- protected final Object doCall(Object[] args)
- {
- Scriptable scope = function.getParentScope();
- Scriptable thisObj = scope;
- Object result = Context.call(null, this, scope, thisObj, args);
- if (nonPrimitiveResultClass != null) {
- if (result == Undefined.instance) {
- // Avoid an error for an undefined value; return null instead.
- result = null;
- } else {
- result = Context.jsToJava(result, nonPrimitiveResultClass);
+ this.proxyHelper
+ = VMBridge.instance.getInterfaceProxyHelper(
+ cf, new Class[] { cl });
+ for (int i = 0; i != argTypes.length; ++i) {
+ if (!ScriptRuntime.isRhinoRuntimeType(argTypes[i])) {
+ if (argsToConvert == null) {
+ argsToConvert = new boolean[argTypes.length];
+ }
+ argsToConvert[i] = true;
}
}
- return result;
}
- public Object call(Context cx, Scriptable scope, Scriptable thisObj,
- Object[] args)
+ public Object invoke(ContextFactory cf,
+ final Object target,
+ final Scriptable topScope,
+ final Method method,
+ final Object[] args)
{
+ ContextAction action = new ContextAction() {
+ public Object run(Context cx)
+ {
+ return invokeImpl(cx, target, topScope, method, args);
+ }
+ };
+ return cf.call(action);
+ }
+
+ Object invokeImpl(Context cx,
+ Object target,
+ Scriptable topScope,
+ Method method,
+ Object[] args)
+ {
+ Callable callable = (Callable)target;
+ int N = (args == null) ? 0 : args.length;
+ Object[] jsargs = new Object[N + 1];
+ if (N != 0) {
+ System.arraycopy(args, 0, jsargs, 0, N);
+ }
+ jsargs[N] = method.getName();
if (argsToConvert != null) {
WrapFactory wf = cx.getWrapFactory();
- for (int i = 0, N = argsToConvert.length; i != N; ++i) {
- int index = argsToConvert[i];
- Object arg = args[index];
- if (arg != null && !(arg instanceof Scriptable)) {
- args[index] = wf.wrap(cx, scope, arg, null);
+ for (int i = 0; i != N; ++i) {
+ if (argsToConvert[i]) {
+ jsargs[i] = wf.wrap(cx, topScope, jsargs[i], null);
}
}
}
- return function.call(cx, scope, thisObj, args);
+
+ Scriptable thisObj = topScope;
+ Object result = callable.call(cx, topScope, thisObj, jsargs);
+ Class javaResultType = method.getReturnType();
+ if (javaResultType == Void.TYPE) {
+ result = null;
+ } else {
+ result = Context.jsToJava(result, javaResultType);
+ }
+ return result;
}
-
-
}
diff --git a/js/rhino/src/org/mozilla/javascript/NativeJavaObject.java b/js/rhino/src/org/mozilla/javascript/NativeJavaObject.java
index ca8cfefbcf47..81c7bf98c5a3 100644
--- a/js/rhino/src/org/mozilla/javascript/NativeJavaObject.java
+++ b/js/rhino/src/org/mozilla/javascript/NativeJavaObject.java
@@ -661,45 +661,35 @@ WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Class)}
return value;
reportConversionError(value, type);
}
- else if (type.isInterface()) {
- if (value instanceof Function
- && interfaceAdapter_create != null)
- {
- // Try to wrap function into interface with single method.
- Function f = (Function)value;
+ else if (type.isInterface() && value instanceof Callable) {
+ // Try to wrap function into interface with single method.
+ Callable callable = (Function)value;
- // Can not wrap generic Function since the resulting object
- // should be reused next time conversion is made
- // and generic Function has no storage for it.
- // WeakMap from JDK 1.2 can address it, but for now
- // restrict the conversion only to classes extending from
- // ScriptableObject to use associateValue for storage
- if (f instanceof ScriptableObject) {
- ScriptableObject so = (ScriptableObject)f;
- Object key = Kit.makeHashKeyFromPair(
- COERCED_INTERFACE_KEY, type);
- Object old = so.getAssociatedValue(key);
- if (old != null) {
- // Function was already wrapped
- return old;
- }
- Object glue;
- Object[] args = { type, f };
- try {
- glue = interfaceAdapter_create.invoke(null, args);
- } catch (Exception ex) {
- throw Context.throwAsScriptRuntimeEx(ex);
- }
- if (glue != null) {
- // Store for later retrival
- glue = so.associateValue(key, glue);
- return glue;
- }
+ // Can not wrap generic Callable since the resulting object
+ // should be reused next time conversion is made
+ // and generic Function has no storage for it.
+ // Weak referencesfrom JDK 1.2 can address it, but for now
+ // restrict the conversion only to classes extending from
+ // ScriptableObject to use associateValue for storage
+ if (callable instanceof ScriptableObject) {
+ ScriptableObject so = (ScriptableObject)callable;
+ Object key = Kit.makeHashKeyFromPair(
+ COERCED_INTERFACE_KEY, type);
+ Object old = so.getAssociatedValue(key);
+ if (old != null) {
+ // Function was already wrapped
+ return old;
+ }
+ Context cx = Context.getContext();
+ Object glue = InterfaceAdapter.create(cx, type, callable);
+ if (glue != null) {
+ // Store for later retrival
+ glue = so.associateValue(key, glue);
+ return glue;
}
}
reportConversionError(value, type);
- }
- else {
+ } else {
reportConversionError(value, type);
}
break;
@@ -978,7 +968,6 @@ WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Class)}
private transient Hashtable fieldAndMethods;
private static final Object COERCED_INTERFACE_KEY = new Object();
- private static Method interfaceAdapter_create;
private static Method adapter_writeAdapterObject;
private static Method adapter_readAdapterObject;
@@ -1003,14 +992,6 @@ WrapFactory#wrap(Context cx, Scriptable scope, Object obj, Class)}
adapter_readAdapterObject = null;
}
}
- cl = Kit.classOrNull("org.mozilla.javascript.InterfaceAdapter");
- if (cl != null) {
- try {
- sig2[0] = ScriptRuntime.ClassClass;
- sig2[1] = ScriptRuntime.FunctionClass;
- interfaceAdapter_create = cl.getMethod("create", sig2);
- } catch (Exception ex) { }
- }
}
}
diff --git a/js/rhino/src/org/mozilla/javascript/ScriptRuntime.java b/js/rhino/src/org/mozilla/javascript/ScriptRuntime.java
index 3cbaf64d3110..b6ce79b5f378 100644
--- a/js/rhino/src/org/mozilla/javascript/ScriptRuntime.java
+++ b/js/rhino/src/org/mozilla/javascript/ScriptRuntime.java
@@ -121,6 +121,17 @@ public class ScriptRuntime {
private static final Object LIBRARY_SCOPE_KEY = new Object();
+ public static boolean isRhinoRuntimeType(Class cl)
+ {
+ if (cl.isPrimitive()) {
+ return (cl != Character.TYPE);
+ } else {
+ return (cl == StringClass || cl == BooleanClass
+ || NumberClass.isAssignableFrom(cl)
+ || ScriptableClass.isAssignableFrom(cl));
+ }
+ }
+
public static ScriptableObject initStandardObjects(Context cx,
ScriptableObject scope,
boolean sealed)
diff --git a/js/rhino/src/org/mozilla/javascript/VMBridge.java b/js/rhino/src/org/mozilla/javascript/VMBridge.java
index b7b51c586f31..ef85e720dbfc 100644
--- a/js/rhino/src/org/mozilla/javascript/VMBridge.java
+++ b/js/rhino/src/org/mozilla/javascript/VMBridge.java
@@ -112,4 +112,45 @@ public abstract class VMBridge
*/
protected abstract boolean tryToMakeAccessible(Object accessibleObject);
+ /**
+ * Create helper object to create later proxies implementing the specified
+ * interfaces later. Under JDK 1.3 the implementation can look like:
+ *
+ * return java.lang.reflect.Proxy.getProxyClass(..., interfaces).
+ * getConstructor(new Class[] {
+ * java.lang.reflect.InvocationHandler.class });
+ *
+ *
+ * @param interfaces Array with one or more interface class objects.
+ */
+ protected Object getInterfaceProxyHelper(ContextFactory cf,
+ Class[] interfaces)
+ {
+ throw Context.reportRuntimeError(
+ "VMBridge.getInterfaceProxyHelper is not supported");
+ }
+
+ /**
+ * Create proxy object for {@link InterfaceAdapter}. The proxy should call
+ * {@link InterfaceAdapter#invoke(ContextFactory cf,
+ * Object target,
+ * Scriptable topScope,
+ * Method method,
+ * Object[] args)}
+ * as implementation of interface methods associated with
+ * proxyHelper.
+ *
+ * @param proxyHelper The result of the previous call to
+ * {@link #getInterfaceProxyHelper(ContextFactory, Class[]).
+ */
+ protected Object newInterfaceProxy(Object proxyHelper,
+ ContextFactory cf,
+ InterfaceAdapter adapter,
+ Object target,
+ Scriptable topScope)
+ {
+ throw Context.reportRuntimeError(
+ "VMBridge.newInterfaceProxy is not supported");
+ }
+
}
diff --git a/js/rhino/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java b/js/rhino/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java
index df25b14732a3..4687dc489830 100644
--- a/js/rhino/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java
+++ b/js/rhino/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java
@@ -34,8 +34,6 @@
* file under either the NPL or the GPL.
*/
-// API class
-
package org.mozilla.javascript.jdk11;
import java.util.Hashtable;
diff --git a/js/rhino/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java b/js/rhino/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java
index 0d77a3688726..3a4ec96537fa 100644
--- a/js/rhino/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java
+++ b/js/rhino/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java
@@ -35,11 +35,14 @@
* file under either the NPL or the GPL.
*/
-// API class
-
package org.mozilla.javascript.jdk13;
import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import org.mozilla.javascript.*;
@@ -98,4 +101,52 @@ public class VMBridge_jdk13 extends VMBridge
return accessible.isAccessible();
}
+
+ protected Object getInterfaceProxyHelper(ContextFactory cf,
+ Class[] interfaces)
+ {
+ // XXX: How to handle interfaces array withclasses from different
+ // class loaders? Using cf.getApplicationClassLoader() ?
+ ClassLoader loader = interfaces[0].getClassLoader();
+ Class cl = Proxy.getProxyClass(loader, interfaces);
+ Constructor c;
+ try {
+ c = cl.getConstructor(new Class[] { InvocationHandler.class });
+ } catch (NoSuchMethodException ex) {
+ // Should not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ }
+ return c;
+ }
+
+ protected Object newInterfaceProxy(Object proxyHelper,
+ final ContextFactory cf,
+ final InterfaceAdapter adapter,
+ final Object target,
+ final Scriptable topScope)
+ {
+ Constructor c = (Constructor)proxyHelper;
+
+ InvocationHandler handler = new InvocationHandler() {
+ public Object invoke(Object proxy,
+ Method method,
+ Object[] args)
+ {
+ return adapter.invoke(cf, target, topScope, method, args);
+ }
+ };
+ Object proxy;
+ try {
+ proxy = c.newInstance(new Object[] { handler });
+ } catch (InvocationTargetException ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ } catch (IllegalAccessException ex) {
+ // Shouls not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ } catch (InstantiationException ex) {
+ // Shouls not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ }
+ return proxy;
+ }
}