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
105 try {
106 m_reader = new ClassReader(classAsStream);
107 } catch (Exception e) {
108 throw new ClassNotFoundException(m_className, e);
109 } finally {
110 classAsStream.close();
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
192 ClassWriter writer = AsmHelper.newClassWriter(true);
193 m_reader.accept(new AttributeClassAdapter(writer), Attributes.getDefaultAttributes(), false);
194
195
196 String path = destDir + File.separator + m_classFileName;
197 File file = new File(path);
198 File parentFile = file.getParentFile();
199 if (!parentFile.exists()) {
200
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
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 }