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 018package org.fusesource.hawtdispatch; 019 020import java.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023 024import org.fusesource.hawtdispatch.DispatchQueue; 025import org.objectweb.asm.ClassWriter; 026import org.objectweb.asm.FieldVisitor; 027import org.objectweb.asm.Label; 028import org.objectweb.asm.MethodVisitor; 029import org.objectweb.asm.Opcodes; 030import org.objectweb.asm.Type; 031 032import static org.objectweb.asm.Type.*; 033 034import 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 */ 072public 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}