/**************************************************************************************
* 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.codehaus.aspectwerkz.transform.inlining.AsmCopyAdapter;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
/**
* Compiler for the AspectWerkz proxies.
* <p/>
* Creates a subclass of the target class and adds delegate methods to all the non-private and non-final
* methods/constructors which delegates to the super class.
* <p/>
* The annotations are copied.
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
*/
public class ProxySubclassingCompiler implements TransformationConstants {
// /**
// * Returns an InputStream that would be the one of the AWproxy for the given proxy class name
// * Used to read annotations from proxy f.e.
// *
// * @param loader
// * @param proxyClassName
// * @return or null if not found
// */
// public static InputStream getProxyResourceAsStream(final ClassLoader loader, final String proxyClassName) {
// String className = Proxy.getUniqueClassNameFromProxy(proxyClassName);
// if (className != null) {
// byte[] proxy = compileProxyFor(loader, className, proxyClassName);
// return new BufferedInputStream(new ByteArrayInputStream(proxy));
// } else {
// return null;
// }
// }
/**
* Compiles a new proxy for the class specified.
*
* @param clazz
* @param proxyClassName
* @return the byte code
*/
public static byte[] compileProxyFor(final Class clazz, final String proxyClassName) {
return compileProxyFor(clazz.getClassLoader(), clazz.getName(), proxyClassName);
}
/**
* Compiles a new proxy for the class specified.
*
* @param loader
* @param className
* @param proxyClassName
* @return the byte code
*/
public static byte[] compileProxyFor(final ClassLoader loader, final String className, final String proxyClassName) {
final String targetClassName = className.replace('.', '/');
final ClassWriter proxyWriter = AsmHelper.newClassWriter(true);
InputStream in = null;
final ClassReader classReader;
try {
if (loader != null) {
in = loader.getResourceAsStream(targetClassName + ".class");
} else {
in = ClassLoader.getSystemClassLoader().getResourceAsStream(targetClassName + ".class");
}
classReader = new ClassReader(in);
} catch (IOException e) {
throw new WrappedRuntimeException("Cannot compile proxy for " + className, e);
} finally {
try {
in.close();
} catch (Throwable t) {
;
}
}
ClassVisitor createProxy = new ProxyCompilerClassVisitor(proxyWriter, proxyClassName.replace('.', '/'));
classReader.accept(createProxy, true);// no need for debug info
return proxyWriter.toByteArray();
}
/**
* Visit the class and create the proxy that delegates to super.
*
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
private static class ProxyCompilerClassVisitor extends AsmNullAdapter.NullClassAdapter {
final private ClassVisitor m_proxyCv;
final private String m_proxyClassName;
private String m_className;
public ProxyCompilerClassVisitor(final ClassVisitor proxyCv, final String proxyClassName) {
m_proxyCv = proxyCv;
m_proxyClassName = proxyClassName;
}
/**
* Visits the class.
*
* @param access
* @param name
* @param signature
* @param superName
* @param interfaces
*/
public void visit(final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces) {
if (Modifier.isFinal(access)) {
throw new RuntimeException("Cannot create a proxy from final class " + name);
}
m_className = name;
m_proxyCv.visit(
version,
ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC,
m_proxyClassName.replace('.', '/'),
signature,
name,
interfaces
);
}
/**
* Visits the methods.
*
* @param access
* @param name
* @param desc
* @param signature
* @param exceptions
* @return
*/
public MethodVisitor visitMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions) {
final MethodVisitor proxyCode;
final boolean copyAnnotation;
if (Modifier.isFinal(access) || Modifier.isPrivate(access) || Modifier.isNative(access)) {
// skip final or private or native methods
// TODO we could proxy native methods but that would lead to difference with regular weaving
proxyCode = AsmNullAdapter.NullMethodAdapter.NULL_METHOD_ADAPTER;
copyAnnotation = false;
} else if (CLINIT_METHOD_NAME.equals(name)) {
// skip clinit
proxyCode = AsmNullAdapter.NullMethodAdapter.NULL_METHOD_ADAPTER;
copyAnnotation = false;
} else if (INIT_METHOD_NAME.equals(name)) {
// constructors
proxyCode = m_proxyCv.visitMethod(
access + ACC_SYNTHETIC,
INIT_METHOD_NAME,
desc,
signature,
exceptions
);
proxyCode.visitVarInsn(ALOAD, 0);
AsmHelper.loadArgumentTypes(proxyCode, Type.getArgumentTypes(desc), false);
proxyCode.visitMethodInsn(INVOKESPECIAL, m_className, INIT_METHOD_NAME, desc);
proxyCode.visitInsn(RETURN);
proxyCode.visitMaxs(0, 0);
copyAnnotation = true;
} else {
// method that can be proxied
proxyCode = m_proxyCv.visitMethod(
access + ACC_SYNTHETIC,
name,
desc,
signature,
exceptions
);
if (Modifier.isStatic(access)) {
AsmHelper.loadArgumentTypes(proxyCode, Type.getArgumentTypes(desc), true);
proxyCode.visitMethodInsn(INVOKESTATIC, m_className, name, desc);
AsmHelper.addReturnStatement(proxyCode, Type.getReturnType(desc));
proxyCode.visitMaxs(0, 0);
} else {
proxyCode.visitVarInsn(ALOAD, 0);
AsmHelper.loadArgumentTypes(proxyCode, Type.getArgumentTypes(desc), false);
proxyCode.visitMethodInsn(INVOKESPECIAL, m_className, name, desc);
AsmHelper.addReturnStatement(proxyCode, Type.getReturnType(desc));
proxyCode.visitMaxs(0, 0);
}
copyAnnotation = true;
}
// copy the annotation if necessary
if (copyAnnotation) {
return new AsmCopyAdapter.CopyMethodAnnotationElseNullAdapter(proxyCode);
} else {
return AsmNullAdapter.NullMethodAdapter.NULL_METHOD_ADAPTER;
}
}
/**
* Visit the class annotation (copy)
* @param desc
* @param visible
* @return
*/
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return new AsmCopyAdapter.CopyAnnotationAdapter(
super.visitAnnotation(desc, visible),
m_proxyCv.visitAnnotation(desc, visible)
);
}
/**
* Visit the custom attribute (copy)
* @param attribute
*/
public void visitAttribute(Attribute attribute) {
m_proxyCv.visitAttribute(attribute);
}
}
// /**
// * Creates constructors that delgates to the matching base class constructors.
// * Skips all private constructors.
// *
// * @param writer
// * @param clazz
// * @param targetClassName
// */
// private static void createConstructorDelegators(final ClassWriter writer,
// final Class clazz,
// final String targetClassName) {
// MethodVisitor cv;
// Constructor[] constructors = clazz.getDeclaredConstructors();
// for (int i = 0; i < constructors.length; i++) {
// Constructor constructor = constructors[i];
// int mods = constructor.getModifiers();
// if (!Modifier.isPrivate(mods) && !Modifier.isFinal(mods)) {
// Class[] exceptionClasses = constructor.getExceptionTypes();
// String[] exceptionTypeNames = new String[constructor.getExceptionTypes().length];
// for (int j = 0; j < exceptionTypeNames.length; j++) {
// exceptionTypeNames[j] = exceptionClasses[j].getName().replace('.', '/');
// }
// final String desc = ReflectHelper.getConstructorSignature(constructor);
// cv = writer.visitMethod(
// mods + ACC_SYNTHETIC,
// INIT_METHOD_NAME,
// desc,
// exceptionTypeNames,
// null
// );
//
// cv.visitVarInsn(ALOAD, 0);
// AsmHelper.loadArgumentTypes(cv, Type.getArgumentTypes(desc), false);
//
// cv.visitMethodInsn(INVOKESPECIAL, targetClassName, INIT_METHOD_NAME, desc);
//
// cv.visitInsn(RETURN);
// cv.visitMaxs(0, 0);
// }
// }
// }
//
// /**
// * Creates method methods that delgates to the base class method.
// * Skips all private and final methods.
// *
// * @param writer
// * @param clazz
// * @param targetClassName
// */
// private static void createMethodDelegators(final ClassWriter writer,
// final Class clazz,
// final String targetClassName) {
// MethodVisitor cv;
// Method[] methods = clazz.getDeclaredMethods();
// for (int i = 0; i < methods.length; i++) {
// Method method = methods[i];
// int mods = method.getModifiers();
// if (!Modifier.isPrivate(mods) && !Modifier.isFinal(mods)) {
//
// Class[] exceptionClasses = method.getExceptionTypes();
// String[] exceptionTypeNames = new String[method.getExceptionTypes().length];
// for (int j = 0; j < exceptionTypeNames.length; j++) {
// exceptionTypeNames[j] = exceptionClasses[j].getName().replace('.', '/');
// }
// final String methodName = method.getName();
// final String desc = Type.getMethodDescriptor(method);
//
// cv = writer.visitMethod(
// mods + ACC_SYNTHETIC,
// methodName,
// desc,
// exceptionTypeNames,
// null
// );
//
// if (Modifier.isStatic(mods)) {
// AsmHelper.loadArgumentTypes(cv, Type.getArgumentTypes(desc), true);
// cv.visitMethodInsn(INVOKESTATIC, targetClassName, methodName, desc);
// } else {
// cv.visitVarInsn(ALOAD, 0);
// AsmHelper.loadArgumentTypes(cv, Type.getArgumentTypes(desc), false);
// cv.visitMethodInsn(INVOKESPECIAL, targetClassName, methodName, desc);
// }
//
// AsmHelper.addReturnStatement(cv, Type.getReturnType(method));
// cv.visitMaxs(0, 0);
// }
// }
// }
}