View Javadoc

    1   /***************************************************************************************
    2    * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
    3    * http://aspectwerkz.codehaus.org                                                    *
    4    * ---------------------------------------------------------------------------------- *
    5    * The software in this package is published under the terms of the LGPL license      *
    6    * a copy of which has been included with this distribution in the license.txt file.  *
    7    **************************************************************************************/
    8   package org.codehaus.aspectwerkz.transform.inlining.weaver;
    9
    10  import java.util.Iterator;
    11  import java.util.Set;
    12  import java.util.List;
    13  import java.util.Map;
    14  import java.util.HashMap;
    15  import java.util.HashSet;
    16
    17  import org.objectweb.asm.*;
    18  import org.codehaus.aspectwerkz.transform.Context;
    19  import org.codehaus.aspectwerkz.transform.TransformationConstants;
    20  import org.codehaus.aspectwerkz.transform.TransformationUtil;
    21  import org.codehaus.aspectwerkz.transform.inlining.ContextImpl;
    22  import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
    23  import org.codehaus.aspectwerkz.definition.SystemDefinition;
    24  import org.codehaus.aspectwerkz.definition.MixinDefinition;
    25  import org.codehaus.aspectwerkz.expression.ExpressionContext;
    26  import org.codehaus.aspectwerkz.expression.PointcutType;
    27  import org.codehaus.aspectwerkz.reflect.ClassInfo;
    28  import org.codehaus.aspectwerkz.reflect.MethodInfo;
    29  import org.codehaus.aspectwerkz.reflect.ClassInfoHelper;
    30  import org.codehaus.aspectwerkz.reflect.FieldInfo;
    31  import org.codehaus.aspectwerkz.DeploymentModel;
    32  import org.codehaus.aspectwerkz.DeploymentModel;
    33  import org.codehaus.aspectwerkz.exception.DefinitionException;
    34
    35  /***
    36   * Adds mixin methods and fields to hold mixin instances to the target class.
    37   *
    38   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    39   */
    40  public class AddMixinMethodsVisitor extends ClassAdapter implements TransformationConstants {
    41
    42      private final ContextImpl m_ctx;
    43      private String m_declaringTypeName;
    44      private final ClassInfo m_classInfo;
    45      private final Set m_addedMethods;
    46      private ExpressionContext m_expressionContext;
    47      private boolean m_hasClinit = false;
    48      private Map m_mixinFields;
    49      private boolean m_isAdvised = false;
    50
    51      /***
    52       * Creates a new class adapter.
    53       *
    54       * @param cv
    55       * @param classInfo
    56       * @param ctx
    57       * @param addedMethods
    58       */
    59      public AddMixinMethodsVisitor(final ClassVisitor cv,
    60                                    final ClassInfo classInfo,
    61                                    final Context ctx,
    62                                    final Set addedMethods) {
    63          super(cv);
    64          m_classInfo = classInfo;
    65          m_ctx = (ContextImpl) ctx;
    66          m_addedMethods = addedMethods;
    67          m_expressionContext = new ExpressionContext(PointcutType.WITHIN, m_classInfo, m_classInfo);
    68      }
    69
    70      /***
    71       * Visits the class.
    72       *
    73       * @param access
    74       * @param name
    75       * @param superName
    76       * @param interfaces
    77       * @param sourceFile
    78       */
    79      public void visit(final int version,
    80                        final int access,
    81                        final String name,
    82                        final String superName,
    83                        final String[] interfaces,
    84                        final String sourceFile) {
    85          ExpressionContext ctx = new ExpressionContext(PointcutType.WITHIN, m_classInfo, m_classInfo);
    86          if (!classFilter(m_classInfo, ctx, m_ctx.getDefinitions())) {
    87              m_declaringTypeName = name;
    88              m_mixinFields = new HashMap();
    89
    90              // populate with fields already present for mixins from previous weaving
    91              for (int i = 0; i < m_classInfo.getFields().length; i++) {
    92                  FieldInfo fieldInfo = m_classInfo.getFields()[i];
    93                  if (fieldInfo.getName().startsWith(MIXIN_FIELD_NAME)) {
    94                      m_mixinFields.put(fieldInfo.getType(), fieldInfo);
    95                  }
    96              }
    97
    98              // add fields and method for (not already there) mixins
    99              addMixinMembers();
    100         }
    101         super.visit(version, access, name, superName, interfaces, sourceFile);
    102     }
    103
    104     /***
    105      * Adds mixin fields and methods to the target class.
    106      */
    107     private void addMixinMembers() {
    108         int index = 0;
    109         for (Iterator it = m_ctx.getDefinitions().iterator(); it.hasNext();) {
    110             List mixinDefs = ((SystemDefinition) it.next()).getMixinDefinitions(m_expressionContext);
    111
    112             // check for method clashes
    113             Set interfaceSet = new HashSet();
    114             for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
    115                 interfaceSet.addAll(((MixinDefinition) it2.next()).getInterfaceClassNames());
    116             }
    117             if (ClassInfoHelper.hasMethodClash(interfaceSet, m_ctx.getLoader())) {
    118                 return;
    119             }
    120
    121             for (Iterator it2 = mixinDefs.iterator(); it2.hasNext();) {
    122                 final MixinDefinition mixinDef = (MixinDefinition) it2.next();
    123                 final ClassInfo mixinImpl = mixinDef.getMixinImpl();
    124                 final DeploymentModel deploymentModel = mixinDef.getDeploymentModel();
    125
    126                 if (m_mixinFields.containsKey(mixinImpl)) {
    127                     continue;
    128                 }
    129                 final MixinFieldInfo fieldInfo = new MixinFieldInfo();
    130                 fieldInfo.fieldName = MIXIN_FIELD_NAME + index;
    131                 fieldInfo.mixinClassInfo = mixinImpl;
    132
    133                 addMixinField(fieldInfo, deploymentModel, mixinDef);
    134                 addMixinMethods(fieldInfo, mixinDef);
    135
    136                 index++;
    137                 m_isAdvised = true;
    138             }
    139         }
    140     }
    141
    142     /***
    143      * Appends mixin instantiation to the clinit method and/or init method.
    144      *
    145      * @param access
    146      * @param name
    147      * @param desc
    148      * @param exceptions
    149      * @param attrs
    150      * @return
    151      */
    152     public CodeVisitor visitMethod(final int access,
    153                                    final String name,
    154                                    final String desc,
    155                                    final String[] exceptions,
    156                                    final Attribute attrs) {
    157         if (m_isAdvised) {
    158             if (name.equals(CLINIT_METHOD_NAME)) {
    159                 m_hasClinit = true;
    160                 CodeVisitor mv = new PrependToClinitMethodCodeAdapter(
    161                         cv.visitMethod(access, name, desc, exceptions, attrs)
    162                 );
    163                 mv.visitMaxs(0, 0);
    164                 return mv;
    165             } else if (name.equals(INIT_METHOD_NAME)) {
    166                 CodeVisitor mv = new AppendToInitMethodCodeAdapter(
    167                         cv.visitMethod(access, name, desc, exceptions, attrs)
    168                 );
    169                 mv.visitMaxs(0, 0);
    170                 return mv;
    171             }
    172         }
    173         return super.visitMethod(access, name, desc, exceptions, attrs);
    174     }
    175
    176     /***
    177      * Creates a new clinit method and adds mixin instantiation if it does not exist.
    178      */
    179     public void visitEnd() {
    180         if (m_isAdvised && !m_hasClinit) {
    181             // add the <clinit> method
    182             CodeVisitor mv = cv.visitMethod(
    183                     ACC_STATIC, CLINIT_METHOD_NAME, NO_PARAM_RETURN_VOID_SIGNATURE, null, null
    184             );
    185             for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
    186                 MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
    187                 if (fieldInfo.isStatic) {
    188                     initializeStaticMixinField(mv, fieldInfo);
    189                 }
    190             }
    191
    192             mv.visitInsn(RETURN);
    193             mv.visitMaxs(0, 0);
    194         }
    195         super.visitEnd();
    196     }
    197
    198     /***
    199      * Initializes a static mixin field.
    200      *
    201      * @param mv
    202      * @param fieldInfo
    203      */
    204     private void initializeStaticMixinField(final CodeVisitor mv, final MixinFieldInfo fieldInfo) {
    205         mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/', '.'));
    206         if (fieldInfo.isPerJVM) {
    207             mv.visitFieldInsn(
    208                     GETSTATIC,
    209                     m_declaringTypeName,
    210                     TARGET_CLASS_FIELD_NAME,
    211                     CLASS_CLASS_SIGNATURE
    212             );
    213             mv.visitMethodInsn(
    214                     INVOKEVIRTUAL,
    215                     CLASS_CLASS,
    216                     GETCLASSLOADER_METHOD_NAME,
    217                     CLASS_CLASS_GETCLASSLOADER_METHOD_SIGNATURE
    218             );
    219             mv.visitMethodInsn(
    220                     INVOKESTATIC,
    221                     MIXINS_CLASS_NAME,
    222                     MIXIN_OF_METHOD_NAME,
    223                     MIXIN_OF_METHOD_PER_JVM_SIGNATURE
    224             );
    225         } else {
    226             mv.visitFieldInsn(
    227                     GETSTATIC,
    228                     m_declaringTypeName,
    229                     TARGET_CLASS_FIELD_NAME,
    230                     CLASS_CLASS_SIGNATURE
    231             );
    232             mv.visitMethodInsn(
    233                     INVOKESTATIC,
    234                     MIXINS_CLASS_NAME,
    235                     MIXIN_OF_METHOD_NAME,
    236                     MIXIN_OF_METHOD_PER_CLASS_SIGNATURE
    237             );
    238         }
    239         mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName().replace('.', '/'));
    240         mv.visitFieldInsn(
    241                 PUTSTATIC,
    242                 m_declaringTypeName,
    243                 fieldInfo.fieldName,
    244                 fieldInfo.mixinClassInfo.getSignature()
    245         );
    246     }
    247
    248     /***
    249      * Initializes a member mixin field.
    250      *
    251      * @param mv
    252      * @param fieldInfo
    253      */
    254     private void initializeMemberMixinField(final CodeVisitor mv, final MixinFieldInfo fieldInfo) {
    255         mv.visitVarInsn(ALOAD, 0);
    256         mv.visitLdcInsn(fieldInfo.mixinClassInfo.getName().replace('/', '.'));
    257         mv.visitVarInsn(ALOAD, 0);
    258         mv.visitMethodInsn(
    259                 INVOKESTATIC,
    260                 MIXINS_CLASS_NAME,
    261                 MIXIN_OF_METHOD_NAME,
    262                 MIXIN_OF_METHOD_PER_INSTANCE_SIGNATURE
    263         );
    264         mv.visitTypeInsn(CHECKCAST, fieldInfo.mixinClassInfo.getName().replace('.', '/'));
    265         mv.visitFieldInsn(
    266                 PUTFIELD,
    267                 m_declaringTypeName,
    268                 fieldInfo.fieldName,
    269                 fieldInfo.mixinClassInfo.getSignature()
    270         );
    271     }
    272
    273     /***
    274      * Adds the mixin field to the target class.
    275      *
    276      * @param fieldInfo
    277      * @param deploymentModel
    278      * @param mixinDef
    279      */
    280     private void addMixinField(final MixinFieldInfo fieldInfo,
    281                                final DeploymentModel deploymentModel,
    282                                final MixinDefinition mixinDef) {
    283         final String signature = fieldInfo.mixinClassInfo.getSignature();
    284         int modifiers = 0;
    285         if (deploymentModel.equals(DeploymentModel.PER_CLASS) || deploymentModel.equals(DeploymentModel.PER_JVM)) {
    286             fieldInfo.isStatic = true;
    287             fieldInfo.isPerJVM = deploymentModel.equals(DeploymentModel.PER_JVM);
    288             modifiers = ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC;
    289         } else if (deploymentModel.equals(DeploymentModel.PER_INSTANCE)) {
    290             fieldInfo.isStatic = false;
    291             modifiers = ACC_PRIVATE + ACC_FINAL + ACC_SYNTHETIC;
    292         } else {
    293             throw new DefinitionException(
    294                     "deployment model [" + mixinDef.getDeploymentModel() +
    295                     "] for mixin [" + mixinDef.getMixinImpl().getName() +
    296                     "] is not supported"
    297             );
    298
    299         }
    300         if (mixinDef.isTransient()) {
    301             modifiers += ACC_TRANSIENT;
    302         }
    303         cv.visitField(modifiers, fieldInfo.fieldName, signature, null, null);
    304         m_mixinFields.put(mixinDef.getMixinImpl(), fieldInfo);
    305     }
    306
    307     /***
    308      * Adds the mixin methods to the target class.
    309      *
    310      * @param fieldInfo
    311      * @param mixinDef
    312      */
    313     private void addMixinMethods(final MixinFieldInfo fieldInfo, final MixinDefinition mixinDef) {
    314         for (Iterator it3 = mixinDef.getMethodsToIntroduce().iterator(); it3.hasNext();) {
    315             MethodInfo methodInfo = (MethodInfo) it3.next();
    316             final String methodName = methodInfo.getName();
    317             final String methodSignature = methodInfo.getSignature();
    318
    319             if (m_addedMethods.contains(AlreadyAddedMethodVisitor.getMethodKey(methodName, methodSignature))) {
    320                 continue;
    321             }
    322
    323             CodeVisitor mv = cv.visitMethod(
    324                     ACC_PUBLIC + ACC_SYNTHETIC,
    325                     methodName,
    326                     methodSignature,
    327                     null,
    328                     null
    329             );
    330             if (fieldInfo.isStatic) {
    331                 mv.visitFieldInsn(
    332                         GETSTATIC,
    333                         m_declaringTypeName,
    334                         fieldInfo.fieldName,
    335                         fieldInfo.mixinClassInfo.getSignature()
    336                 );
    337             } else {
    338                 mv.visitVarInsn(ALOAD, 0);
    339                 mv.visitFieldInsn(
    340                         GETFIELD,
    341                         m_declaringTypeName,
    342                         fieldInfo.fieldName,
    343                         fieldInfo.mixinClassInfo.getSignature()
    344                 );
    345             }
    346             AsmHelper.loadArgumentTypes(mv, Type.getArgumentTypes(methodSignature), false);
    347             mv.visitMethodInsn(
    348                     INVOKEVIRTUAL,
    349                     fieldInfo.mixinClassInfo.getName().replace('.', '/'),
    350                     methodName,
    351                     methodSignature
    352             );
    353             AsmHelper.addReturnStatement(mv, Type.getReturnType(methodSignature));
    354             mv.visitMaxs(0, 0);
    355         }
    356     }
    357
    358     /***
    359      * Filters the classes to be transformed.
    360      *
    361      * @param classInfo   the class to filter
    362      * @param ctx         the context
    363      * @param definitions a set with the definitions
    364      * @return boolean true if the method should be filtered away
    365      */
    366     public static boolean classFilter(final ClassInfo classInfo,
    367                                       final ExpressionContext ctx,
    368                                       final Set definitions) {
    369         for (Iterator it = definitions.iterator(); it.hasNext();) {
    370             SystemDefinition systemDef = (SystemDefinition) it.next();
    371             if (classInfo.isInterface()) {
    372                 return true;
    373             }
    374             String className = classInfo.getName().replace('/', '.');
    375             if (systemDef.inExcludePackage(className)) {
    376                 return true;
    377             }
    378             if (!systemDef.inIncludePackage(className)) {
    379                 return true;
    380             }
    381             if (systemDef.hasMixin(ctx)) {
    382                 return false;
    383             }
    384         }
    385         return true;
    386     }
    387
    388     /***
    389      * Adds initialization of static mixin fields to the beginning of the clinit method.
    390      *
    391      * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    392      */
    393     public class PrependToClinitMethodCodeAdapter extends CodeAdapter {
    394
    395         public PrependToClinitMethodCodeAdapter(final CodeVisitor ca) {
    396             super(ca);
    397             for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
    398                 MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
    399                 if (fieldInfo.isStatic) {
    400                     initializeStaticMixinField(ca, fieldInfo);
    401                 }
    402             }
    403         }
    404     }
    405
    406     /***
    407      * Adds initialization of member mixin fields to end of the init method.
    408      *
    409      * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    410      */
    411     public class AppendToInitMethodCodeAdapter extends CodeAdapter {
    412
    413         public AppendToInitMethodCodeAdapter(final CodeVisitor ca) {
    414             super(ca);
    415         }
    416
    417         public void visitInsn(final int opcode) {
    418             if (opcode == RETURN) {
    419                 for (Iterator i4 = m_mixinFields.values().iterator(); i4.hasNext();) {
    420                     MixinFieldInfo fieldInfo = (MixinFieldInfo) i4.next();
    421                     if (!fieldInfo.isStatic) {
    422                         initializeMemberMixinField(cv, fieldInfo);
    423                     }
    424                 }
    425             }
    426             super.visitInsn(opcode);
    427         }
    428     }
    429
    430     private static class MixinFieldInfo {
    431         private String fieldName;
    432         private ClassInfo mixinClassInfo;
    433         private boolean isStatic;
    434         private boolean isPerJVM = false;
    435     }
    436 }