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