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    }