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