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 }