mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-28 05:10:49 +00:00
Implementing bug 306308: now java.lang.reflect.Proxy is used for function->interface conversion
This commit is contained in:
parent
2077eeee97
commit
cad58c28dd
@ -89,7 +89,6 @@ Requires Ant version 1.2 or later
|
||||
|
||||
<!-- exclude classes that uses class generation library -->
|
||||
<exclude name="org/mozilla/javascript/JavaAdapter*.class"/>
|
||||
<exclude name="org/mozilla/javascript/InterfaceAdapter*.class"/>
|
||||
|
||||
<include name="org/mozilla/javascript/regexp/*.class"
|
||||
unless="no-regexp"/>
|
||||
|
@ -319,6 +319,14 @@ rethrown erro...</td>
|
||||
|
||||
</tr>
|
||||
|
||||
<tr style="height: 12.75pt;" height="17">
|
||||
|
||||
<td class="xl26" style="height: 12.75pt; width: 48pt; text-align: left;" x:num="" height="17" width="64"><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=306308">306308</a></td>
|
||||
|
||||
<td class="xl25" style="width: 330pt;" width="440">JS function as Java interface via reflect.Proxy</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -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 <tt>cl</tt> 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,
|
||||
"<ifglue>");
|
||||
cfw.addInterface(interfaceClass.getName());
|
||||
|
||||
// Generate empty constructor
|
||||
cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
|
||||
cfw.add(ByteCode.ALOAD_0); // this
|
||||
cfw.addInvoke(ByteCode.INVOKESPECIAL,
|
||||
superName, "<init>", "()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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
* <pre>
|
||||
* return java.lang.reflect.Proxy.getProxyClass(..., interfaces).
|
||||
* getConstructor(new Class[] {
|
||||
* java.lang.reflect.InvocationHandler.class });
|
||||
* </pre>
|
||||
*
|
||||
* @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
|
||||
* <tt>proxyHelper</tt>.
|
||||
*
|
||||
* @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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,8 +34,6 @@
|
||||
* file under either the NPL or the GPL.
|
||||
*/
|
||||
|
||||
// API class
|
||||
|
||||
package org.mozilla.javascript.jdk11;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user