Implementing bug 306308: now java.lang.reflect.Proxy is used for function->interface conversion

This commit is contained in:
igor%mir2.org 2005-08-29 10:57:30 +00:00
parent 2077eeee97
commit cad58c28dd
8 changed files with 210 additions and 174 deletions

View File

@ -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"/>

View File

@ -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>

View File

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

View File

@ -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) { }
}
}
}

View File

@ -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)

View File

@ -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");
}
}

View File

@ -34,8 +34,6 @@
* file under either the NPL or the GPL.
*/
// API class
package org.mozilla.javascript.jdk11;
import java.util.Hashtable;

View File

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