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:
1 public class BridgeTest {
2 public interface CharList {
3 public int length();
4 public char charAt(int i);
5 }
6 void print(CharList l) {
7 for(int i=0; i<l.length(); i++)
8 System.out.print(l.charAt(i));
9 }
10 public static void main(String args[]) {
11 CharList list = Bridge.expose("Hello world\n", CharList.class);
12 print(list);
13 }
14 }
Bridge.java:
1 /* Make an object expose an interface at runtime */
2
3 import org.apache.bcel.generic.*;
4 import org.apache.bcel.Constants;
5
6 import java.lang.reflect.Method;
7 import java.util.*;
8
9 public abstract class Bridge<C,I> {
10 protected C __target; // the implementation of the interface
11 private final void __init(Object target) { this.__target = (C)target; }
12
13 // cache already-generated bridges
14 private static HashMap<Class, HashMap<Class, Class>> cache
15 = new HashMap<Class, HashMap<Class, Class>>();
16 // allow injection of classes from byte arrays
17 private static InjectingClassLoader loader = new InjectingClassLoader();
18
19 private static final void cacheSet(Class key1, Class key2, Class value) {
20 HashMap<Class,Class> intermediate = cache.get(key1);
21 if(intermediate == null)
22 cache.put(key1, intermediate = new HashMap<Class,Class>());
23 intermediate.put(key2, value);
24 }
25 private static final Class cacheGet(Class key1, Class key2) {
26 HashMap<Class,Class> intermediate = cache.get(key1);
27 if(intermediate == null)
28 return null;
29 return intermediate.get(key2);
30 }
31
32 // returns [ [ifaceMethods...] [fromMethods...] ]
33 private static Method[][] getMapping(Class from, Class iface) throws NoSuchMethodException,IllegalAccessException {
34 if(!iface.isInterface())
35 throw new IllegalArgumentException(iface.getName()+" is not an interface");
36 if(!java.lang.reflect.Modifier.isPublic(from.getModifiers()))
37 throw new IllegalAccessException(from.getName()+" is not public");
38 java.lang.reflect.Method[][] map = new java.lang.reflect.Method[2][];
39 map[0] = iface.getMethods();
40 map[1] = new java.lang.reflect.Method[map[0].length];
41
42 for(int i=0;i<map[0].length;i++) {
43 try {
44 Method match = from.getMethod(map[0][i].getName(), map[0][i].getParameterTypes());
45 if(!map[0][i].getReturnType().isAssignableFrom(match.getReturnType()))
46 throw new NoSuchMethodException("Return type "+match.getReturnType().getName()+
47 " of "+toString(match)+" is not compatible with return type "+
48 map[0][i].getReturnType().getName()+" of "+toString(map[0][i]));
49 map[1][i] = match;
50 } catch (NoSuchMethodException e) {
51 throw new NoSuchMethodException("Couldn't find "+toString(map[0][i])+" in "+
52 from.getName());
53 }
54 }
55 return map;
56 }
57 private static String toString(Method m) {
58 StringBuffer sb = new StringBuffer(m.getDeclaringClass().getName());
59 sb.append(".").append(m.getName()).append("(");
60 boolean first=true;
61 for(Class c : m.getParameterTypes()) {
62 if(!first)
63 sb.append(", ");
64 sb.append(c.getName());
65 first=false;
66 }
67 sb.append(")");
68 return sb.toString();
69 }
70
71 private static <C,I> Class<? extends Bridge<C,I>> create(Class<C> from, Class<I> iface) {
72 try {
73 Method[][] map = getMapping(from, iface);
74
75 String bridgeName = "Bridge_"+from.getSimpleName()+"_"+iface.getSimpleName();
76 // public class Bridge_Thing_Mungible extends Bridge<Thing,Mungible> implements Mungible
77 ClassGen cg = new ClassGen(
78 bridgeName,
79 Bridge.class.getName(), // superclass name
80 "<generated>", // source file name
81 Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SUPER, // flags
82 new String [] { iface.getName() } // interfaces
83 );
84
85 ConstantPoolGen cpg = cg.getConstantPool();
86 InstructionFactory factory = new InstructionFactory(cg, cpg);
87
88 ReferenceType targetType = (ReferenceType)Type.getType(from);
89
90 for(int i=0; i<map[0].length; i++) {
91 Method imeth = map[0][i];
92 Method fmeth = map[1][i];
93
94 Type ireturnType = Type.getType(imeth.getReturnType()),
95 freturnType = Type.getType(fmeth.getReturnType());
96 Class[] iargs = imeth.getParameterTypes(),
97 fargs = fmeth.getParameterTypes();
98 Type[] iargsT = new Type[iargs.length],
99 fargsT = new Type[fargs.length];
100 String[] argNames = new String[iargs.length];
101 for(int j=0; j<iargs.length; j++) {
102 iargsT[j] = Type.getType(iargs[j]);
103 fargsT[j] = Type.getType(fargs[j]);
104 argNames[j] = "arg"+i;
105 }
106
107 InstructionList il = new InstructionList();
108 // public int munge(Object arg0, int arg1) {
109 MethodGen mg = new MethodGen(
110 Constants.ACC_PUBLIC,
111 ireturnType,
112 iargsT,
113 argNames,
114 imeth.getName(),
115 bridgeName,
116 il, cpg);
117
118 // (Thing)this.__target
119 il.append(new ALOAD(0));
120 il.append(factory.createFieldAccess(
121 Bridge.class.getName(),
122 "__target",
123 Type.OBJECT,
124 Constants.GETFIELD));
125 il.append(factory.createCheckCast(targetType));
126
127 // .munge(arg0, arg1);
128 int position = 1;
129 for(int j=0;j<iargs.length;j++) {
130 il.append(factory.createLoad(iargsT[j], position));
131 position += iargsT[j].getSize();
132 }
133 il.append(factory.createInvoke(
134 from.getName(),
135 fmeth.getName(),
136 freturnType,
137 fargsT,
138 from.isInterface() ? Constants.INVOKEINTERFACE : Constants.INVOKEVIRTUAL));
139
140 // return (last result, if any)
141 il.append(factory.createReturn(ireturnType));
142
143 mg.setMaxStack();
144 cg.addMethod(mg.getMethod());
145 il.dispose(); // Allow instruction handles to be reused
146 }
147
148 // public Bridge_Thing_Mungible() { super(); }
149 cg.addEmptyConstructor(Constants.ACC_PUBLIC);
150
151 try { cg.getJavaClass().dump("proxy.class"); } catch(Exception e) { throw new RuntimeException(e); }
152
153 byte[] classData = cg.getJavaClass().getBytes();
154 Class c = loader.load(cg.getClassName(), classData);
155 return (Class<Bridge<C,I>>)c;
156 } catch (IllegalAccessException e) {
157 throw new BridgeFailure(from, iface, e);
158 } catch (NoSuchMethodException e) {
159 throw new BridgeFailure(from, iface, e);
160 }
161 }
162 private static <C,I> Class<? extends Bridge<C,I>> get(Class<C> from, Class<I> iface) {
163 // Pair<Class,Class> key = new Pair<Class,Class>(from, iface);
164 Class<? extends Bridge<C,I>> bridgeClass = (Class<? extends Bridge<C,I>>)cacheGet(from, iface);
165 if(bridgeClass == null) {
166 bridgeClass = create(from, iface);
167 cacheSet(from, iface, bridgeClass);
168 }
169 return bridgeClass;
170 }
171
172 // Expose interface iface by creating a proxy.
173 // the type of target must be from, a subclass of from, or a class implementing from.
174 // from must be public, and must expose all methods in iface.
175 public static <I> I expose(Object target, Class from, Class<I> iface) {
176 try {
177 Class<? extends Bridge<?,I>> c = Bridge.get(from, iface);
178 Bridge<?,I> proxy = c.newInstance();
179 proxy.__init(target);
180 return (I)proxy;
181 } catch (InstantiationException e) {
182 throw new RuntimeException(e);
183 } catch (IllegalAccessException e) {
184 throw new RuntimeException(e);
185 }
186 }
187
188 // Expose interface iface by creating a proxy.
189 // Tries expose(target, target.getClass(), iface) first.
190 // then works up the class hierarchy. If doesn't find a public superclass
191 // that exposes all methods, it tries interfaces.
192 // if you know in advance the class or interface that exposes the needed methods,
193 // use expose(target, Class.forName("ExposingClass"), iface).
194 public static <I> I expose(Object target, Class<I> iface) {
195 Class tClass = target.getClass();
196
197 Class from = tClass;
198 do {
199 try {
200 I result = expose(target, from, iface);
201 if(tClass != from) // if tClass == tFrom then get() sets the cache
202 cacheSet(tClass, iface, result.getClass());
203 return result;
204 } catch (BridgeFailure e) {
205 Throwable cause = e.getCause();
206 if(cause instanceof NoSuchMethodException) {
207 // have traced superclass up until the interface isn't satisfied.
208 // try interfaces and then give up.
209 // in the case of a private implementation of a public interface,
210 // this allows you to subset the interface.
211 Class[] targetIfaces = target.getClass().getInterfaces();
212 for(Class targetIface : targetIfaces)
213 try {
214 I result = expose(target, targetIface, iface);
215 cacheSet(tClass, iface, result.getClass()); // perf hack
216 return result;
217 // ignore, reported exception is that for class hierarchy.
218 } catch (BridgeFailure ex) {}
219 throw new BridgeFailure(target, iface, e);
220 } else if(!(cause instanceof IllegalAccessException))
221 throw e;
222 }
223 from = from.getSuperclass();
224 } while(from != null);
225 throw new RuntimeException("Object "+target+" ("+target.getClass().getName()+") has no public superclass");
226 }
227 }
228 class BridgeFailure extends RuntimeException {
229 public BridgeFailure(Class from, Class iface, Exception cause) {
230 super("Could not map class "+from.getName()+" to interface "+iface.getName());
231 initCause(cause);
232 }
233 public BridgeFailure(Object target, Class iface, Exception cause) {
234 super("Could not map first public supertype of " + target +
235 " (" + target.getClass().getName() + ")," +
236 " or any implemented interface, to interface " + iface.getName());
237 initCause(cause);
238 }
239 }
240 // ClassLoader implementation to let you load classes from byte arrays
241 class InjectingClassLoader extends ClassLoader {
242 public InjectingClassLoader() {
243 super(InjectingClassLoader.class.getClassLoader());
244 }
245 public Class load(String name, byte[] buffer) {
246 return defineClass(name, buffer, 0, buffer.length);
247 }
248 }