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}