Force an object expose an interface at runtime, through a generated proxy class. It must implement all required methods.
Requires BCEL.
UPDATED: does verification at bridge generation time.
expose(Object, Class) will search for the correct public target type to use.
UPDATED: performance hacks.
Example usage:
public class BridgeTest {
public interface CharList {
public int length();
public char charAt(int i);
}
void print(CharList l) {
for(int i=0; i<l.length(); i++)
System.out.print(l.charAt(i));
}
public static void main(String args[]) {
CharList list = Bridge.expose("Hello world\n", CharList.class);
print(list);
}
}Bridge.java:
/* Make an object expose an interface at runtime */
import org.apache.bcel.generic.*;
import org.apache.bcel.Constants;
import java.lang.reflect.Method;
import java.util.*;
public abstract class Bridge<C,I> {
protected C __target; // the implementation of the interface
private final void __init(Object target) { this.__target = (C)target; }
// cache already-generated bridges
private static HashMap<Class, HashMap<Class, Class>> cache
= new HashMap<Class, HashMap<Class, Class>>();
// allow injection of classes from byte arrays
private static InjectingClassLoader loader = new InjectingClassLoader();
private static final void cacheSet(Class key1, Class key2, Class value) {
HashMap<Class,Class> intermediate = cache.get(key1);
if(intermediate == null)
cache.put(key1, intermediate = new HashMap<Class,Class>());
intermediate.put(key2, value);
}
private static final Class cacheGet(Class key1, Class key2) {
HashMap<Class,Class> intermediate = cache.get(key1);
if(intermediate == null)
return null;
return intermediate.get(key2);
}
// returns [ [ifaceMethods...] [fromMethods...] ]
private static Method[][] getMapping(Class from, Class iface) throws NoSuchMethodException,IllegalAccessException {
if(!iface.isInterface())
throw new IllegalArgumentException(iface.getName()+" is not an interface");
if(!java.lang.reflect.Modifier.isPublic(from.getModifiers()))
throw new IllegalAccessException(from.getName()+" is not public");
java.lang.reflect.Method[][] map = new java.lang.reflect.Method[2][];
map[0] = iface.getMethods();
map[1] = new java.lang.reflect.Method[map[0].length];
for(int i=0;i<map[0].length;i++) {
try {
Method match = from.getMethod(map[0][i].getName(), map[0][i].getParameterTypes());
if(!map[0][i].getReturnType().isAssignableFrom(match.getReturnType()))
throw new NoSuchMethodException("Return type "+match.getReturnType().getName()+
" of "+toString(match)+" is not compatible with return type "+
map[0][i].getReturnType().getName()+" of "+toString(map[0][i]));
map[1][i] = match;
} catch (NoSuchMethodException e) {
throw new NoSuchMethodException("Couldn't find "+toString(map[0][i])+" in "+
from.getName());
}
}
return map;
}
private static String toString(Method m) {
StringBuffer sb = new StringBuffer(m.getDeclaringClass().getName());
sb.append(".").append(m.getName()).append("(");
boolean first=true;
for(Class c : m.getParameterTypes()) {
if(!first)
sb.append(", ");
sb.append(c.getName());
first=false;
}
sb.append(")");
return sb.toString();
}
private static <C,I> Class<? extends Bridge<C,I>> create(Class<C> from, Class<I> iface) {
try {
Method[][] map = getMapping(from, iface);
String bridgeName = "Bridge_"+from.getSimpleName()+"_"+iface.getSimpleName();
// public class Bridge_Thing_Mungible extends Bridge<Thing,Mungible> implements Mungible
ClassGen cg = new ClassGen(
bridgeName,
Bridge.class.getName(), // superclass name
"<generated>", // source file name
Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SUPER, // flags
new String [] { iface.getName() } // interfaces
);
ConstantPoolGen cpg = cg.getConstantPool();
InstructionFactory factory = new InstructionFactory(cg, cpg);
ReferenceType targetType = (ReferenceType)Type.getType(from);
for(int i=0; i<map[0].length; i++) {
Method imeth = map[0][i];
Method fmeth = map[1][i];
Type ireturnType = Type.getType(imeth.getReturnType()),
freturnType = Type.getType(fmeth.getReturnType());
Class[] iargs = imeth.getParameterTypes(),
fargs = fmeth.getParameterTypes();
Type[] iargsT = new Type[iargs.length],
fargsT = new Type[fargs.length];
String[] argNames = new String[iargs.length];
for(int j=0; j<iargs.length; j++) {
iargsT[j] = Type.getType(iargs[j]);
fargsT[j] = Type.getType(fargs[j]);
argNames[j] = "arg"+i;
}
InstructionList il = new InstructionList();
// public int munge(Object arg0, int arg1) {
MethodGen mg = new MethodGen(
Constants.ACC_PUBLIC,
ireturnType,
iargsT,
argNames,
imeth.getName(),
bridgeName,
il, cpg);
// (Thing)this.__target
il.append(new ALOAD(0));
il.append(factory.createFieldAccess(
Bridge.class.getName(),
"__target",
Type.OBJECT,
Constants.GETFIELD));
il.append(factory.createCheckCast(targetType));
// .munge(arg0, arg1);
int position = 1;
for(int j=0;j<iargs.length;j++) {
il.append(factory.createLoad(iargsT[j], position));
position += iargsT[j].getSize();
}
il.append(factory.createInvoke(
from.getName(),
fmeth.getName(),
freturnType,
fargsT,
from.isInterface() ? Constants.INVOKEINTERFACE : Constants.INVOKEVIRTUAL));
// return (last result, if any)
il.append(factory.createReturn(ireturnType));
mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose(); // Allow instruction handles to be reused
}
// public Bridge_Thing_Mungible() { super(); }
cg.addEmptyConstructor(Constants.ACC_PUBLIC);
try { cg.getJavaClass().dump("proxy.class"); } catch(Exception e) { throw new RuntimeException(e); }
byte[] classData = cg.getJavaClass().getBytes();
Class c = loader.load(cg.getClassName(), classData);
return (Class<Bridge<C,I>>)c;
} catch (IllegalAccessException e) {
throw new BridgeFailure(from, iface, e);
} catch (NoSuchMethodException e) {
throw new BridgeFailure(from, iface, e);
}
}
private static <C,I> Class<? extends Bridge<C,I>> get(Class<C> from, Class<I> iface) {
// Pair<Class,Class> key = new Pair<Class,Class>(from, iface);
Class<? extends Bridge<C,I>> bridgeClass = (Class<? extends Bridge<C,I>>)cacheGet(from, iface);
if(bridgeClass == null) {
bridgeClass = create(from, iface);
cacheSet(from, iface, bridgeClass);
}
return bridgeClass;
}
// Expose interface iface by creating a proxy.
// the type of target must be from, a subclass of from, or a class implementing from.
// from must be public, and must expose all methods in iface.
public static <I> I expose(Object target, Class from, Class<I> iface) {
try {
Class<? extends Bridge<?,I>> c = Bridge.get(from, iface);
Bridge<?,I> proxy = c.newInstance();
proxy.__init(target);
return (I)proxy;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
// Expose interface iface by creating a proxy.
// Tries expose(target, target.getClass(), iface) first.
// then works up the class hierarchy. If doesn't find a public superclass
// that exposes all methods, it tries interfaces.
// if you know in advance the class or interface that exposes the needed methods,
// use expose(target, Class.forName("ExposingClass"), iface).
public static <I> I expose(Object target, Class<I> iface) {
Class tClass = target.getClass();
Class from = tClass;
do {
try {
I result = expose(target, from, iface);
if(tClass != from) // if tClass == tFrom then get() sets the cache
cacheSet(tClass, iface, result.getClass());
return result;
} catch (BridgeFailure e) {
Throwable cause = e.getCause();
if(cause instanceof NoSuchMethodException) {
// have traced superclass up until the interface isn't satisfied.
// try interfaces and then give up.
// in the case of a private implementation of a public interface,
// this allows you to subset the interface.
Class[] targetIfaces = target.getClass().getInterfaces();
for(Class targetIface : targetIfaces)
try {
I result = expose(target, targetIface, iface);
cacheSet(tClass, iface, result.getClass()); // perf hack
return result;
// ignore, reported exception is that for class hierarchy.
} catch (BridgeFailure ex) {}
throw new BridgeFailure(target, iface, e);
} else if(!(cause instanceof IllegalAccessException))
throw e;
}
from = from.getSuperclass();
} while(from != null);
throw new RuntimeException("Object "+target+" ("+target.getClass().getName()+") has no public superclass");
}
}
class BridgeFailure extends RuntimeException {
public BridgeFailure(Class from, Class iface, Exception cause) {
super("Could not map class "+from.getName()+" to interface "+iface.getName());
initCause(cause);
}
public BridgeFailure(Object target, Class iface, Exception cause) {
super("Could not map first public supertype of " + target +
" (" + target.getClass().getName() + ")," +
" or any implemented interface, to interface " + iface.getName());
initCause(cause);
}
}
// ClassLoader implementation to let you load classes from byte arrays
class InjectingClassLoader extends ClassLoader {
public InjectingClassLoader() {
super(InjectingClassLoader.class.getClassLoader());
}
public Class load(String name, byte[] buffer) {
return defineClass(name, buffer, 0, buffer.length);
}
}