/**************************************************************************************
* 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.transform.inlining.weaver;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.Set;
import org.objectweb.asm.*;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import org.codehaus.aspectwerkz.transform.TransformationConstants;
import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
import org.codehaus.aspectwerkz.transform.inlining.EmittedJoinPoint;
import org.codehaus.aspectwerkz.transform.inlining.AsmCopyAdapter;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.intercept.AdvisableImpl;
/**
* Adds a "proxy method" to the methods that matches an <tt>execution</tt> pointcut as well as prefixing the "original
* method".
* <br/>
* The proxy method calls the JPClass.invoke static method. The signature of the invoke method depends if the
* target method is static or not as follow:
* <pre>
* invoke(callee, args.., caller) // non static
* invoke(args.., caller) // static
* </pre>
* (The reason why is that it simplifies call pointcut stack management)
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
* @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
*/
public class MethodExecutionVisitor extends ClassAdapter implements TransformationConstants {
private final ClassInfo m_classInfo;
private final ContextImpl m_ctx;
private String m_declaringTypeName;
private final Set m_addedMethods;
/**
* Creates a new class adapter.
*
* @param cv
* @param classInfo
* @param ctx
* @param addedMethods
*/
public MethodExecutionVisitor(final ClassVisitor cv,
final ClassInfo classInfo,
final Context ctx,
final Set addedMethods) {
super(cv);
m_classInfo = classInfo;
m_ctx = (ContextImpl) ctx;
m_addedMethods = addedMethods;
}
/**
* 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) {
m_declaringTypeName = name;
super.visit(version, access, name, signature, superName, 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) {
if (INIT_METHOD_NAME.equals(name) ||
CLINIT_METHOD_NAME.equals(name) ||
name.startsWith(ASPECTWERKZ_PREFIX) ||
name.startsWith(SYNTHETIC_MEMBER_PREFIX) ||
name.startsWith(WRAPPER_METHOD_PREFIX) ||
(AdvisableImpl.ADD_ADVICE_METHOD_NAME.equals(name) && AdvisableImpl.ADD_ADVICE_METHOD_DESC.equals(desc)) ||
(AdvisableImpl.REMOVE_ADVICE_METHOD_NAME.equals(name) && AdvisableImpl.REMOVE_ADVICE_METHOD_DESC.equals(desc))) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
int hash = AsmHelper.calculateMethodHash(name, desc);
MethodInfo methodInfo = m_classInfo.getMethod(hash);
if (methodInfo == null) {
System.err.println(
"AW::WARNING " +
"metadata structure could not be build for method ["
+ m_classInfo.getName().replace('/', '.')
+ '.' + name + ':' + desc + ']'
);
// bail out
return cv.visitMethod(access, name, desc, signature, exceptions);
}
ExpressionContext ctx = new ExpressionContext(PointcutType.EXECUTION, methodInfo, methodInfo);
if (methodFilter(m_ctx.getDefinitions(), ctx, methodInfo)) {
return cv.visitMethod(access, name, desc, signature, exceptions);
} else {
String prefixedOriginalName = TransformationUtil.getPrefixedOriginalMethodName(name, m_declaringTypeName);
if (m_addedMethods.contains(AlreadyAddedMethodAdapter.getMethodKey(prefixedOriginalName, desc))) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
m_ctx.markAsAdvised();
// create the proxy for the original method
final MethodVisitor proxyMethod = createProxyMethod(access, name, desc, signature, exceptions, methodInfo);
int modifiers = ACC_SYNTHETIC;
if (Modifier.isStatic(access)) {
modifiers |= ACC_STATIC;
}
// prefix the original method and make sure we copy method annotations to the proxyMethod
// while keeping the body for the prefixed method
return new MethodAdapter(cv.visitMethod(modifiers, prefixedOriginalName, desc, signature, exceptions)) {
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return new AsmCopyAdapter.CopyAnnotationAdapter(
super.visitAnnotation(desc, visible),
proxyMethod.visitAnnotation(desc, visible)
);
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
return new AsmCopyAdapter.CopyAnnotationAdapter(
super.visitParameterAnnotation(parameter, desc, visible),
proxyMethod.visitParameterAnnotation(parameter, desc, visible)
);
}
public void visitAttribute(Attribute attr) {
super.visitAttribute(attr);
proxyMethod.visitAttribute(attr);
}
};
}
}
/**
* Creates the "proxy method", e.g. the method that has the same name and signature as the original method but a
* completely other implementation.
*
* @param access
* @param name
* @param desc
* @param signature
* @param exceptions
* @param methodInfo
* @return the method visitor
*/
private MethodVisitor createProxyMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions,
final MethodInfo methodInfo) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
// load "this" ie callee if target method is not static
if (!Modifier.isStatic(access)) {
mv.visitVarInsn(ALOAD, 0);
}
// load args
AsmHelper.loadArgumentTypes(mv, Type.getArgumentTypes(desc), Modifier.isStatic(access));
// load "this" ie caller or null if method is static
if (Modifier.isStatic(access)) {
mv.visitInsn(ACONST_NULL);
} else {
mv.visitVarInsn(ALOAD, 0);
}
int joinPointHash = AsmHelper.calculateMethodHash(name, desc);
String joinPointClassName = TransformationUtil.getJoinPointClassName(
m_declaringTypeName,
name,
desc,
m_declaringTypeName,
JoinPointType.METHOD_EXECUTION_INT,
joinPointHash
);
// TODO: should we provide some sort of option to do JITgen when weaving instead of when loading ?
// use case: offline full packaging and alike
mv.visitMethodInsn(
INVOKESTATIC,
joinPointClassName,
INVOKE_METHOD_NAME,
TransformationUtil.getInvokeSignatureForCodeJoinPoints(
access, desc, m_declaringTypeName, m_declaringTypeName
)
);
AsmHelper.addReturnStatement(mv, Type.getReturnType(desc));
mv.visitMaxs(0, 0);
// emit the joinpoint
m_ctx.addEmittedJoinPoint(
new EmittedJoinPoint(
JoinPointType.METHOD_EXECUTION_INT,
m_declaringTypeName,
name,
desc,
access,
m_declaringTypeName,
name,
desc,
access,
joinPointHash,
joinPointClassName,
EmittedJoinPoint.NO_LINE_NUMBER
)
);
return mv;
}
/**
* Filters out the methods that are not eligible for transformation.
*
* @param definitions
* @param ctx
* @param methodInfo
* @return boolean true if the method should be filtered out
*/
public static boolean methodFilter(final Set definitions,
final ExpressionContext ctx,
final MethodInfo methodInfo) {
if (Modifier.isAbstract(methodInfo.getModifiers())
|| Modifier.isNative(methodInfo.getModifiers())
|| methodInfo.getName().equals(INIT_METHOD_NAME)
|| methodInfo.getName().equals(CLINIT_METHOD_NAME)
|| methodInfo.getName().startsWith(ORIGINAL_METHOD_PREFIX)) {
return true;
}
for (Iterator it = definitions.iterator(); it.hasNext();) {
if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
return false;
} else {
continue;
}
}
return true;
}
}