org.fusesource.hawtdispatch.DispatchQueueProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hawtdispatch Show documentation
Show all versions of hawtdispatch Show documentation
HawtDispatch: The libdispatch style API for Java
/**
* Copyright (C) 2009-2010, Progress Software Corporation and/or its
* subsidiaries or affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.hawtdispatch;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import static org.objectweb.asm.Type.*;
import static org.objectweb.asm.ClassWriter.*;
/**
*
* This class creates proxy objects that allow you to easily service all
* method calls to an interface via a {@link DispatchQueue}.
*
* The general idea is that proxy asynchronously invoke the delegate using
* the dispatch queue. The proxy implementation is generated using ASM
* using the following code generation pattern:
*
*
* class <>$__ACTOR_PROXY__ implements <> {
*
* private final <> target;
* private final DispatchQueue queue;
*
* public <>$__ACTOR_PROXY__(<> target, DispatchQueue queue) {
* this.target = target;
* this.queue = queue;
* }
*
* <>
*
* <> {
* queue.execute( new Runnable() {
* public void run() {
* this.target.<>;
* }
* } );
* }
* <>
* }
*
*
*
* @author Hiram Chirino
*/
public class DispatchQueueProxy {
/**
* Create an asynchronous dispatch proxy to the target object via the dispatch queue. The
* class loader of the generated proxy will be the same as the class loader of the target
* object.
*
* @param interfaceClass the interface that will be implemented by the proxy
* @param target the delegate object the proxy will asynchronously invoke
* @param queue the dispatch queue that the asynchronous runnables will execute on
* @param the type of the interface class
* @return the new asynchronous dispatch proxy
* @throws IllegalArgumentException
*/
public static T create(Class interfaceClass, T target, DispatchQueue queue) throws IllegalArgumentException {
return create(target.getClass().getClassLoader(), interfaceClass, target, queue);
}
/**
* Create an asynchronous dispatch proxy to the target object via the dispatch queue.
*
* @param classLoader the classloader which the proxy class should use
* @param interfaceClass the interface that will be implemented by the proxy
* @param target the delegate object the proxy will asynchronously invoke
* @param queue the dispatch queue that the asyncronous runnables will execute on
* @param the type of the interface asynchronous
* @return the new asynchronous dispatch proxy
* @throws IllegalArgumentException
*/
synchronized public static T create(ClassLoader classLoader, Class interfaceClass, T target, DispatchQueue queue) throws IllegalArgumentException {
Class proxyClass = getProxyClass(classLoader, interfaceClass);
Constructor constructor = proxyClass.getConstructors()[0];
Object rc;
try {
rc = constructor.newInstance(new Object[]{target, queue});
} catch (Throwable e) {
throw new RuntimeException("Could not create an instance of the proxy due to: "+e.getMessage(), e);
}
return proxyClass.cast(rc);
}
@SuppressWarnings("unchecked")
private static Class getProxyClass(ClassLoader loader, Class interfaceClass) throws IllegalArgumentException {
String proxyName = proxyName(interfaceClass);
try {
return (Class) loader.loadClass(proxyName);
} catch (ClassNotFoundException e) {
Generator generator = new Generator(loader, interfaceClass);
return generator.generate();
}
}
static private String proxyName(Class clazz) {
return clazz.getName()+"$__ACTOR_PROXY__";
}
private static final class Generator implements Opcodes {
private static final String RUNNABLE = "java/lang/Runnable";
private static final String OBJECT_CLASS = "java/lang/Object";
private static final String DISPATCH_QUEUE = DispatchQueue.class.getName().replace('.','/');
private final ClassLoader loader;
private Method defineClassMethod;
private final Class interfaceClass;
private String proxyName;
private String interfaceName;
private Generator(ClassLoader loader, Class interfaceClass) throws RuntimeException {
this.loader = loader;
this.interfaceClass = interfaceClass;
this.proxyName = proxyName(interfaceClass).replace('.', '/');
this.interfaceName = interfaceClass.getName().replace('.','/');
try {
defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class, int.class, int.class });
defineClassMethod.setAccessible(true);
} catch (Throwable e) {
throw new RuntimeException("Could not access the 'java.lang.ClassLoader.defineClass' method due to: "+e.getMessage(), e);
}
}
private Class generate() throws IllegalArgumentException {
// Define all the runnable classes used for each method.
Method[] methods = interfaceClass.getMethods();
for (int index = 0; index < methods.length; index++) {
String name = runnable(index, methods[index]).replace('/', '.');
byte[] clazzBytes = dumpRunnable(index, methods[index]);
defineClass(name, clazzBytes);
}
String name = proxyName.replace('/', '.');
byte[] clazzBytes = dumpProxy(methods);
return defineClass(name, clazzBytes);
}
@SuppressWarnings("unchecked")
private Class defineClass(String name, byte[] classBytes) throws RuntimeException {
try {
return (Class) defineClassMethod.invoke(loader, new Object[] {name, classBytes, 0, classBytes.length});
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not define the generated class due to: "+e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Could not define the generated class due to: "+e.getMessage(), e);
}
}
public byte[] dumpProxy(Method[] methods) {
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
FieldVisitor fv;
MethodVisitor mv;
Label start, end;
// example:
cw.visit(V1_4, ACC_PUBLIC + ACC_SUPER, proxyName, null, OBJECT_CLASS, new String[] { interfaceName });
{
// example:
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "queue", sig(DISPATCH_QUEUE), null, null);
fv.visitEnd();
// example:
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "target", sig(interfaceName), null, null);
fv.visitEnd();
// example: public PizzaServiceCustomProxy(IPizzaService target, DispatchQueue queue)
mv = cw.visitMethod(ACC_PUBLIC, "", "(" + sig(interfaceName) + sig(DISPATCH_QUEUE) + ")V", null, null);
{
mv.visitCode();
// example: super();
start = new Label();
mv.visitLabel(start);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS, "", "()V");
// example: queue=queue;
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 2);
mv.visitFieldInsn(PUTFIELD, proxyName, "queue", sig(DISPATCH_QUEUE));
// example: this.target=target;
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, proxyName, "target", sig(interfaceName));
// example: return;
mv.visitInsn(RETURN);
end = new Label();
mv.visitLabel(end);
mv.visitLocalVariable("this", sig(proxyName), null, start, end, 0);
mv.visitLocalVariable("target", sig(interfaceName), null, start, end, 1);
mv.visitLocalVariable("queue", sig(DISPATCH_QUEUE), null, start, end, 2);
mv.visitMaxs(2, 3);
}
mv.visitEnd();
for (int index = 0; index < methods.length; index++) {
Method method = methods[index];
Class[] params = method.getParameterTypes();
Type[] types = Type.getArgumentTypes(method);
String methodSig = Type.getMethodDescriptor(method);
// example: public void order(final long count)
mv = cw.visitMethod(ACC_PUBLIC, method.getName(), methodSig, null, null);
{
mv.visitCode();
// example: queue.execute(new OrderRunnable(target, count));
start = new Label();
mv.visitLabel(start);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, proxyName, "queue", sig(DISPATCH_QUEUE));
mv.visitTypeInsn(NEW, runnable(index, methods[index]));
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, proxyName, "target", sig(interfaceName));
for (int i = 0; i < params.length; i++) {
mv.visitVarInsn(types[i].getOpcode(ILOAD), 1+i);
}
mv.visitMethodInsn(INVOKESPECIAL, runnable(index, methods[index]), "", "(" + sig(interfaceName) + sig(params) +")V");
mv.visitMethodInsn(INVOKEINTERFACE, DISPATCH_QUEUE, "execute", "(" + sig(RUNNABLE) + ")V");
Type returnType = Type.getType(method.getReturnType());
Integer returnValue = defaultConstant(returnType);
if( returnValue!=null ) {
mv.visitInsn(returnValue);
}
mv.visitInsn(returnType.getOpcode(IRETURN));
end = new Label();
mv.visitLabel(end);
mv.visitLocalVariable("this", sig(proxyName), null, start, end, 0);
for (int i = 0; i < params.length; i++) {
mv.visitLocalVariable("param"+i, sig(params[i]), null, start, end, 1+i);
}
mv.visitMaxs(0, 0);
}
mv.visitEnd();
}
}
cw.visitEnd();
return cw.toByteArray();
}
private Integer defaultConstant(Type returnType) {
Integer value=null;
switch(returnType.getSort()) {
case BOOLEAN:
case CHAR:
case BYTE:
case SHORT:
case INT:
value = ICONST_0;
break;
case Type.LONG:
value = LCONST_0;
break;
case Type.FLOAT:
value = FCONST_0;
break;
case Type.DOUBLE:
value = DCONST_0;
break;
case ARRAY:
case OBJECT:
value = ACONST_NULL;
}
return value;
}
public byte[] dumpRunnable(int index, Method method) {
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
FieldVisitor fv;
MethodVisitor mv;
Label start, end;
// example: final class OrderRunnable implements Runnable
String runnableClassName = runnable(index, method);
cw.visit(V1_4, ACC_FINAL + ACC_SUPER, runnableClassName, null, OBJECT_CLASS, new String[] { RUNNABLE });
{
// example: private final IPizzaService target;
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "target", sig(interfaceName), null, null);
fv.visitEnd();
// TODO.. add field for each method parameter
// example: private final long count;
Class[] params = method.getParameterTypes();
Type[] types = Type.getArgumentTypes(method);
for (int i = 0; i < params.length; i++) {
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "param"+i, sig(params[i]), null, null);
fv.visitEnd();
}
// example: public OrderRunnable(IPizzaService target, long count)
mv = cw.visitMethod(ACC_PUBLIC, "", "(" + sig(interfaceName)+sig(params)+")V", null, null);
{
mv.visitCode();
// example: super();
start = new Label();
mv.visitLabel(start);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS, "", "()V");
// example: this.target = target;
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, runnableClassName, "target", sig(interfaceName));
// example: this.count = count;
for (int i = 0; i < params.length; i++) {
// TODO: figure out how to do the right loads. it varies with the type.
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(types[i].getOpcode(ILOAD), 2+i);
mv.visitFieldInsn(PUTFIELD, runnableClassName, "param"+i, sig(params[i]));
}
// example: return;
mv.visitInsn(RETURN);
end = new Label();
mv.visitLabel(end);
mv.visitLocalVariable("this", sig(runnableClassName), null, start, end, 0);
mv.visitLocalVariable("target", sig(interfaceName), null, start, end, 1);
for (int i = 0; i < params.length; i++) {
mv.visitLocalVariable("param"+i, sig(params[i]), null, start, end, 2+i);
}
mv.visitMaxs(0, 0);
}
mv.visitEnd();
// example: public void run()
mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
{
mv.visitCode();
// example: target.order(count);
start = new Label();
mv.visitLabel(start);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, runnableClassName, "target", sig(interfaceName));
for (int i = 0; i < params.length; i++) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, runnableClassName, "param"+i, sig(params[i]));
}
String methodSig = Type.getMethodDescriptor(method);
mv.visitMethodInsn(INVOKEINTERFACE, interfaceName, method.getName(), methodSig);
Type returnType = Type.getType(method.getReturnType());
if( returnType != VOID_TYPE ) {
mv.visitInsn(POP);
}
// example: return;
mv.visitInsn(RETURN);
end = new Label();
mv.visitLabel(end);
mv.visitLocalVariable("this", sig(runnableClassName), null, start, end, 0);
mv.visitMaxs(0, 0);
}
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
private String sig(Class[] params) {
StringBuilder methodSig = new StringBuilder();
for (int i = 0; i < params.length; i++) {
methodSig.append(sig(params[i]));
}
return methodSig.toString();
}
private String sig(Class clazz) {
return Type.getDescriptor(clazz);
}
private String runnable(int index, Method method) {
return proxyName+"$"+index+"$"+method.getName();
}
private String sig(String name) {
return "L"+name+";";
}
}
}