/**************************************************************************************
* 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 org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.Label;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
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.expression.ExpressionContext;
import org.codehaus.aspectwerkz.expression.PointcutType;
import org.codehaus.aspectwerkz.definition.SystemDefinition;
import org.codehaus.aspectwerkz.joinpoint.management.JoinPointType;
import java.util.Iterator;
import java.util.Set;
/**
* Handles constructor execution weaving.
* For each matching ctor, a static method is added with the same signature and with the extra thisClass parameter
* prepended to the list. Then the orginal ctor body is changed to call the JP.invoke, only after call to this / super
* initializers.
* <p/>
* TODO rename in ..execution..
*
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
*/
public class ConstructorBodyVisitor extends ClassAdapter implements TransformationConstants {
private final ContextImpl m_ctx;
private final ClassInfo m_calleeClassInfo;
private String m_declaringTypeName;
private Set m_addedMethods;
/**
* Creates a new instance.
*
* @param cv
* @param classInfo
* @param ctx
* @param addedMethods
*/
public ConstructorBodyVisitor(final ClassVisitor cv,
final ClassInfo classInfo,
final Context ctx,
final Set addedMethods) {
super(cv);
m_calleeClassInfo = 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);
}
/**
* @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 (!INIT_METHOD_NAME.equals(name)) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
int hash = AsmHelper.calculateConstructorHash(desc);
ConstructorInfo constructorInfo = m_calleeClassInfo.getConstructor(hash);
if (constructorInfo == null) {
System.err.println(
"AW::WARNING " +
"metadata structure could not be build for constructor ["
+ m_calleeClassInfo.getName().replace('/', '.')
+ ".<init>: " + desc + ']'
);
return cv.visitMethod(access, name, desc, signature, exceptions);
}
ExpressionContext ctx = new ExpressionContext(PointcutType.EXECUTION, constructorInfo, constructorInfo);
if (constructorFilter(m_ctx.getDefinitions(), ctx)) {
return cv.visitMethod(access, name, desc, signature, exceptions);
} else {
String wrapperName = TransformationUtil.getConstructorBodyMethodName(m_declaringTypeName);
String wrapperDesc = TransformationUtil.getConstructorBodyMethodSignature(desc, m_declaringTypeName);
if (m_addedMethods.contains(AlreadyAddedMethodAdapter.getMethodKey(wrapperName, wrapperDesc))) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
m_ctx.markAsAdvised();
// create the proxy constructor for the original constructor
MethodVisitor proxyCtorMethodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
// create the ctorBodyMethod for the original constructor body
int modifiers = ACC_SYNTHETIC | ACC_STATIC;
MethodVisitor ctorBodyMethodMethodVisitor = cv.visitMethod(
modifiers, wrapperName, wrapperDesc, signature, exceptions
);
// return a dispatch Code Adapter in between the orginal one and both of them
return new DispatchCtorBodyCodeAdapter(proxyCtorMethodVisitor, ctorBodyMethodMethodVisitor, access, desc);
}
}
/**
* Creates the "proxy constructor" join point invocation body
*
* @param ctorProxy
* @param access
* @param desc
*/
private void insertJoinPointInvoke(final MethodVisitor ctorProxy,
final int access,
final String desc) {
// load "this"
ctorProxy.visitVarInsn(ALOAD, 0);// is too simple f.e. when DUP was used
// load args
AsmHelper.loadArgumentTypes(ctorProxy, Type.getArgumentTypes(desc), false);
// caller = callee
ctorProxy.visitVarInsn(ALOAD, 0);
int joinPointHash = AsmHelper.calculateConstructorHash(desc);
String joinPointClassName = TransformationUtil.getJoinPointClassName(
m_declaringTypeName,
INIT_METHOD_NAME,
desc,
m_declaringTypeName,
JoinPointType.CONSTRUCTOR_EXECUTION_INT,
joinPointHash
);
ctorProxy.visitMethodInsn(
INVOKESTATIC,
joinPointClassName,
INVOKE_METHOD_NAME,
TransformationUtil.getInvokeSignatureForCodeJoinPoints(
access, desc, m_declaringTypeName, m_declaringTypeName
)
);
// emit the joinpoint
m_ctx.addEmittedJoinPoint(
new EmittedJoinPoint(
JoinPointType.CONSTRUCTOR_EXECUTION_INT,
m_declaringTypeName,
TransformationConstants.INIT_METHOD_NAME,
desc,
access,
m_declaringTypeName,
TransformationConstants.INIT_METHOD_NAME,
desc,
access,
joinPointHash,
joinPointClassName,
EmittedJoinPoint.NO_LINE_NUMBER
)
);
}
/**
* Filters out the constructor that are not eligible for transformation.
*
* @param definitions
* @param ctx
* @return boolean true if the constructor should be filtered out
*/
public static boolean constructorFilter(final Set definitions,
final ExpressionContext ctx) {
for (Iterator it = definitions.iterator(); it.hasNext();) {
if (((SystemDefinition) it.next()).hasPointcut(ctx)) {
return false;
} else {
continue;
}
}
return true;
}
/**
* A class that dispatch the ctor body instruction to any other given code visitor
* </p>
* The behavior is like this:
* 1/ as long as the INVOKESPECIAL for the object initialization has not been reached, every bytecode
* instruction is dispatched in the ctor code visitor. [note 1]
* 2/ when this one is reached, it is only added in the ctor code visitor and a JP invoke is added
* 3/ after that, only the other code visitor receives the instructions
* </p>
* [note 1] To support schemes like http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#9839
* where the stack is like ALOAD_0 + DUP, we handle a special case.
* f.e. CGlib proxy ctor are like that..
* Don't know if some other info can be left on the stack (f.e. ILOAD 1, DUP ...)
*/
private class DispatchCtorBodyCodeAdapter extends MethodAdapter {
private MethodVisitor m_ctorBodyMethodMethodVisitor;
private MethodVisitor m_proxyCtorMethodVisitor;
private final int m_constructorAccess;
private final String m_constructorDesc;
private int m_newCount = 0;
private boolean m_proxyCtorCodeDone = false;
private boolean m_isALOADDUPHeuristic = false;
private int m_index = -1;
public DispatchCtorBodyCodeAdapter(MethodVisitor proxyCtor, MethodVisitor ctorBodyMethod, final int access,
final String desc) {
super(proxyCtor);
m_proxyCtorMethodVisitor = proxyCtor;
m_ctorBodyMethodMethodVisitor = ctorBodyMethod;
m_constructorAccess = access;
m_constructorDesc = desc;
}
public void visitInsn(int opcode) {
super.visitInsn(opcode);
if (!m_proxyCtorCodeDone && opcode == DUP && m_index == 0) {
// heuristic for ALOAD_0 + DUP confirmed
m_isALOADDUPHeuristic = true;
m_index++;
}
}
public void visitIntInsn(int i, int i1) {
super.visitIntInsn(i, i1);
}
public void visitVarInsn(int opcode, int i1) {
super.visitVarInsn(opcode, i1);
if (!m_proxyCtorCodeDone) {
if (opcode == ALOAD && i1 == 0) {
m_index++;
}
}
}
public void visitFieldInsn(int i, String s, String s1, String s2) {
super.visitFieldInsn(i, s, s1, s2);
}
public void visitLdcInsn(Object o) {
super.visitLdcInsn(o);
}
public void visitIincInsn(int i, int i1) {
super.visitIincInsn(i, i1);
}
public void visitMultiANewArrayInsn(String s, int i) {
super.visitMultiANewArrayInsn(s, i);
}
/**
* Visit NEW type to ignore corresponding INVOKESPECIAL for those
*
* @param opcode
* @param name
*/
public void visitTypeInsn(int opcode, String name) {
super.visitTypeInsn(opcode, name);
if (opcode == NEW) {
m_newCount++;
}
}
public void visitMethodInsn(int opcode,
String owner,
String name,
String desc) {
if (!m_proxyCtorCodeDone) {
if (opcode == INVOKESPECIAL) {
if (m_newCount == 0) {
// first INVOKESPECIAL encountered to <init> for a NON new XXX()
m_proxyCtorMethodVisitor.visitMethodInsn(opcode, owner, name, desc);
// insert the JoinPoint invocation
insertJoinPointInvoke(m_proxyCtorMethodVisitor, m_constructorAccess, m_constructorDesc);
m_proxyCtorMethodVisitor.visitInsn(RETURN);
m_proxyCtorMethodVisitor.visitMaxs(0, 0);
m_proxyCtorMethodVisitor = null;
m_proxyCtorCodeDone = true;
mv = m_ctorBodyMethodMethodVisitor;
// load ALOAD 0 if under heuristic
if (m_isALOADDUPHeuristic) {
m_ctorBodyMethodMethodVisitor.visitVarInsn(ALOAD, 0);
}
} else {
m_newCount--;
mv.visitMethodInsn(opcode, owner, name, desc);
}
} else {
mv.visitMethodInsn(opcode, owner, name, desc);
}
} else {
mv.visitMethodInsn(opcode, owner, name, desc);
}
}
}
}