// Ruby style mixin in Java using dynamic code generation
1
2 import net.sf.cglib.core.*;
3 import net.sf.cglib.asm.ClassVisitor;
4 import net.sf.cglib.asm.Type;
5
6 import java.lang.reflect.Constructor;
7 import java.lang.reflect.InvocationHandler;
8 import java.lang.reflect.Method;
9 import java.lang.reflect.Proxy;
10 import java.util.*;
11
12 public class MixinTest
13 {
14 /*
15 * The Demo
16 */
17 public interface Chimera extends Lion, Goat, Serpent, Animal
18 {
19 }
20
21 public static void main(String[] args)
22 {
23 Chimera chimera = (Chimera) MixinFactory.create(Chimera.class, new AnimalImpl());
24 System.out.println("The Chimera: ");
25 chimera.roar();
26 chimera.stomp();
27 chimera.slither();
28 chimera.action("Sing a song");
29
30 System.out.println("Reflection Mixin");
31 timeMixin();
32 timeMixin();
33 timeMixin();
34 System.out.println("CGLib Mixin");
35 timeCGMixin();
36 timeCGMixin();
37 timeCGMixin();
38 System.out.println("Plain method call");
39 timeNormal();
40 timeNormal();
41 timeNormal();
42 }
43
44 private static void timeMixin()
45 {
46 Chimera chimera = (Chimera) MixinFactory.create(Chimera.class, new AnimalImpl());
47 int sampleSize = 1000000;
48 long start = System.currentTimeMillis();
49 for (int i = 0; i < sampleSize; i++)
50 {
51 chimera.roar();
52 }
53 long total = System.currentTimeMillis() - start;
54 System.out.println("Total = " + total);
55 }
56
57 private static void timeCGMixin()
58 {
59 Chimera chimera = (Chimera) CGMixinFactory.create(Chimera.class, new AnimalImpl());
60 int sampleSize = 1000000;
61 long start = System.currentTimeMillis();
62 for (int i = 0; i < sampleSize; i++)
63 {
64 chimera.roar();
65 }
66 long total = System.currentTimeMillis() - start;
67 System.out.println("Total = " + total);
68 }
69
70 private static void timeNormal()
71 {
72 int sampleSize = 1000000;
73 Lion lion = new LionImpl(new AnimalImpl());
74 long start = System.currentTimeMillis();
75 for (int i = 0; i < sampleSize; i++)
76 {
77 lion.roar();
78 }
79 long total = System.currentTimeMillis() - start;
80 System.out.println("Total = " + total);
81 }
82
83 /*
84 * The Interfaces
85 */
86 public interface Lion
87 {
88 void roar();
89 }
90
91 public interface Goat
92 {
93 void stomp();
94 }
95
96 public interface Serpent
97 {
98 void slither();
99 }
100
101 public interface Animal
102 {
103 void action(String description);
104 }
105
106 /*
107 * The Implementations
108 */
109 public static class LionImpl implements Lion
110 {
111 private Animal animal;
112
113 public LionImpl(Animal animal)
114 {
115 this.animal = animal;
116 }
117
118 public void roar()
119 {
120 animal.action("Roars with lion head");
121 }
122 }
123
124 public static class GoatImpl implements Goat
125 {
126 private Animal animal;
127
128 public GoatImpl(Animal animal)
129 {
130 this.animal = animal;
131 }
132
133 public void stomp()
134 {
135 animal.action("Stomps with goat foot");
136 }
137 }
138
139 public static class SerpentImpl implements Serpent
140 {
141 private Animal animal;
142
143 public SerpentImpl(Animal animal)
144 {
145 this.animal = animal;
146 }
147
148 public void slither()
149 {
150 animal.action("Slithers with serpent tail");
151 }
152 }
153
154 public static class AnimalImpl implements Animal
155 {
156 String last;
157
158 public void action(String description)
159 {
160 last = description;
161 //System.out.println(description);
162 }
163 }
164
165
166 public static class CGMixinFactory
167 {
168 public static Object create(Class interfaceClass, Object core)
169 {
170 CGGenerator generator = new CGGenerator(interfaceClass, delegates(interfaceClass, core).toArray());
171 return generator.create();
172 }
173
174 private static Collection delegates(Class interfaceClass, Object core)
175 {
176 Collection delegates = new HashSet();
177 for (int i = 0; i < interfaceClass.getInterfaces().length; i++)
178 {
179 Class anInterface = interfaceClass.getInterfaces()[i];
180 try
181 {
182 Class delegateClass = Class.forName(anInterface.getName() + "Impl");
183 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, core.getClass());
184 if (delegateConstructor != null)
185 {
186 delegates.add(delegateConstructor.newInstance(new Object[]{core}));
187 }
188 else
189 {
190 delegates.add(core);
191 }
192 }
193 catch (Exception e)
194 {
195 throw new RuntimeException(e);
196 }
197 }
198 return delegates;
199 }
200
201 private static Constructor findBestMatchConstrustor(Class delegateClass, Class coreClass)
202 {
203 if (coreClass == null)
204 {
205 return null;
206 }
207 try
208 {
209 return delegateClass.getConstructor(new Class[]{coreClass});
210 }
211 catch (NoSuchMethodException e)
212 {
213 Class[] interfaces = coreClass.getInterfaces();
214 for (int i = 0; interfaces != null && i < interfaces.length; i++)
215 {
216 Class interfaceClass = interfaces[i];
217 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, interfaceClass);
218 if (delegateConstructor != null)
219 {
220 return delegateConstructor;
221 }
222 }
223 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, coreClass.getSuperclass());
224 if (delegateConstructor != null)
225 {
226 return delegateConstructor;
227 }
228 }
229 return null;
230 }
231 }
232
233 public static class CGGenerator extends AbstractClassGenerator
234 {
235 private static final MixinKey KEY_FACTORY =
236 (MixinKey)KeyFactory.create(MixinKey.class, KeyFactory.CLASS_BY_NAME);
237
238 interface MixinKey
239 {
240 public Object newInstance(Class classes, int[] route);
241 }
242
243 private static final Source SOURCE = new Source(CGMixinFactory.class.getName());
244 private Class interfaceClass;
245 private Object[] delegates;
246 private int route[];
247 private Method methods[];
248
249 public CGGenerator(Class interfaceClass, Object delegates[])
250 {
251 super(SOURCE);
252 this.interfaceClass = interfaceClass;
253 this.delegates = delegates;
254 methods = interfaceClass.getMethods();
255 calculateRoute();
256 }
257
258 private void calculateRoute()
259 {
260 route = new int[methods.length];
261 Map delegateIndexes = calculateDelegateIndexes();
262 for (int i = 0; i < methods.length; i++)
263 {
264 route[i] = ((Integer)delegateIndexes.get(methods[i].getDeclaringClass().getName())).intValue();
265 }
266 }
267
268 private Map calculateDelegateIndexes()
269 {
270 Map delegateIndexes = new HashMap();
271 for (int i = 0; i < delegates.length; i++)
272 {
273 delegateIndexes.put(interfaceNameForImpl(delegates[i]), new Integer(i));
274 }
275 return delegateIndexes;
276 }
277
278 private String interfaceNameForImpl(Object delegate)
279 {
280 return delegate.getClass().getName().replaceAll("Impl", "");
281 }
282
283 public Object create()
284 {
285 return super.create(KEY_FACTORY.newInstance(interfaceClass, route));
286 }
287 protected ClassLoader getDefaultClassLoader()
288 {
289 return interfaceClass.getClassLoader();
290 }
291
292 protected Object firstInstance(Class type) throws Exception
293 {
294 return ReflectUtils.newInstance(type, new Class[] {Object[].class}, new Object[] {delegates});
295 }
296
297 protected Object nextInstance(Object instance) throws Exception
298 {
299 return firstInstance(instance.getClass());
300 }
301
302 public void generateClass(ClassVisitor classVisitor) throws Exception
303 {
304 new CGMixinEmitter(classVisitor, interfaceClass, getClassName(), methods, route);
305 }
306 }
307
308 public static class CGMixinEmitter extends ClassEmitter
309 {
310 private static final String FIELD_NAME = "CGLIB$DELEGATES";
311 private static final Signature CSTRUCT_OBJECT_ARRAY = TypeUtils.parseConstructor("Object[]");
312
313 public CGMixinEmitter(ClassVisitor classVisitor, Class interfaceClass, String className, Method methods[], int route[])
314 {
315 super(classVisitor);
316
317 begin_class(Constants.V1_3,
318 Constants.ACC_PUBLIC,
319 className,
320 getMixinSuperType(),
321 TypeUtils.getTypes(new Class[]{interfaceClass}),
322 Constants.SOURCE_FILE);
323
324 declare_field(Constants.ACC_PRIVATE, FIELD_NAME, Constants.TYPE_OBJECT_ARRAY, null, null);
325
326 CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null, null);
327 e.load_this();
328 e.super_invoke_constructor();
329 e.load_this();
330 e.load_arg(0);
331 e.putfield(FIELD_NAME);
332 e.return_value();
333 e.end_method();
334
335 for (int j = 0; j < methods.length; j++)
336 {
337 MethodInfo method = ReflectUtils.getMethodInfo(methods[j]);
338 e = EmitUtils.begin_method(this, method, Constants.ACC_PUBLIC);
339 e.load_this();
340 e.getfield(FIELD_NAME);
341 e.aaload(route[j]);
342 e.checkcast(method.getClassInfo().getType());
343 e.load_args();
344 e.invoke(method);
345 e.return_value();
346 e.end_method();
347 }
348
349 end_class();
350 }
351
352 private Type getMixinSuperType()
353 {
354 return TypeUtils.getTypes(new Class[]{Object.class})[0];
355 }
356 }
357
358 /*
359 * The Guts
360 */
361 public static class MixinFactory
362 {
363 private static class MixinInvocationHandler implements InvocationHandler
364 {
365 private Map implMap = new HashMap();
366 private Map methodCache = new HashMap();
367
368 public MixinInvocationHandler(Class clazz, Object core)
369 {
370 for (int i = 0; i < clazz.getInterfaces().length; i++)
371 {
372 Class anInterface = clazz.getInterfaces()[i];
373 try
374 {
375 Class delegateClass = Class.forName(anInterface.getName() + "Impl");
376 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, core.getClass());
377 if (delegateConstructor != null)
378 {
379 implMap.put(anInterface, delegateConstructor.newInstance(new Object[]{core}));
380 }
381 else
382 {
383 implMap.put(anInterface, core);
384 }
385 }
386 catch (Exception e)
387 {
388 throw new RuntimeException(e);
389 }
390 }
391 }
392
393 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
394 {
395 Object delegate = implMap.get(method.getDeclaringClass());
396
397 return getImplMethod(delegate.getClass(), method).invoke(delegate, args);
398 }
399
400 private Method getImplMethod(Class implClass, Method method) throws NoSuchMethodException
401 {
402 if (methodCache.containsKey(method))
403 {
404 return (Method) methodCache.get(method);
405 }
406 else
407 {
408 Method declaredMethod = implClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
409 methodCache.put(method, declaredMethod);
410 return declaredMethod;
411 }
412 }
413
414 private static Constructor findBestMatchConstrustor(Class delegateClass, Class coreClass)
415 {
416 if (coreClass == null)
417 {
418 return null;
419 }
420 try
421 {
422 return delegateClass.getConstructor(new Class[]{coreClass});
423 }
424 catch (NoSuchMethodException e)
425 {
426 Class[] interfaces = coreClass.getInterfaces();
427 for (int i = 0; interfaces != null && i < interfaces.length; i++)
428 {
429 Class interfaceClass = interfaces[i];
430 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, interfaceClass);
431 if (delegateConstructor != null)
432 {
433 return delegateConstructor;
434 }
435 }
436 Constructor delegateConstructor = findBestMatchConstrustor(delegateClass, coreClass.getSuperclass());
437 if (delegateConstructor != null)
438 {
439 return delegateConstructor;
440 }
441 }
442 return null;
443 }
444 }
445
446 public static Object create(Class clazz, Object core)
447 {
448 return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
449 new MixinInvocationHandler(clazz, core));
450 }
451 }
452 }