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.annotation.instrumentation.asm;
    9
    10  import com.thoughtworks.qdox.model.JavaField;
    11  import com.thoughtworks.qdox.model.JavaMethod;
    12
    13  import org.codehaus.aspectwerkz.annotation.instrumentation.AttributeEnhancer;
    14  import org.codehaus.aspectwerkz.definition.DescriptorUtil;
    15  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
    16  import org.codehaus.aspectwerkz.expression.QDoxParser;
    17  import org.codehaus.aspectwerkz.reflect.TypeConverter;
    18  import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
    19  import org.objectweb.asm.Attribute;
    20  import org.objectweb.asm.ClassAdapter;
    21  import org.objectweb.asm.ClassReader;
    22  import org.objectweb.asm.ClassVisitor;
    23  import org.objectweb.asm.ClassWriter;
    24  import org.objectweb.asm.CodeVisitor;
    25  import org.objectweb.asm.attrs.RuntimeInvisibleAnnotations;
    26  import org.objectweb.asm.attrs.Attributes;
    27
    28  import java.io.ByteArrayOutputStream;
    29  import java.io.File;
    30  import java.io.FileOutputStream;
    31  import java.io.IOException;
    32  import java.io.InputStream;
    33  import java.io.ObjectOutputStream;
    34  import java.net.URL;
    35  import java.net.URLClassLoader;
    36  import java.util.ArrayList;
    37  import java.util.Arrays;
    38  import java.util.Iterator;
    39  import java.util.List;
    40
    41  /***
    42   * Enhances classes with custom attributes using the ASM library.
    43   *
    44   * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    45   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
    46   */
    47  public class AsmAttributeEnhancer implements AttributeEnhancer {
    48      /***
    49       * The class reader.
    50       */
    51      private ClassReader m_reader = null;
    52
    53      /***
    54       * The name of the class file.
    55       */
    56      private String m_classFileName = null;
    57
    58      /***
    59       * The class name.
    60       */
    61      private String m_className = null;
    62
    63      /***
    64       * Compiled class class loader
    65       */
    66      private URLClassLoader m_loader = null;
    67
    68      /***
    69       * The class attributes.
    70       */
    71      private List m_classAttributes = new ArrayList();
    72
    73      /***
    74       * The constructor attributes.
    75       */
    76      private List m_constructorAttributes = new ArrayList();
    77
    78      /***
    79       * The method attributes.
    80       */
    81      private List m_methodAttributes = new ArrayList();
    82
    83      /***
    84       * The field attributes.
    85       */
    86      private List m_fieldAttributes = new ArrayList();
    87
    88      /***
    89       * Initializes the attribute enhancer. Must always be called before use.
    90       *
    91       * @param className the class name
    92       * @param classPath the class path
    93       * @return true if the class was succefully loaded, false otherwise
    94       */
    95      public boolean initialize(final String className, final URL[] classPath) {
    96          try {
    97              m_className = className;
    98              m_loader = new URLClassLoader(classPath);
    99              m_classFileName = className.replace('.', '/') + ".class";
    100             InputStream classAsStream = m_loader.getResourceAsStream(m_classFileName);
    101             if (classAsStream == null) {
    102                 return false;
    103             }
    104             // setup the ASM stuff in init, but only parse at write time
    105             try {
    106                 m_reader = new ClassReader(classAsStream);
    107             } catch (Exception e) {
    108                 throw new ClassNotFoundException(m_className, e);
    109             } finally {
    110                 classAsStream.close();//AW-296
    111             }
    112         } catch (Exception e) {
    113             throw new WrappedRuntimeException(e);
    114         }
    115         return true;
    116     }
    117
    118     /***
    119      * Inserts an attribute on class level.
    120      *
    121      * @param attribute the attribute
    122      */
    123     public void insertClassAttribute(final Object attribute) {
    124         if (m_reader == null) {
    125             throw new IllegalStateException("attribute enhancer is not initialized");
    126         }
    127         final byte[] serializedAttribute = serialize(attribute);
    128         m_classAttributes.add(serializedAttribute);
    129     }
    130
    131     /***
    132      * Inserts an attribute on field level.
    133      *
    134      * @param field     the QDox java field
    135      * @param attribute the attribute
    136      */
    137     public void insertFieldAttribute(final JavaField field, final Object attribute) {
    138         if (m_reader == null) {
    139             throw new IllegalStateException("attribute enhancer is not initialized");
    140         }
    141         final byte[] serializedAttribute = serialize(attribute);
    142         m_fieldAttributes.add(new FieldAttributeInfo(field, serializedAttribute));
    143     }
    144
    145     /***
    146      * Inserts an attribute on method level.
    147      *
    148      * @param method    the QDox java method
    149      * @param attribute the attribute
    150      */
    151     public void insertMethodAttribute(final JavaMethod method, final Object attribute) {
    152         if (m_reader == null) {
    153             throw new IllegalStateException("attribute enhancer is not initialized");
    154         }
    155         final String[] methodParamTypes = new String[method.getParameters().length];
    156         for (int i = 0; i < methodParamTypes.length; i++) {
    157             methodParamTypes[i] = TypeConverter.convertTypeToJava(method.getParameters()[i].getType());
    158         }
    159         final byte[] serializedAttribute = serialize(attribute);
    160         m_methodAttributes.add(new MethodAttributeInfo(method, serializedAttribute));
    161     }
    162
    163     /***
    164      * Inserts an attribute on constructor level.
    165      *
    166      * @param constructor the QDox java method
    167      * @param attribute   the attribute
    168      */
    169     public void insertConstructorAttribute(final JavaMethod constructor, final Object attribute) {
    170         if (m_reader == null) {
    171             throw new IllegalStateException("attribute enhancer is not initialized");
    172         }
    173         final String[] methodParamTypes = new String[constructor.getParameters().length];
    174         for (int i = 0; i < methodParamTypes.length; i++) {
    175             methodParamTypes[i] = TypeConverter.convertTypeToJava(constructor.getParameters()[i].getType());
    176         }
    177         final byte[] serializedAttribute = serialize(attribute);
    178         m_constructorAttributes.add(new MethodAttributeInfo(constructor, serializedAttribute));
    179     }
    180
    181     /***
    182      * Writes the enhanced class to file.
    183      *
    184      * @param destDir the destination directory
    185      */
    186     public void write(final String destDir) {
    187         if (m_reader == null) {
    188             throw new IllegalStateException("attribute enhancer is not initialized");
    189         }
    190         try {
    191             // parse the bytecode
    192             ClassWriter writer = AsmHelper.newClassWriter(true);
    193             m_reader.accept(new AttributeClassAdapter(writer), Attributes.getDefaultAttributes(), false);
    194
    195             // write the bytecode to disk
    196             String path = destDir + File.separator + m_classFileName;
    197             File file = new File(path);
    198             File parentFile = file.getParentFile();
    199             if (!parentFile.exists()) {
    200                 // directory does not exist create all directories in the path
    201                 if (!parentFile.mkdirs()) {
    202                     throw new RuntimeException(
    203                             "could not create dir structure needed to write file "
    204                             + path
    205                             + " to disk"
    206                     );
    207                 }
    208             }
    209             FileOutputStream os = new FileOutputStream(destDir + File.separator + m_classFileName);
    210             os.write(writer.toByteArray());
    211             os.close();
    212         } catch (IOException e) {
    213             throw new WrappedRuntimeException(e);
    214         }
    215     }
    216
    217     /***
    218      * Serializes the attribute to byte array.
    219      *
    220      * @param attribute the attribute
    221      * @return the attribute as a byte array
    222      */
    223     public static byte[] serialize(final Object attribute) {
    224         try {
    225             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    226             ObjectOutputStream oos = new ObjectOutputStream(baos);
    227             oos.writeObject(attribute);
    228             return baos.toByteArray();
    229         } catch (IOException e) {
    230             throw new WrappedRuntimeException(e);
    231         }
    232     }
    233
    234     /***
    235      * Return the first interfaces implemented by a level in the class hierarchy (bottom top)
    236      *
    237      * @return nearest superclass (including itself) implemented interfaces
    238      */
    239     public String[] getNearestInterfacesInHierarchy(final String innerClassName) {
    240         if (m_loader == null) {
    241             throw new IllegalStateException("attribute enhancer is not initialized");
    242         }
    243         try {
    244             Class innerClass = Class.forName(innerClassName, false, m_loader);
    245             return getNearestInterfacesInHierarchy(innerClass);
    246         } catch (ClassNotFoundException e) {
    247             throw new RuntimeException("could not load mixin for mixin implicit interface: " + e.toString());
    248         } catch (NoClassDefFoundError er) {
    249             // raised if extends / implements dependancies not found
    250             throw new RuntimeException(
    251                     "could not find dependency for mixin implicit interface: "
    252                     + innerClassName
    253                     + " due to: "
    254                     + er.toString()
    255             );
    256         }
    257     }
    258
    259     /***
    260      * Return the first interfaces implemented by a level in the class hierarchy (bottom top)
    261      *
    262      * @return nearest superclass (including itself) implemented interfaces starting from root
    263      */
    264     private String[] getNearestInterfacesInHierarchy(final Class root) {
    265         if (root == null) {
    266             return new String[]{};
    267         }
    268         Class[] implementedClasses = root.getInterfaces();
    269         String[] interfaces = null;
    270         if (implementedClasses.length == 0) {
    271             interfaces = getNearestInterfacesInHierarchy(root.getSuperclass());
    272         } else {
    273             interfaces = new String[implementedClasses.length];
    274             for (int i = 0; i < implementedClasses.length; i++) {
    275                 interfaces[i] = implementedClasses[i].getName();
    276             }
    277         }
    278         return interfaces;
    279     }
    280
    281     /***
    282      * Base class for the attribute adapter visitors.
    283      *
    284      * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    285      */
    286     private class AttributeClassAdapter extends ClassAdapter {
    287         private static final String INIT_METHOD_NAME = "<init>";
    288
    289         private boolean classLevelAnnotationDone = false;
    290
    291         public AttributeClassAdapter(final ClassVisitor cv) {
    292             super(cv);
    293         }
    294
    295         public void visitField(final int access,
    296                                final String name,
    297                                final String desc,
    298                                final Object value,
    299                                final Attribute attrs) {
    300
    301             RuntimeInvisibleAnnotations invisible = CustomAttributeHelper.linkRuntimeInvisibleAnnotations(attrs);
    302             for (Iterator it = m_fieldAttributes.iterator(); it.hasNext();) {
    303                 FieldAttributeInfo struct = (FieldAttributeInfo) it.next();
    304                 if (name.equals(struct.field.getName())) {
    305                     invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(struct.attribute));
    306                 }
    307             }
    308             if (invisible.annotations.size() == 0) {
    309                 invisible = null;
    310             }
    311             super.visitField(access, name, desc, value, (attrs != null) ? attrs : invisible);
    312         }
    313
    314         public CodeVisitor visitMethod(final int access,
    315                                        final String name,
    316                                        final String desc,
    317                                        final String[] exceptions,
    318                                        final Attribute attrs) {
    319
    320             RuntimeInvisibleAnnotations invisible = CustomAttributeHelper.linkRuntimeInvisibleAnnotations(attrs);
    321             if (!name.equals(INIT_METHOD_NAME)) {
    322                 for (Iterator it = m_methodAttributes.iterator(); it.hasNext();) {
    323                     MethodAttributeInfo struct = (MethodAttributeInfo) it.next();
    324                     JavaMethod method = struct.method;
    325                     String[] parameters = QDoxParser.getJavaMethodParametersAsStringArray(method);
    326                     if (name.equals(method.getName()) && Arrays.equals(parameters, DescriptorUtil.getParameters(desc))) {
    327                         invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(struct.attribute));
    328                     }
    329                 }
    330             } else {
    331                 for (Iterator it = m_constructorAttributes.iterator(); it.hasNext();) {
    332                     MethodAttributeInfo struct = (MethodAttributeInfo) it.next();
    333                     JavaMethod method = struct.method;
    334                     String[] parameters = QDoxParser.getJavaMethodParametersAsStringArray(method);
    335                     if (name.equals(INIT_METHOD_NAME) && Arrays.equals(parameters, DescriptorUtil.getParameters(desc))) {
    336                         invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(struct.attribute));
    337                     }
    338                 }
    339             }
    340             if (invisible.annotations.size() == 0) {
    341                 invisible = null;
    342             }
    343             return cv.visitMethod(access, name, desc, exceptions, (attrs != null) ? attrs : invisible);
    344         }
    345
    346         public void visitAttribute(Attribute attrs) {
    347             classLevelAnnotationDone = true;
    348             RuntimeInvisibleAnnotations invisible = CustomAttributeHelper.linkRuntimeInvisibleAnnotations(attrs);
    349             for (Iterator it = m_classAttributes.iterator(); it.hasNext();) {
    350                 byte[] bytes = (byte[]) it.next();
    351                 invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(bytes));
    352             }
    353             if (invisible.annotations.size() == 0) {
    354                 invisible = null;
    355             }
    356             super.visitAttribute((attrs != null) ? attrs : invisible);
    357         }
    358
    359         public void visitEnd() {
    360             if (!classLevelAnnotationDone) {
    361                 classLevelAnnotationDone = true;
    362                 RuntimeInvisibleAnnotations invisible = CustomAttributeHelper.linkRuntimeInvisibleAnnotations(null);
    363                 for (Iterator it = m_classAttributes.iterator(); it.hasNext();) {
    364                     byte[] bytes = (byte[]) it.next();
    365                     invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(bytes));
    366                 }
    367                 if (invisible.annotations.size() > 0) {
    368                     super.visitAttribute(invisible);
    369                 }
    370                 super.visitEnd();
    371             }
    372         }
    373     }
    374
    375     /***
    376      * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    377      */
    378     private static class FieldAttributeInfo {
    379         public final byte[] attribute;
    380         public final JavaField field;
    381
    382         public FieldAttributeInfo(final JavaField field, final byte[] attribute) {
    383             this.field = field;
    384             this.attribute = attribute;
    385         }
    386     }
    387
    388     /***
    389      * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
    390      */
    391     private static class MethodAttributeInfo {
    392         public final byte[] attribute;
    393         public final JavaMethod method;
    394
    395         public MethodAttributeInfo(final JavaMethod method, final byte[] attribute) {
    396             this.method = method;
    397             this.attribute = attribute;
    398         }
    399     }
    400 }