/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.proxy;
import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
import org.codehaus.aspectwerkz.transform.inlining.AsmNullAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
/**
* Compile a proxy class for the delegation strategy.
*
* All interfaces methods are taken in the given interface order and implemented using delegation.
* A single constructor is compiled wich accept each interface as argument
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
public class ProxyDelegationCompiler {
/**
* Compile the proxy
*
* @param loader
* @param interfaces
* @param proxyClassName
* @return
*/
public static byte[] compileProxyFor(final ClassLoader loader, final Class[] interfaces, final String proxyClassName) {
final ClassWriter proxyWriter = AsmHelper.newClassWriter(true);
final Set methodSignatures = new HashSet();
final String[] interfaceClassNames = new String[interfaces.length];
final String[] interfaceSignatures = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
interfaceClassNames[i] = interfaces[i].getName().replace('.', '/');
interfaceSignatures[i] = 'L' + interfaceClassNames[i] + ';';
}
//FIXME copy interfaces class level annotations, and make sure we ignore doublons if any
ProxyCompilerClassVisitor createProxy = new ProxyDelegationCompiler.ProxyCompilerClassVisitor(
proxyWriter,
proxyClassName.replace('.', '/'),
methodSignatures,
interfaceClassNames,
interfaceSignatures
);
// visit each interface
for (int i = 0; i < interfaces.length; i++) {
Class anInterface = interfaces[i];
final String interfaceClassName = anInterface.getName().replace('.', '/');
InputStream in = null;
final ClassReader classReader;
try {
if (loader != null) {
in = loader.getResourceAsStream(interfaceClassName + ".class");
} else {
in = ClassLoader.getSystemClassLoader().getResourceAsStream(interfaceClassName + ".class");
}
classReader = new ClassReader(in);
} catch (IOException e) {
throw new WrappedRuntimeException("Cannot compile proxy for " + anInterface, e);
} finally {
try {
in.close();
} catch (Throwable t) {
;
}
}
classReader.accept(createProxy, true);// no need for debug info
}
return proxyWriter.toByteArray();
}
/**
* Proxy compiler. Ones can call accept as many times as needed.
* visitEnd allow to track the index of the visited interface
*/
public static class ProxyCompilerClassVisitor extends AsmNullAdapter.NullClassAdapter implements Opcodes, TransformationConstants {
final ClassVisitor m_proxyCv;
final String m_proxyClassName;
final Set m_signatures;
private int currentInterfaceIndex = 0;
final String[] m_interfaceClassNames;
final String[] m_interfaceSignatures;
/**
* Create the class, a field per interface, and the single constructor
*
* @param proxyCv
* @param proxyClassName
* @param signatures
* @param interfaceClassNames
*/
public ProxyCompilerClassVisitor(final ClassVisitor proxyCv, final String proxyClassName, final Set signatures, final String[] interfaceClassNames, final String[] interfaceSignatures) {
//super(proxyCv);
m_proxyCv = proxyCv;
m_proxyClassName = proxyClassName;
m_signatures = signatures;
m_interfaceClassNames = interfaceClassNames;
m_interfaceSignatures = interfaceSignatures;
m_proxyCv.visit(
AsmHelper.JAVA_VERSION,
ACC_PUBLIC + ACC_SYNTHETIC + ACC_SUPER,
m_proxyClassName,
null,//FIXME generic is that correct ?
OBJECT_CLASS_NAME,
interfaceClassNames
);
// create one field per implemented interface
for (int i = 0; i < interfaceClassNames.length; i++) {
m_interfaceSignatures[i] = 'L' + interfaceClassNames[i] + ';';
m_proxyCv.visitField(
ACC_PRIVATE + ACC_SYNTHETIC + ACC_FINAL,
"DELEGATE_" + i,
m_interfaceSignatures[i],
null,
null
);
}
// create ctor
StringBuffer ctorDesc = new StringBuffer("(");
for (int i = 0; i < interfaceClassNames.length; i++) {
ctorDesc.append(m_interfaceSignatures[i]);
}
ctorDesc.append(")V");
MethodVisitor init = m_proxyCv.visitMethod(
ACC_PUBLIC + ACC_SYNTHETIC,
INIT_METHOD_NAME,
ctorDesc.toString(),
null,
null
);
init.visitVarInsn(ALOAD, 0);
init.visitMethodInsn(INVOKESPECIAL, OBJECT_CLASS_NAME, INIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE);
for (int i = 0; i < interfaceClassNames.length; i++) {
init.visitVarInsn(ALOAD, 0);
init.visitVarInsn(ALOAD, 1 + i);
init.visitFieldInsn(PUTFIELD, m_proxyClassName, "DELEGATE_" + i, m_interfaceSignatures[i]);
}
init.visitInsn(RETURN);
init.visitMaxs(0, 0);
}
/**
* Implement the interface method by delegating to the corresponding field
*
* @param access
* @param name
* @param desc
* @param signature
* @param exceptions
* @return
*/
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (m_signatures.contains(name + desc)) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
m_signatures.add(name + desc);
MethodVisitor cv = m_proxyCv.visitMethod(
access & ~ACC_ABSTRACT,
name,
desc,
signature,
exceptions
);
cv.visitVarInsn(ALOAD, 0);
cv.visitFieldInsn(
GETFIELD,
m_proxyClassName,
"DELEGATE_" + currentInterfaceIndex,
m_interfaceSignatures[currentInterfaceIndex]
);
AsmHelper.loadArgumentTypes(cv, Type.getArgumentTypes(desc), false);
cv.visitMethodInsn(
INVOKEINTERFACE,
m_interfaceClassNames[currentInterfaceIndex],
name,
desc
);
AsmHelper.addReturnStatement(cv, Type.getReturnType(desc));
cv.visitMaxs(0, 0);
// as we return cv we will copy the interface[currentInterfaceIndex] current method annotations
// which is what we want
return cv;
}
/**
* Update the interface index for the next accept()
*/
public void visitEnd() {
currentInterfaceIndex++;
super.visitEnd();
}
}
}