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.AsmHelper;
19  import org.codehaus.aspectwerkz.transform.AsmHelper;
20  import org.codehaus.aspectwerkz.util.Base64;
21  import org.objectweb.asm.Attribute;
22  import org.objectweb.asm.ClassAdapter;
23  import org.objectweb.asm.ClassReader;
24  import org.objectweb.asm.ClassVisitor;
25  import org.objectweb.asm.ClassWriter;
26  import org.objectweb.asm.CodeVisitor;
27  import org.objectweb.asm.attrs.RuntimeInvisibleAnnotations;
28  import org.objectweb.asm.attrs.Attributes;
29  
30  import java.io.ByteArrayOutputStream;
31  import java.io.File;
32  import java.io.FileOutputStream;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.ObjectOutputStream;
36  import java.net.URL;
37  import java.net.URLClassLoader;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Iterator;
41  import java.util.List;
42  
43  /***
44   * Enhances classes with custom attributes using the ASM library.
45   * 
46   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
47   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
48   */
49  public class AsmAttributeEnhancer implements AttributeEnhancer {
50      /***
51       * The class reader.
52       */
53      private ClassReader m_reader = null;
54  
55      /***
56       * The name of the class file.
57       */
58      private String m_classFileName = null;
59  
60      /***
61       * The class name.
62       */
63      private String m_className = null;
64  
65      /***
66       * Compiled class class loader
67       */
68      private URLClassLoader m_loader = null;
69  
70      /***
71       * The class attributes.
72       */
73      private List m_classAttributes = new ArrayList();
74  
75      /***
76       * The constructor attributes.
77       */
78      private List m_constructorAttributes = new ArrayList();
79  
80      /***
81       * The method attributes.
82       */
83      private List m_methodAttributes = new ArrayList();
84  
85      /***
86       * The field attributes.
87       */
88      private List m_fieldAttributes = new ArrayList();
89  
90      /***
91       * Initializes the attribute enhancer. Must always be called before use.
92       * 
93       * @param className the class name
94       * @param classPath the class path
95       * @return true if the class was succefully loaded, false otherwise
96       */
97      public boolean initialize(final String className, final URL[] classPath) {
98          try {
99              m_className = className;
100             m_loader = new URLClassLoader(classPath);
101             m_classFileName = className.replace('.', '/') + ".class";
102             InputStream classAsStream = m_loader.getResourceAsStream(m_classFileName);
103             if (classAsStream == null) {
104                 return false;
105             }
106             // setup the ASM stuff in init, but only parse at write time
107             try {
108                 m_reader = new ClassReader(classAsStream);
109             } catch (Exception e) {
110                 throw new ClassNotFoundException(m_className, e);
111             } finally {
112                 classAsStream.close();//AW-296
113             }
114         } catch (Exception e) {
115             throw new WrappedRuntimeException(e);
116         }
117         return true;
118     }
119 
120     /***
121      * Inserts an attribute on class level.
122      * 
123      * @param attribute the attribute
124      */
125     public void insertClassAttribute(final Object attribute) {
126         if (m_reader == null) {
127             throw new IllegalStateException("attribute enhancer is not initialized");
128         }
129         final byte[] serializedAttribute = serialize(attribute);
130         m_classAttributes.add(serializedAttribute);
131     }
132 
133     /***
134      * Inserts an attribute on field level.
135      * 
136      * @param field the QDox java field
137      * @param attribute the attribute
138      */
139     public void insertFieldAttribute(final JavaField field, final Object attribute) {
140         if (m_reader == null) {
141             throw new IllegalStateException("attribute enhancer is not initialized");
142         }
143         final byte[] serializedAttribute = serialize(attribute);
144         m_fieldAttributes.add(new FieldAttributeInfo(field, serializedAttribute));
145     }
146 
147     /***
148      * Inserts an attribute on method level.
149      * 
150      * @param method the QDox java method
151      * @param attribute the attribute
152      */
153     public void insertMethodAttribute(final JavaMethod method, final Object attribute) {
154         if (m_reader == null) {
155             throw new IllegalStateException("attribute enhancer is not initialized");
156         }
157         final String[] methodParamTypes = new String[method.getParameters().length];
158         for (int i = 0; i < methodParamTypes.length; i++) {
159             methodParamTypes[i] = TypeConverter.convertTypeToJava(method.getParameters()[i].getType());
160         }
161         final byte[] serializedAttribute = serialize(attribute);
162         m_methodAttributes.add(new MethodAttributeInfo(method, serializedAttribute));
163     }
164 
165     /***
166      * Inserts an attribute on constructor level.
167      * 
168      * @param constructor the QDox java method
169      * @param attribute the attribute
170      */
171     public void insertConstructorAttribute(final JavaMethod constructor, final Object attribute) {
172         if (m_reader == null) {
173             throw new IllegalStateException("attribute enhancer is not initialized");
174         }
175         final String[] methodParamTypes = new String[constructor.getParameters().length];
176         for (int i = 0; i < methodParamTypes.length; i++) {
177             methodParamTypes[i] = TypeConverter.convertTypeToJava(constructor.getParameters()[i].getType());
178         }
179         final byte[] serializedAttribute = serialize(attribute);
180         m_constructorAttributes.add(new MethodAttributeInfo(constructor, serializedAttribute));
181     }
182 
183     /***
184      * Writes the enhanced class to file.
185      * 
186      * @param destDir the destination directory
187      */
188     public void write(final String destDir) {
189         if (m_reader == null) {
190             throw new IllegalStateException("attribute enhancer is not initialized");
191         }
192         try {
193             // parse the bytecode
194             ClassWriter writer = AsmHelper.newClassWriter(true);
195             m_reader.accept(new AttributeClassAdapter(writer), Attributes.getDefaultAttributes(),false);
196 
197             // write the bytecode to disk
198             String path = destDir + File.separator + m_classFileName;
199             File file = new File(path);
200             File parentFile = file.getParentFile();
201             if (!parentFile.exists()) {
202                 // directory does not exist create all directories in the path
203                 if (!parentFile.mkdirs()) {
204                     throw new RuntimeException("could not create dir structure needed to write file "
205                         + path
206                         + " to disk");
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("could not find dependency for mixin implicit interface: "
251                 + innerClassName
252                 + " due to: "
253                 + er.toString());
254         }
255     }
256 
257     /***
258      * Return the first interfaces implemented by a level in the class hierarchy (bottom top)
259      * 
260      * @return nearest superclass (including itself) implemented interfaces starting from root
261      */
262     private String[] getNearestInterfacesInHierarchy(final Class root) {
263         if (root == null) {
264             return new String[] {};
265         }
266         Class[] implementedClasses = root.getInterfaces();
267         String[] interfaces = null;
268         if (implementedClasses.length == 0) {
269             interfaces = getNearestInterfacesInHierarchy(root.getSuperclass());
270         } else {
271             interfaces = new String[implementedClasses.length];
272             for (int i = 0; i < implementedClasses.length; i++) {
273                 interfaces[i] = implementedClasses[i].getName();
274             }
275         }
276         return interfaces;
277     }
278 
279     /***
280      * Base class for the attribute adapter visitors.
281      * 
282      * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
283      */
284     private class AttributeClassAdapter extends ClassAdapter {
285         private static final String INIT_METHOD_NAME = "<init>";
286 
287         private boolean classLevelAnnotationDone = false;
288 
289         public AttributeClassAdapter(final ClassVisitor cv) {
290             super(cv);
291         }
292 
293         public void visitField(
294             final int access,
295             final String name,
296             final String desc,
297             final Object value,
298             final Attribute attrs) {
299 
300             RuntimeInvisibleAnnotations invisible = CustomAttributeHelper.linkRuntimeInvisibleAnnotations(attrs);
301             for (Iterator it = m_fieldAttributes.iterator(); it.hasNext();) {
302                 FieldAttributeInfo struct = (FieldAttributeInfo) it.next();
303                 if (name.equals(struct.field.getName())) {
304                     invisible.annotations.add(CustomAttributeHelper.createCustomAnnotation(struct.attribute));
305                 }
306             }
307             if (invisible.annotations.size() == 0) {
308                 invisible = null;
309             }
310             super.visitField(access, name, desc, value, (attrs!=null)?attrs:invisible);
311         }
312 
313         public CodeVisitor visitMethod(
314             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 }