// Creation of delegates from reified static and instance methods in Java // sestoft@itu.dk * 2002-05-08, 2006-03-10 // Requires the gnu.bytecode package from http://www.gnu.org/software/kawa import gnu.bytecode.*; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; /* (1) Creating a delegate from a static method m of class C, with return type R and parameter types T1,...,Tn, represented by method object mo. The call createDelegate(I.class, mo) returns an object dlg that can be cast to interface I, and so that a call to dlg.call(...) will call the method as if by C.m(...). This works by constructing, at runtime, a class Dlg that implements interface I and contains a method `call' that calls the given method, as if written according to this schema: public class Dlg implements I extends Object { public Dlg() { super(); } public R call(T1 p1, ..., Tn pn) { return C.m(p1, ..., pn); } } The new class Dlg is loaded into the JVM, and an instance of the class is created and returned. (2) Creating a delegate from an instance method m of class C, with return type R and parameter types T1,...,Tn, represented by method object mo. The call createDelegate(I.class, mo, o) returns an object dlg that can be cast to interface I, and so that a call to dlg.call(...) will call the method as if by o.m(...). This works as above, except that the constructed class looks like this: public class Dlg implements I extends Object { private C obj; public Dlg() { super(); } public void setObj(Object obj) { this.obj = (C)obj; } public R call(T1 p1, ..., Tn pn) { return obj.m(p1, ..., pn); } } The new class Dlg is loaded into the JVM, an instance dlg of the class is created, the dlg.setObj method is called on the given object o, and dlg is returned. TODO: Reuse a previously generated MyDelegate class when creating a new delegate for an interface I and method M for which a class was previously generated. This can be done with a HashMap from Pair of Class and Method to Class. */ class Delegate { private static int delegateNumber = 0; private static ArrayClassLoader classLoader = new ArrayClassLoader(); public static Object createDelegate(Class intrface, java.lang.reflect.Method method) { return createDelegate(intrface, method, null); } public static synchronized Object createDelegate(Class intrface, java.lang.reflect.Method method, Object obj) { // Check that intrface is a public interface with a single call method if (!(intrface.isInterface() && Modifier.isPublic(intrface.getModifiers()))) throw new Error("Delegate type must be a public interface"); java.lang.reflect.Method[] intrfaceMethods = intrface.getMethods(); if (!(intrfaceMethods.length == 1 && intrfaceMethods[0].getName().equals("call"))) throw new Error("Interface must describe a single call method"); java.lang.reflect.Method call = intrfaceMethods[0]; // Check that intrface and method agree on return and parameter types: Class returnType = call.getReturnType(); if (returnType != method.getReturnType()) throw new Error("Wrong call() return type: " + returnType); Class[] parameterTypes = call.getParameterTypes(); if (!equalClasses(parameterTypes, method.getParameterTypes())) throw new Error("Wrong call() parameter types"); // Check that object is given iff method is instance method: boolean isStatic = Modifier.isStatic(method.getModifiers()); if (isStatic) { if (obj != null) throw new Error("Delegate to static method needs no object"); } else if (obj == null) throw new Error("Delegate to instance method needs an object"); // Check that the method and the declaring class are public: Class methodClass = method.getDeclaringClass(); if (!(Modifier.isPublic(methodClass.getModifiers()) && Modifier.isPublic(method.getModifiers()))) throw new Error("Method and its class must be public"); ClassType methodClassType = (ClassType)Type.make(methodClass); String dlgClassName = "MyDelegate" + delegateNumber++; ClassType dlgClass = new ClassType(dlgClassName); dlgClass.setSuper("java.lang.Object"); dlgClass.addInterface((ClassType)Type.make(intrface)); dlgClass.setModifiers(Access.PUBLIC); { // Build constructor: public MyDelegate117() { super(); } gnu.bytecode.Method initMethod = dlgClass.addMethod("", new Type[] {}, Type.void_type, 0); initMethod.setModifiers(Access.PUBLIC); initMethod.initCode(); CodeAttr jvmg = initMethod.getCode(); Scope scope = initMethod.pushScope(); Variable thisVar = scope.addVariable(jvmg, dlgClass, "this"); jvmg.emitLoad(thisVar); jvmg.emitInvokeSpecial(ClassType.make("java.lang.Object") .getMethod("", new Type[] {})); initMethod.popScope(); jvmg.emitReturn(); } gnu.bytecode.Field objField = null; if (!isStatic) { objField = dlgClass.addField("obj", methodClassType); // Build method: public void setObj(Object o) { this.obj = ()o; } gnu.bytecode.Method setObjMethod = dlgClass.addMethod("setObj", new Type[] { Type.make(Object.class) }, Type.void_type, 0); setObjMethod.setModifiers(Access.PUBLIC); setObjMethod.initCode(); CodeAttr jvmg = setObjMethod.getCode(); Scope scope = setObjMethod.pushScope(); Variable thisVar = scope.addVariable(jvmg, dlgClass, "this"); Variable objVar = scope.addVariable(jvmg, dlgClass, "obj"); jvmg.emitLoad(thisVar); jvmg.emitLoad(objVar); jvmg.emitCheckcast(methodClassType); jvmg.emitPutField(objField); setObjMethod.popScope(); jvmg.emitReturn(); } { // Build: public static call() { ... } String signature = signature(parameterTypes, returnType); gnu.bytecode.Method dlgMethod = dlgClass.addMethod("call"); dlgMethod.setSignature(signature); dlgMethod.setModifiers(Access.PUBLIC); dlgMethod.initCode(); CodeAttr jvmg = dlgMethod.getCode(); Scope scope = dlgMethod.pushScope(); Variable thisVar = scope.addVariable(jvmg, dlgClass, "this"); if (!isStatic) { jvmg.emitLoad(thisVar); jvmg.emitGetField(objField); } // Push arguments for (int i=0; i