001 /**
002 * Copyright (C) 2012 FuseSource, Inc.
003 * http://fusesource.com
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.fusesource.hawtdispatch;
019
020 import java.lang.reflect.Constructor;
021 import java.lang.reflect.InvocationTargetException;
022 import java.lang.reflect.Method;
023
024 import org.fusesource.hawtdispatch.DispatchQueue;
025 import org.objectweb.asm.ClassWriter;
026 import org.objectweb.asm.FieldVisitor;
027 import org.objectweb.asm.Label;
028 import org.objectweb.asm.MethodVisitor;
029 import org.objectweb.asm.Opcodes;
030 import org.objectweb.asm.Type;
031
032 import static org.objectweb.asm.Type.*;
033
034 import static org.objectweb.asm.ClassWriter.*;
035
036 /**
037 * <p>
038 * This class creates proxy objects that allow you to easily service all
039 * method calls to an interface via a {@link DispatchQueue}.
040 * </p><p>
041 * The general idea is that proxy asynchronously invoke the delegate using
042 * the dispatch queue. The proxy implementation is generated using ASM
043 * using the following code generation pattern:
044 * </p>
045 * <pre>
046 * class <<interface-class>>$__ACTOR_PROXY__ implements <<interface-class>> {
047 *
048 * private final <<interface-class>> target;
049 * private final DispatchQueue queue;
050 *
051 * public <<interface-class>>$__ACTOR_PROXY__(<<interface-class>> target, DispatchQueue queue) {
052 * this.target = target;
053 * this.queue = queue;
054 * }
055 *
056 * <<for each method in interface-class>>
057 *
058 * <<method-signature>> {
059 * queue.execute( new Runnable() {
060 * public void run() {
061 * this.target.<<method-call>>;
062 * }
063 * } );
064 * }
065 * <<for each end>>
066 * }
067 * </pre>
068 *
069 *
070 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
071 */
072 public class DispatchQueueProxy {
073
074 /**
075 * Create an asynchronous dispatch proxy to the target object via the dispatch queue. The
076 * class loader of the generated proxy will be the same as the class loader of the target
077 * object.
078 *
079 * @param interfaceClass the interface that will be implemented by the proxy
080 * @param target the delegate object the proxy will asynchronously invoke
081 * @param queue the dispatch queue that the asynchronous runnables will execute on
082 * @param <T> the type of the interface class
083 * @return the new asynchronous dispatch proxy
084 * @throws IllegalArgumentException
085 */
086 public static <T> T create(Class<T> interfaceClass, T target, DispatchQueue queue) throws IllegalArgumentException {
087 return create(target.getClass().getClassLoader(), interfaceClass, target, queue);
088 }
089
090 /**
091 * Create an asynchronous dispatch proxy to the target object via the dispatch queue.
092 *
093 * @param classLoader the classloader which the proxy class should use
094 * @param interfaceClass the interface that will be implemented by the proxy
095 * @param target the delegate object the proxy will asynchronously invoke
096 * @param queue the dispatch queue that the asyncronous runnables will execute on
097 * @param <T> the type of the interface asynchronous
098 * @return the new asynchronous dispatch proxy
099 * @throws IllegalArgumentException
100 */
101 synchronized public static <T> T create(ClassLoader classLoader, Class<T> interfaceClass, T target, DispatchQueue queue) throws IllegalArgumentException {
102 Class<T> proxyClass = getProxyClass(classLoader, interfaceClass);
103 Constructor<?> constructor = proxyClass.getConstructors()[0];
104 Object rc;
105 try {
106 rc = constructor.newInstance(new Object[]{target, queue});
107 } catch (Throwable e) {
108 throw new RuntimeException("Could not create an instance of the proxy due to: "+e.getMessage(), e);
109 }
110 return proxyClass.cast(rc);
111 }
112
113 @SuppressWarnings("unchecked")
114 private static <T> Class<T> getProxyClass(ClassLoader loader, Class<T> interfaceClass) throws IllegalArgumentException {
115 String proxyName = proxyName(interfaceClass);
116 try {
117 return (Class<T>) loader.loadClass(proxyName);
118 } catch (ClassNotFoundException e) {
119 Generator generator = new Generator(loader, interfaceClass);
120 return generator.generate();
121 }
122 }
123
124 static private String proxyName(Class<?> clazz) {
125 return clazz.getName()+"$__ACTOR_PROXY__";
126 }
127
128 private static final class Generator<T> implements Opcodes {
129
130 private static final String RUNNABLE = "java/lang/Runnable";
131 private static final String OBJECT_CLASS = "java/lang/Object";
132 private static final String DISPATCH_QUEUE = DispatchQueue.class.getName().replace('.','/');
133
134 private final ClassLoader loader;
135 private Method defineClassMethod;
136
137 private final Class<T> interfaceClass;
138 private String proxyName;
139 private String interfaceName;
140
141 private Generator(ClassLoader loader, Class<T> interfaceClass) throws RuntimeException {
142 this.loader = loader;
143 this.interfaceClass = interfaceClass;
144 this.proxyName = proxyName(interfaceClass).replace('.', '/');
145 this.interfaceName = interfaceClass.getName().replace('.','/');
146
147 try {
148 defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
149 defineClassMethod.setAccessible(true);
150 } catch (Throwable e) {
151 throw new RuntimeException("Could not access the 'java.lang.ClassLoader.defineClass' method due to: "+e.getMessage(), e);
152 }
153 }
154
155 private Class<T> generate() throws IllegalArgumentException {
156
157 // Define all the runnable classes used for each method.
158 Method[] methods = interfaceClass.getMethods();
159 for (int index = 0; index < methods.length; index++) {
160 String name = runnable(index, methods[index]).replace('/', '.');
161 byte[] clazzBytes = dumpRunnable(index, methods[index]);
162 defineClass(name, clazzBytes);
163 }
164
165 String name = proxyName.replace('/', '.');
166 byte[] clazzBytes = dumpProxy(methods);
167 return defineClass(name, clazzBytes);
168 }
169
170 @SuppressWarnings("unchecked")
171 private Class<T> defineClass(String name, byte[] classBytes) throws RuntimeException {
172 try {
173 return (Class<T>) defineClassMethod.invoke(loader, new Object[] {name, classBytes, 0, classBytes.length});
174 } catch (IllegalAccessException e) {
175 throw new RuntimeException("Could not define the generated class due to: "+e.getMessage(), e);
176 } catch (InvocationTargetException e) {
177 throw new RuntimeException("Could not define the generated class due to: "+e.getMessage(), e);
178 }
179 }
180
181 public byte[] dumpProxy(Method[] methods) {
182 ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
183 FieldVisitor fv;
184 MethodVisitor mv;
185 Label start, end;
186
187 // example:
188 cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, proxyName, null, OBJECT_CLASS, new String[] { interfaceName });
189 {
190 // example:
191 fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "queue", sig(DISPATCH_QUEUE), null, null);
192 fv.visitEnd();
193
194 // example:
195 fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "target", sig(interfaceName), null, null);
196 fv.visitEnd();
197
198 // example: public PizzaServiceCustomProxy(IPizzaService target, DispatchQueue queue)
199 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(" + sig(interfaceName) + sig(DISPATCH_QUEUE) + ")V", null, null);
200 {
201 mv.visitCode();
202
203 // example: super();
204 start = new Label();
205 mv.visitLabel(start);
206 mv.visitVarInsn(ALOAD, 0);
207 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS, "<init>", "()V");
208
209 // example: queue=queue;
210 mv.visitVarInsn(ALOAD, 0);
211 mv.visitVarInsn(ALOAD, 2);
212 mv.visitFieldInsn(PUTFIELD, proxyName, "queue", sig(DISPATCH_QUEUE));
213
214 // example: this.target=target;
215 mv.visitVarInsn(ALOAD, 0);
216 mv.visitVarInsn(ALOAD, 1);
217 mv.visitFieldInsn(PUTFIELD, proxyName, "target", sig(interfaceName));
218
219 // example: return;
220 mv.visitInsn(RETURN);
221
222 end = new Label();
223 mv.visitLabel(end);
224 mv.visitLocalVariable("this", sig(proxyName), null, start, end, 0);
225 mv.visitLocalVariable("target", sig(interfaceName), null, start, end, 1);
226 mv.visitLocalVariable("queue", sig(DISPATCH_QUEUE), null, start, end, 2);
227 mv.visitMaxs(2, 3);
228 }
229 mv.visitEnd();
230
231 for (int index = 0; index < methods.length; index++) {
232 Method method = methods[index];
233
234 Class<?>[] params = method.getParameterTypes();
235 Type[] types = Type.getArgumentTypes(method);
236
237 String methodSig = Type.getMethodDescriptor(method);
238
239 // example: public void order(final long count)
240 mv = cw.visitMethod(ACC_PUBLIC, method.getName(), methodSig, null, null);
241 {
242 mv.visitCode();
243
244 // example: queue.execute(new OrderRunnable(target, count));
245 start = new Label();
246 mv.visitLabel(start);
247 mv.visitVarInsn(ALOAD, 0);
248 mv.visitFieldInsn(GETFIELD, proxyName, "queue", sig(DISPATCH_QUEUE));
249 mv.visitTypeInsn(NEW, runnable(index, methods[index]));
250 mv.visitInsn(DUP);
251 mv.visitVarInsn(ALOAD, 0);
252 mv.visitFieldInsn(GETFIELD, proxyName, "target", sig(interfaceName));
253
254 for (int i = 0; i < params.length; i++) {
255 mv.visitVarInsn(types[i].getOpcode(ILOAD), 1+i);
256 }
257
258 mv.visitMethodInsn(INVOKESPECIAL, runnable(index, methods[index]), "<init>", "(" + sig(interfaceName) + sig(params) +")V");
259 mv.visitMethodInsn(INVOKEINTERFACE, DISPATCH_QUEUE, "execute", "(" + sig(RUNNABLE) + ")V");
260
261 Type returnType = Type.getType(method.getReturnType());
262 Integer returnValue = defaultConstant(returnType);
263 if( returnValue!=null ) {
264 mv.visitInsn(returnValue);
265 }
266 mv.visitInsn(returnType.getOpcode(IRETURN));
267
268 end = new Label();
269 mv.visitLabel(end);
270 mv.visitLocalVariable("this", sig(proxyName), null, start, end, 0);
271 for (int i = 0; i < params.length; i++) {
272 mv.visitLocalVariable("param"+i, sig(params[i]), null, start, end, 1+i);
273 }
274 mv.visitMaxs(0, 0);
275 }
276 mv.visitEnd();
277 }
278 }
279 cw.visitEnd();
280
281 return cw.toByteArray();
282 }
283
284 private Integer defaultConstant(Type returnType) {
285 Integer value=null;
286 switch(returnType.getSort()) {
287 case BOOLEAN:
288 case CHAR:
289 case BYTE:
290 case SHORT:
291 case INT:
292 value = ICONST_0;
293 break;
294 case Type.LONG:
295 value = LCONST_0;
296 break;
297 case Type.FLOAT:
298 value = FCONST_0;
299 break;
300 case Type.DOUBLE:
301 value = DCONST_0;
302 break;
303 case ARRAY:
304 case OBJECT:
305 value = ACONST_NULL;
306 }
307 return value;
308 }
309
310 public byte[] dumpRunnable(int index, Method method) {
311
312 ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
313 FieldVisitor fv;
314 MethodVisitor mv;
315 Label start, end;
316
317 // example: final class OrderRunnable implements Runnable
318 String runnableClassName = runnable(index, method);
319 cw.visit(V1_4, ACC_FINAL + ACC_SUPER, runnableClassName, null, OBJECT_CLASS, new String[] { RUNNABLE });
320 {
321
322 // example: private final IPizzaService target;
323 fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "target", sig(interfaceName), null, null);
324 fv.visitEnd();
325
326 // TODO.. add field for each method parameter
327 // example: private final long count;
328
329 Class<?>[] params = method.getParameterTypes();
330 Type[] types = Type.getArgumentTypes(method);
331
332 for (int i = 0; i < params.length; i++) {
333 fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "param"+i, sig(params[i]), null, null);
334 fv.visitEnd();
335 }
336
337 // example: public OrderRunnable(IPizzaService target, long count)
338 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(" + sig(interfaceName)+sig(params)+")V", null, null);
339 {
340 mv.visitCode();
341
342 // example: super();
343 start = new Label();
344 mv.visitLabel(start);
345 mv.visitVarInsn(ALOAD, 0);
346 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS, "<init>", "()V");
347
348 // example: this.target = target;
349 mv.visitVarInsn(ALOAD, 0);
350 mv.visitVarInsn(ALOAD, 1);
351 mv.visitFieldInsn(PUTFIELD, runnableClassName, "target", sig(interfaceName));
352
353 // example: this.count = count;
354 for (int i = 0; i < params.length; i++) {
355
356 // TODO: figure out how to do the right loads. it varies with the type.
357 mv.visitVarInsn(ALOAD, 0);
358 mv.visitVarInsn(types[i].getOpcode(ILOAD), 2+i);
359 mv.visitFieldInsn(PUTFIELD, runnableClassName, "param"+i, sig(params[i]));
360
361 }
362
363 // example: return;
364 mv.visitInsn(RETURN);
365
366 end = new Label();
367 mv.visitLabel(end);
368 mv.visitLocalVariable("this", sig(runnableClassName), null, start, end, 0);
369 mv.visitLocalVariable("target", sig(interfaceName), null, start, end, 1);
370
371 for (int i = 0; i < params.length; i++) {
372 mv.visitLocalVariable("param"+i, sig(params[i]), null, start, end, 2+i);
373 }
374 mv.visitMaxs(0, 0);
375 }
376 mv.visitEnd();
377
378 // example: public void run()
379 mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
380 {
381 mv.visitCode();
382
383 // example: target.order(count);
384 start = new Label();
385 mv.visitLabel(start);
386 mv.visitVarInsn(ALOAD, 0);
387 mv.visitFieldInsn(GETFIELD, runnableClassName, "target", sig(interfaceName));
388
389 for (int i = 0; i < params.length; i++) {
390 mv.visitVarInsn(ALOAD, 0);
391 mv.visitFieldInsn(GETFIELD, runnableClassName, "param"+i, sig(params[i]));
392 }
393
394 String methodSig = Type.getMethodDescriptor(method);
395 mv.visitMethodInsn(INVOKEINTERFACE, interfaceName, method.getName(), methodSig);
396
397 Type returnType = Type.getType(method.getReturnType());
398 if( returnType != VOID_TYPE ) {
399 mv.visitInsn(POP);
400 }
401
402 // example: return;
403 mv.visitInsn(RETURN);
404
405 end = new Label();
406 mv.visitLabel(end);
407 mv.visitLocalVariable("this", sig(runnableClassName), null, start, end, 0);
408 mv.visitMaxs(0, 0);
409 }
410 mv.visitEnd();
411 }
412 cw.visitEnd();
413
414 return cw.toByteArray();
415 }
416
417
418 private String sig(Class<?>[] params) {
419 StringBuilder methodSig = new StringBuilder();
420 for (int i = 0; i < params.length; i++) {
421 methodSig.append(sig(params[i]));
422 }
423 return methodSig.toString();
424 }
425
426 private String sig(Class<?> clazz) {
427 return Type.getDescriptor(clazz);
428 }
429
430 private String runnable(int index, Method method) {
431 return proxyName+"$"+index+"$"+method.getName();
432 }
433
434 private String sig(String name) {
435 return "L"+name+";";
436 }
437 }
438 }