/**************************************************************************************
* 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 QPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.transform;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.Type;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.FieldGen;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.PUTSTATIC;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.GETFIELD;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Method;
import org.codehaus.aspectwerkz.metadata.FieldMetaData;
import org.codehaus.aspectwerkz.metadata.BcelMetaDataMaker;
import org.codehaus.aspectwerkz.metadata.ClassMetaData;
import org.codehaus.aspectwerkz.definition.AspectWerkzDefinition;
import org.codehaus.aspectwerkz.definition.DefinitionLoader;
/**
* Transforms member fields to become "aspect-aware".
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
*/
public class AdviseMemberFieldTransformer implements AspectWerkzCodeTransformerComponent {
/**
* The definitions.
*/
private final List m_definitions;
/**
* Retrieves the weave model.
*/
public AdviseMemberFieldTransformer() {
super();
m_definitions = DefinitionLoader.getDefinitionsForTransformation();
}
/**
* Transforms the fields.
*
* @param context the transformation context
* @param klass the class set.
*/
public void transformCode(final Context context, final Klass klass) {
// loop over all the definitions
for (Iterator it = m_definitions.iterator(); it.hasNext();) {
AspectWerkzDefinition definition = (AspectWerkzDefinition)it.next();
definition.loadAspects(context.getLoader());
final ClassGen cg = klass.getClassGen();
ClassMetaData classMetaData = BcelMetaDataMaker.
createClassMetaData(context.getJavaClass(cg));
if (classFilter(definition, classMetaData, cg)) {
return;
}
final Method[] methods = cg.getMethods();
// get the indexes for the <init> methods
List initIndexes = new ArrayList();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals("<init>")) {
initIndexes.add(new Integer(i));
}
}
final ConstantPoolGen cpg = cg.getConstantPool();
final String className = cg.getClassName();
final InstructionFactory factory = new InstructionFactory(cg);
final Set setFieldJoinPoints = new HashSet();
final Set getFieldJoinPoints = new HashSet();
boolean isClassAdvised = false;
boolean isMethodAdvised = false;
for (int i = 0; i < methods.length; i++) {
// filter methods
if (methodFilter(methods[i])) {
continue;
}
isMethodAdvised = false;
MethodGen mg = new MethodGen(methods[i], className, cpg);
// do not modify anything within the hidden system methods
if (mg.getMethod().getName().startsWith(TransformationUtil.ASPECTWERKZ_PREFIX)) {
continue;
}
InstructionList il = mg.getInstructionList();
InstructionHandle ih = il.getStart();
// get the current field instruction
FieldInstruction currentGetFieldIns = null;
// search for all GETFIELD and PUTFIELD instructions and
// inserts the pre and post advices
// handle GETFIELD followed by INVOKEINTERFACE on Collection like carrefully
while (ih != null) {
Instruction ins = ih.getInstruction();
// handle the java.util.Collection classes
if (ins instanceof GETFIELD) {
FieldInstruction checkMe = (FieldInstruction)ins;
// if the field is an added join point field => skip it
// needed if a field of type collection is both setField
// and getField advised
if (!checkMe.getFieldName(cpg).startsWith(TransformationUtil.JOIN_POINT_PREFIX)) {
currentGetFieldIns = checkMe;
Instruction next = ih.getNext().getInstruction();
if (next instanceof INVOKEINTERFACE) {
// handle the INVOKEINTERFACE instruction
final InvokeInstruction invokeIns = (InvokeInstruction)next;
// do we have a collection?
if (invokeIns.getClassName(cpg).equals("java.util.Collection") ||
invokeIns.getClassName(cpg).equals("java.util.Enumeration") ||
invokeIns.getClassName(cpg).equals("java.util.Iterator") ||
invokeIns.getClassName(cpg).equals("java.util.List") ||
invokeIns.getClassName(cpg).equals("java.util.Map") ||
invokeIns.getClassName(cpg).equals("java.util.Set") ||
invokeIns.getClassName(cpg).equals("java.util.SortedMap") ||
invokeIns.getClassName(cpg).equals("java.util.SortedSet")) {
String methodName = invokeIns.getName(cpg);
// is the collection modified (not only accessed or single PUTFIELD instr)?
if (methodName.equals("add") ||
methodName.equals("addAll") ||
methodName.equals("set") ||
methodName.equals("remove") ||
methodName.equals("removeAll") ||
methodName.equals("retainAll") ||
methodName.equals("clear") ||
methodName.equals("put") ||
methodName.equals("putAll")) {
final String fieldName = currentGetFieldIns.getName(cpg);
final String signature = currentGetFieldIns.getFieldType(cpg).
toString() + " " + fieldName;
final Type joinPointType = TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_TYPE;
FieldMetaData fieldMetaData =
BcelMetaDataMaker.createFieldMetaData(currentGetFieldIns, cpg);
String uuid = setFieldFilter(definition, classMetaData, fieldMetaData);
// do we have to set a joinpoint ?
if (uuid != null) {
final String fieldClassName = currentGetFieldIns.getClassName(cpg);
if (fieldClassName.equals(cg.getClassName())) {
isMethodAdvised = true;
insertPreAdvice(
il, ih, cg, fieldName,
factory, joinPointType);
insertPostAdvice(
il, ih.getNext().getNext(), cg,
fieldName, factory, joinPointType);
// store the join point field data
JoinPointFieldData data = new JoinPointFieldData(
fieldName, signature, joinPointType, uuid);
if (!setFieldJoinPoints.contains(data)) {
setFieldJoinPoints.add(data);
}
// add one step more to the InstructionList (GETFIELD(current) INVOKEINTERFACE with jp)
ih = ih.getNext();
ins = ih.getInstruction();
}
}
}
}
}
}
}
// handle the getField instructions
// (if we have set a getfield for collection modification jp the ins has been altered)
if (ins instanceof GETFIELD) {
final FieldInstruction gfIns = (FieldInstruction)ins;
String fieldName = gfIns.getName(cpg);
String signature = gfIns.getFieldType(cpg).toString() + " " + fieldName;
Type joinPointType = TransformationUtil.MEMBER_FIELD_GET_JOIN_POINT_TYPE;
FieldMetaData fieldMetaData = BcelMetaDataMaker.createFieldMetaData(gfIns, cpg);
String uuid = getFieldFilter(definition, classMetaData, fieldMetaData);
if (uuid != null) {
final String fieldClassName = gfIns.getClassName(cpg);
if (fieldClassName.equals(cg.getClassName())) {
isMethodAdvised = true;
insertPreAdvice(
il, ih, cg, fieldName,
factory, joinPointType);
insertPostAdvice(
il, ih.getNext(), cg,
fieldName, factory, joinPointType);
// store the join point field data
JoinPointFieldData data = new JoinPointFieldData(
fieldName, signature, joinPointType, uuid);
if (!getFieldJoinPoints.contains(data)) {
getFieldJoinPoints.add(data);
}
}
}
}
// handle the setField instructions
else if (ins instanceof PUTFIELD) {
final FieldInstruction pfIns = (FieldInstruction)ins;
String fieldName = pfIns.getName(cpg);
String signature = pfIns.getFieldType(cpg).toString() + " " + fieldName;
Type joinPointType = TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_TYPE;
FieldMetaData fieldMetaData =
BcelMetaDataMaker.createFieldMetaData(pfIns, cpg);
String uuid = setFieldFilter(definition, classMetaData, fieldMetaData);
if (uuid != null) {
final String fieldClassName = pfIns.getClassName(cpg);
if (fieldClassName.equals(cg.getClassName())) {
isMethodAdvised = true;
insertPreAdvice(
il, ih, cg, fieldName,
factory, joinPointType);
insertPostAdvice(
il, ih.getNext(), cg,
fieldName, factory, joinPointType);
// store the join point field data
JoinPointFieldData data = new JoinPointFieldData(
fieldName, signature, joinPointType, uuid);
if (!setFieldJoinPoints.contains(data)) {
setFieldJoinPoints.add(data);
}
}
}
}
ih = ih.getNext();
}
if (isMethodAdvised) {
mg.setMaxStack();
methods[i] = mg.getMethod();
isClassAdvised = true;
}
}
// create the set field join point member fields
for (Iterator it2 = setFieldJoinPoints.iterator(); it2.hasNext();) {
JoinPointFieldData data = (JoinPointFieldData)it2.next();
addJoinPointMemberField(cpg, cg, data.getName(), data.getType());
// advise all the constructors
for (Iterator it3 = initIndexes.iterator(); it3.hasNext();) {
final int initIndex = ((Integer)it3.next()).intValue();
methods[initIndex] = createJoinPointMemberField(
cpg, cg, methods[initIndex], factory,
data.getName(), data.getSignature(),
data.getType(), data.getUuid());
}
}
// create the get field join point member fields
for (Iterator it2 = getFieldJoinPoints.iterator(); it2.hasNext();) {
JoinPointFieldData data = (JoinPointFieldData)it2.next();
addJoinPointMemberField(cpg, cg, data.getName(), data.getType());
// advise all the constructors
for (Iterator it3 = initIndexes.iterator(); it3.hasNext();) {
final int initIndex = ((Integer)it3.next()).intValue();
methods[initIndex] = createJoinPointMemberField(
cpg, cg, methods[initIndex], factory,
data.getName(), data.getSignature(),
data.getType(), data.getUuid());
}
}
if (isClassAdvised) {
context.markAsAdvised();
cg.setMethods(methods);
}
}
}
/**
* Adds a new join point member field.
*
* @param cp the ConstantPoolGen
* @param cg the ClassGen
* @param fieldName the name of the field
* @param joinPointType the type of join point
*/
private void addJoinPointMemberField(final ConstantPoolGen cp,
final ClassGen cg,
final String fieldName,
final Type joinPointType) {
final String joinPointPrefix = getJoinPointPrefix(joinPointType);
final StringBuffer joinPoint = getJoinPointName(joinPointPrefix, fieldName);
if (cg.containsField(joinPoint.toString()) != null) {
return;
}
final FieldGen field = new FieldGen(
Constants.ACC_PRIVATE | Constants.ACC_FINAL,
joinPointType,
joinPoint.toString(),
cp);
cg.addField(field.getField());
}
/**
* Creates a join point member field.
*
* @param cp the ConstantPoolGen
* @param cg the ClassGen
* @param init the constructor for the class
* @param factory the objectfactory
* @param fieldName the name of the field
* @param signature the signature of the field
* @param joinPointType the type of join point
* @param uuid the UUID for the weave model
* @return the modified init method
*/
private Method createJoinPointMemberField(final ConstantPoolGen cp,
final ClassGen cg,
final Method init,
final InstructionFactory factory,
final String fieldName,
final String signature,
final Type joinPointType,
final String uuid) {
final String joinPointPrefix = getJoinPointPrefix(joinPointType);
final String joinPointClass = getJoinPointClass(joinPointType);
final MethodGen mg = new MethodGen(init, cg.getClassName(), cp);
final InstructionList il = mg.getInstructionList();
final InstructionHandle[] ihs = il.getInstructionHandles();
// grab the handle to the the call to this(..) or super(..) (if is one),
// otherwise grab the handle to the beginning of the constructor
InstructionHandle ih = ihs[0];
for (int i = 0; i < ihs.length; i++) {
Instruction instruction = ihs[i].getInstruction();
if (instruction instanceof InvokeInstruction) {
InvokeInstruction invokeInstruction = (InvokeInstruction)instruction;
String methodName = invokeInstruction.getMethodName(cp);
if (methodName.equals("<init>")) {
i++; // step over the call to be able to insert *after* the call
ih = ihs[i]; // set the instruction handle to the super/this call
break;
}
}
}
// build the right join point member field name
final StringBuffer joinPoint = getJoinPointName(joinPointPrefix, fieldName);
// create an new join point
final InstructionHandle ihPost = il.insert(ih, factory.createLoad(Type.OBJECT, 0));
il.insert(ih, factory.createNew(joinPointClass));
// load the parameters (uuid, this, field signature)
il.insert(ih, InstructionConstants.DUP);
il.insert(ih, new PUSH(cp, uuid));
il.insert(ih, factory.createLoad(Type.OBJECT, 0));
il.insert(ih, new PUSH(cp, signature));
// invokes the constructor
il.insert(ih, factory.createInvoke(
joinPointClass,
"<init>",
Type.VOID,
new Type[]{Type.STRING, Type.OBJECT, Type.STRING},
Constants.INVOKESPECIAL));
// set the join point to the member field specified
il.insert(ih, factory.createFieldAccess(
cg.getClassName(),
joinPoint.toString(),
joinPointType,
Constants.PUTFIELD));
il.redirectBranches(ih, ihPost);
mg.setMaxStack();
mg.setMaxLocals();
return mg.getMethod();
}
/**
* Insert the pre advices for the field.
*
* @param il the instruction list
* @param before the instruction handler to insert it before
* @param cg the ClassGen
* @param fieldName the name of the field
* @param factory the objectfactory
* @param joinPointType the type of the join point
*/
private void insertPreAdvice(final InstructionList il,
final InstructionHandle before,
final ClassGen cg,
final String fieldName,
final InstructionFactory factory,
final Type joinPointType) {
final String joinPointPrefix = getJoinPointPrefix(joinPointType);
final String joinPointClass = getJoinPointClass(joinPointType);
final StringBuffer joinPoint = getJoinPointName(joinPointPrefix, fieldName);
il.insert(before, factory.createLoad(Type.OBJECT, 0));
il.insert(before, factory.createFieldAccess(
cg.getClassName(),
joinPoint.toString(),
joinPointType,
Constants.GETFIELD));
il.insert(before, factory.createInvoke(joinPointClass,
TransformationUtil.FIELD_JOIN_POINT_PRE_EXECUTION_METHOD,
Type.VOID,
Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
}
/**
* Insert the post advices for the field.
*
* @param il the instruction list
* @param before the instruction handle to insert before
* @param cg the ClassGen
* @param fieldName the name of the field
* @param factory the objectfactory
* @param joinPointType the type of the join point
*/
private void insertPostAdvice(final InstructionList il,
final InstructionHandle before,
final ClassGen cg,
final String fieldName,
final InstructionFactory factory,
final Type joinPointType) {
final String joinPointPrefix = getJoinPointPrefix(joinPointType);
final String joinPointClass = getJoinPointClass(joinPointType);
final StringBuffer joinPoint =
getJoinPointName(joinPointPrefix, fieldName);
il.insert(before, factory.createLoad(Type.OBJECT, 0));
il.insert(before, factory.createFieldAccess(
cg.getClassName(),
joinPoint.toString(),
joinPointType,
Constants.GETFIELD));
il.insert(before, factory.createInvoke(
joinPointClass,
TransformationUtil.FIELD_JOIN_POINT_POST_EXECUTION_METHOD,
Type.VOID,
Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
}
/**
* Callback method. Is being called before each transformation.
*/
public void sessionStart() {
}
/**
* Callback method. Is being called after each transformation.
*/
public void sessionEnd() {
}
/**
* Callback method. Prints a log/status message at each transformation.
*
* @return a log string
*/
public String verboseMessage() {
return this.getClass().getName();
}
/**
* Filters the classes to be transformed.
*
* @param definition the definition
* @param classMetaData the meta-data for the class
* @param cg the class to filter
* @return boolean true if the method should be filtered away
*/
private boolean classFilter(final AspectWerkzDefinition definition,
final ClassMetaData classMetaData,
final ClassGen cg) {
if (cg.isInterface()) {
return true;
}
String className = cg.getClassName();
if (definition.inExcludePackage(className)) {
return true;
}
if (!definition.inIncludePackage(className)) {
return true;
}
if (definition.hasSetPointcut(classMetaData) ||
definition.hasGetPointcut(classMetaData)) {
return false;
}
return true;
}
/**
* Filters the methods.
*
* @param method the method to filter
* @return boolean true if the method should be filtered away
*/
private boolean methodFilter(final Method method) {
return method.isNative() || method.isAbstract();
}
/**
* Filters the PUTFIELD's to be transformed.
*
* @param definition the definition
* @param classMetaData the class to filter
* @param fieldMetaData the field to filter
* @return the UUID for the weave model
*/
private String setFieldFilter(final AspectWerkzDefinition definition,
final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData) {
if (fieldMetaData.getName().startsWith(TransformationUtil.ASPECTWERKZ_PREFIX)) {
return null;
}
if (definition.hasSetPointcut(classMetaData, fieldMetaData)) {
return definition.getUuid();
}
return null;
}
/**
* Filters the GETFIELD's to be transformed.
*
* @param definition the definition
* @param classMetaData the class to filter
* @param fieldMetaData the field to filter
* @return the UUID for the weave model
*/
private String getFieldFilter(final AspectWerkzDefinition definition,
final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData) {
if (fieldMetaData.getName().startsWith(TransformationUtil.ASPECTWERKZ_PREFIX)) {
return null;
}
if (definition.hasGetPointcut(classMetaData, fieldMetaData)) {
return definition.getUuid();
}
return null;
}
/**
* Returns the join point prefix.
*
* @param joinPointType the join point type
* @return the join point prefix
*/
private String getJoinPointPrefix(final Type joinPointType) {
String joinPointPrefix;
if (joinPointType.equals(TransformationUtil.MEMBER_FIELD_GET_JOIN_POINT_TYPE)) {
joinPointPrefix = TransformationUtil.MEMBER_FIELD_GET_JOIN_POINT_PREFIX;
}
else if (joinPointType.equals(TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_TYPE)) {
joinPointPrefix = TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_PREFIX;
}
else {
throw new RuntimeException("field join point type unknown: " + joinPointType);
}
return joinPointPrefix;
}
/**
* Returns the join point class.
*
* @param joinPointType the join point type
* @return the join point class
*/
private String getJoinPointClass(final Type joinPointType) {
String joinPointClass;
if (joinPointType.equals(TransformationUtil.MEMBER_FIELD_GET_JOIN_POINT_TYPE)) {
joinPointClass = TransformationUtil.MEMBER_FIELD_GET_JOIN_POINT_CLASS;
}
else if (joinPointType.equals(TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_TYPE)) {
joinPointClass = TransformationUtil.MEMBER_FIELD_SET_JOIN_POINT_CLASS;
}
else {
throw new RuntimeException("field join point type unknown: " + joinPointType);
}
return joinPointClass;
}
/**
* Returns the name of the join point.
*
* @param joinPointPrefix the prefix
* @param fieldName the name of the field
* @return the name of the join point
*/
private static StringBuffer getJoinPointName(final String joinPointPrefix,
final String fieldName) {
final StringBuffer joinPoint = new StringBuffer();
joinPoint.append(joinPointPrefix);
joinPoint.append(fieldName);
return joinPoint;
}
/**
* Container for the join point field data.
*/
private static class JoinPointFieldData {
private String m_name;
private String m_signature;
private Type m_type;
private String m_uuid;
public JoinPointFieldData(final String name,
final String signature,
final Type type,
final String uuid) {
m_name = name;
m_signature = signature;
m_type = type;
m_uuid = uuid;
}
public String getName() {
return m_name;
}
public String getSignature() {
return m_signature;
}
public Type getType() {
return m_type;
}
public String getUuid() {
return m_uuid;
}
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof JoinPointFieldData)) return false;
final JoinPointFieldData obj = (JoinPointFieldData)o;
return areEqualsOrBothNull(obj.m_name, this.m_name) &&
areEqualsOrBothNull(obj.m_uuid, this.m_uuid) &&
areEqualsOrBothNull(obj.m_signature, this.m_signature);
}
public int hashCode() {
int result = 17;
result = 37 * result + hashCodeOrZeroIfNull(m_name);
result = 37 * result + hashCodeOrZeroIfNull(m_uuid);
result = 37 * result + hashCodeOrZeroIfNull(m_signature);
return result;
}
protected static boolean areEqualsOrBothNull(final Object o1, final Object o2) {
if (null == o1) return (null == o2);
return o1.equals(o2);
}
protected static int hashCodeOrZeroIfNull(final Object o) {
if (null == o) return 19;
return o.hashCode();
}
}
}