/**************************************************************************************
* 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;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.CodeVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Constants;
import org.objectweb.asm.CodeAdapter;
import org.objectweb.asm.Type;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.transform.Context;
import org.codehaus.aspectwerkz.transform.TransformationUtil;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
/**
* Instruments method CALL join points by replacing INVOKEXXX instructions with invocations of the compiled join point.
* <br/>
* It 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 MethodCallClassAdapter extends ClassAdapter implements Constants, TransformationConstants {
private static final String INVOKE_METHOD_NAME = "invoke";
private final ContextImpl m_ctx;
private final ClassLoader m_loader;
private final ClassInfo m_callerClassInfo;
/**
* Creates a new instance.
*
* @param cv
* @param loader
* @param classInfo
* @param ctx
*/
public MethodCallClassAdapter(final ClassVisitor cv,
final ClassLoader loader,
final ClassInfo classInfo,
final Context ctx) {
super(cv);
m_loader = loader;
m_callerClassInfo = classInfo;
m_ctx = (ContextImpl) ctx;
}
/**
* Visits the caller methods.
*
* @param access
* @param name
* @param desc
* @param exceptions
* @param attrs
* @return
*/
public CodeVisitor visitMethod(final int access,
final String name,
final String desc,
final String[] exceptions,
final Attribute attrs) {
if (INIT_METHOD_NAME.equals(name) || CLINIT_METHOD_NAME.equals(name)) {
return super.visitMethod(access, name, desc, exceptions, attrs);
}
CodeVisitor mv = cv.visitMethod(access, name, desc, exceptions, attrs);
return mv == null ? null : new ReplaceInvokeInstructionCodeAdapter(
mv,
m_loader,
m_callerClassInfo,
m_ctx.getClassName(),
name,
desc
);
}
/**
* Replaces 'INVOKEXXX' instructions with a call to the compiled JoinPoint instance.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
*/
public class ReplaceInvokeInstructionCodeAdapter extends CodeAdapter {
private final ClassLoader m_loader;
private final ClassInfo m_callerClassInfo;
private final String m_callerClassName;
private final String m_callerMethodName;
private final String m_callerMethodDesc;
private final MethodInfo m_callerMethodInfo;
private int m_sequence = 1;
/**
* Creates a new instance.
*
* @param ca
* @param loader
* @param callerClassInfo
* @param callerClassName
* @param callerMethodName
* @param callerMethodDesc
*/
public ReplaceInvokeInstructionCodeAdapter(final CodeVisitor ca,
final ClassLoader loader,
final ClassInfo callerClassInfo,
final String callerClassName,
final String callerMethodName,
final String callerMethodDesc) {
super(ca);
m_loader = loader;
m_callerClassInfo = callerClassInfo;
m_callerClassName = callerClassName;
m_callerMethodName = callerMethodName;
m_callerMethodDesc = callerMethodDesc;
m_callerMethodInfo = m_callerClassInfo.getMethod(
AsmHelper.calculateMethodHash(
m_callerMethodName,
m_callerMethodDesc
)
);
if (m_callerMethodInfo == null) {
throw new Error(
"caller method info metadata structure could not be build for method: "
+ callerClassName
+ '.'
+ callerMethodName
+ ':'
+ callerMethodDesc
);
}
}
/**
* Visits 'INVOKEXXX' instructions.
*
* @param opcode
* @param calleeClassName
* @param calleeMethodName
* @param calleeMethodDesc
*/
public void visitMethodInsn(final int opcode,
final String calleeClassName,
final String calleeMethodName,
final String calleeMethodDesc) {
if (INIT_METHOD_NAME.equals(calleeMethodName) ||
CLINIT_METHOD_NAME.equals(calleeMethodName) ||
calleeMethodName.startsWith(TransformationUtil.ASPECTWERKZ_PREFIX) ||
calleeClassName.endsWith(JoinPointCompiler.JOIN_POINT_CLASS_SUFFIX)) {
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
return;
}
int joinPointHash = AsmHelper.calculateMethodHash(calleeMethodName, calleeMethodDesc);
ClassInfo classInfo = AsmClassInfo.getClassInfo(calleeClassName.replace('/', '.'), m_loader);
MethodInfo calleeMethodInfo = classInfo.getMethod(joinPointHash);
if (calleeMethodInfo == null) {
// lookup in the class hierarchy
ClassInfo superClassInfo = classInfo.getSuperClass();
while (superClassInfo != null) {
calleeMethodInfo = superClassInfo.getMethod(joinPointHash);
if (calleeMethodInfo == null) {
// go up in the hierarchy
superClassInfo = superClassInfo.getSuperClass();
} else {
break;
}
}
if (calleeMethodInfo == null) {
throw new Error(
"callee method info metadata structure could not be build for method: "
+ calleeClassName
+ '.'
+ calleeMethodName
+ ':'
+ calleeMethodDesc
);
}
}
ExpressionContext ctx = new ExpressionContext(PointcutType.CALL, calleeMethodInfo, m_callerMethodInfo);
if (methodFilter(m_ctx.getDefinitions(), ctx, calleeMethodInfo)) {
super.visitMethodInsn(opcode, calleeClassName, calleeMethodName, calleeMethodDesc);
} else {
m_ctx.markAsAdvised();
m_sequence++; // single place of incrementation, is used in multiple places
// TODO is caller class sufficient in the name or do we need callee classname?
String joinPointClassName = JoinPointCompiler.getJoinPointClassName(
m_callerClassName,
JoinPointType.METHOD_CALL,
joinPointHash
);
// load the caller instance (this), or null if in a static context
// note that callee instance [optional] and args are already on the stack
if (Modifier.isStatic(m_callerMethodInfo.getModifiers())) {
visitInsn(ACONST_NULL);
} else {
visitVarInsn(ALOAD, 0);
}
// add the call to the join point
super.visitMethodInsn(
INVOKESTATIC,
joinPointClassName,
INVOKE_METHOD_NAME,
AsmHelper.getInvokeSignatureForCodeJoinPoints(
calleeMethodInfo.getModifiers(), calleeMethodDesc, m_callerClassName, calleeClassName
)
);
// emit the joinpoint
m_ctx.addEmittedInlinedJoinPoint(
new ContextImpl.EmittedInlinedJoinPoint(
JoinPointType.METHOD_CALL,
m_callerClassName,
m_callerMethodName,
m_callerMethodDesc,
m_callerMethodInfo.getModifiers(),
calleeClassName,
calleeMethodName,
calleeMethodDesc,
calleeMethodInfo.getModifiers(),
m_sequence,
joinPointHash,
joinPointClassName
)
);
}
}
/**
* Filters out the methods that are not eligible for transformation.
*
* @param definitions
* @param ctx
* @param calleeMethodInfo
* @return boolean true if the method should be filtered out
*/
public boolean methodFilter(final List definitions,
final ExpressionContext ctx,
final MethodInfo calleeMethodInfo) {
if (Modifier.isAbstract(calleeMethodInfo.getModifiers())
|| Modifier.isNative(calleeMethodInfo.getModifiers())
|| calleeMethodInfo.getName().equals(INIT_METHOD_NAME)
|| calleeMethodInfo.getName().equals(CLINIT_METHOD_NAME)
|| calleeMethodInfo.getName().startsWith(TransformationUtil.ORIGINAL_METHOD_PREFIX)) {
return true;
}
for (Iterator it = definitions.iterator(); it.hasNext();) {
if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
return false;
} else {
continue;
}
}
return true;
}
}
}