001: /**************************************************************************************
002: * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved. *
003: * http://aspectwerkz.codehaus.org *
004: * ---------------------------------------------------------------------------------- *
005: * The software in this package is published under the terms of the LGPL license *
006: * a copy of which has been included with this distribution in the license.txt file. *
007: **************************************************************************************/package org.codehaus.aspectwerkz.annotation.instrumentation.asm;
008:
009: import com.thoughtworks.qdox.model.JavaField;
010: import com.thoughtworks.qdox.model.JavaMethod;
011:
012: import org.codehaus.aspectwerkz.annotation.instrumentation.AttributeEnhancer;
013: import org.codehaus.aspectwerkz.definition.DescriptorUtil;
014: import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
015: import org.codehaus.aspectwerkz.expression.QDoxParser;
016: import org.codehaus.aspectwerkz.reflect.TypeConverter;
017: import org.codehaus.aspectwerkz.transform.inlining.AsmHelper;
018: import org.objectweb.asm.Attribute;
019: import org.objectweb.asm.ClassAdapter;
020: import org.objectweb.asm.ClassReader;
021: import org.objectweb.asm.ClassVisitor;
022: import org.objectweb.asm.ClassWriter;
023: import org.objectweb.asm.CodeVisitor;
024: import org.objectweb.asm.attrs.RuntimeInvisibleAnnotations;
025: import org.objectweb.asm.attrs.Attributes;
026:
027: import java.io.ByteArrayOutputStream;
028: import java.io.File;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.ObjectOutputStream;
033: import java.net.URL;
034: import java.net.URLClassLoader;
035: import java.util.ArrayList;
036: import java.util.Arrays;
037: import java.util.Iterator;
038: import java.util.List;
039:
040: /**
041: * Enhances classes with custom attributes using the ASM library.
042: *
043: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
044: * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
045: */
046: public class AsmAttributeEnhancer implements AttributeEnhancer {
047: /**
048: * The class reader.
049: */
050: private ClassReader m_reader = null;
051:
052: /**
053: * The name of the class file.
054: */
055: private String m_classFileName = null;
056:
057: /**
058: * The class name.
059: */
060: private String m_className = null;
061:
062: /**
063: * Compiled class class loader
064: */
065: private URLClassLoader m_loader = null;
066:
067: /**
068: * The class attributes.
069: */
070: private List m_classAttributes = new ArrayList();
071:
072: /**
073: * The constructor attributes.
074: */
075: private List m_constructorAttributes = new ArrayList();
076:
077: /**
078: * The method attributes.
079: */
080: private List m_methodAttributes = new ArrayList();
081:
082: /**
083: * The field attributes.
084: */
085: private List m_fieldAttributes = new ArrayList();
086:
087: /**
088: * Initializes the attribute enhancer. Must always be called before use.
089: *
090: * @param className the class name
091: * @param classPath the class path
092: * @return true if the class was succefully loaded, false otherwise
093: */
094: public boolean initialize(final String className,
095: final URL[] classPath) {
096: try {
097: m_className = className;
098: m_loader = new URLClassLoader(classPath);
099: m_classFileName = className.replace('.', '/') + ".class";
100: InputStream classAsStream = m_loader
101: .getResourceAsStream(m_classFileName);
102: if (classAsStream == null) {
103: return false;
104: }
105: // setup the ASM stuff in init, but only parse at write time
106: try {
107: m_reader = new ClassReader(classAsStream);
108: } catch (Exception e) {
109: throw new ClassNotFoundException(m_className, e);
110: } finally {
111: classAsStream.close();//AW-296
112: }
113: } catch (Exception e) {
114: throw new WrappedRuntimeException(e);
115: }
116: return true;
117: }
118:
119: /**
120: * Inserts an attribute on class level.
121: *
122: * @param attribute the attribute
123: */
124: public void insertClassAttribute(final Object attribute) {
125: if (m_reader == null) {
126: throw new IllegalStateException(
127: "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,
140: final Object attribute) {
141: if (m_reader == null) {
142: throw new IllegalStateException(
143: "attribute enhancer is not initialized");
144: }
145: final byte[] serializedAttribute = serialize(attribute);
146: m_fieldAttributes.add(new FieldAttributeInfo(field,
147: serializedAttribute));
148: }
149:
150: /**
151: * Inserts an attribute on method level.
152: *
153: * @param method the QDox java method
154: * @param attribute the attribute
155: */
156: public void insertMethodAttribute(final JavaMethod method,
157: final Object attribute) {
158: if (m_reader == null) {
159: throw new IllegalStateException(
160: "attribute enhancer is not initialized");
161: }
162: final String[] methodParamTypes = new String[method
163: .getParameters().length];
164: for (int i = 0; i < methodParamTypes.length; i++) {
165: methodParamTypes[i] = TypeConverter
166: .convertTypeToJava(method.getParameters()[i]
167: .getType());
168: }
169: final byte[] serializedAttribute = serialize(attribute);
170: m_methodAttributes.add(new MethodAttributeInfo(method,
171: serializedAttribute));
172: }
173:
174: /**
175: * Inserts an attribute on constructor level.
176: *
177: * @param constructor the QDox java method
178: * @param attribute the attribute
179: */
180: public void insertConstructorAttribute(
181: final JavaMethod constructor, final Object attribute) {
182: if (m_reader == null) {
183: throw new IllegalStateException(
184: "attribute enhancer is not initialized");
185: }
186: final String[] methodParamTypes = new String[constructor
187: .getParameters().length];
188: for (int i = 0; i < methodParamTypes.length; i++) {
189: methodParamTypes[i] = TypeConverter
190: .convertTypeToJava(constructor.getParameters()[i]
191: .getType());
192: }
193: final byte[] serializedAttribute = serialize(attribute);
194: m_constructorAttributes.add(new MethodAttributeInfo(
195: constructor, serializedAttribute));
196: }
197:
198: /**
199: * Writes the enhanced class to file.
200: *
201: * @param destDir the destination directory
202: */
203: public void write(final String destDir) {
204: if (m_reader == null) {
205: throw new IllegalStateException(
206: "attribute enhancer is not initialized");
207: }
208: try {
209: // parse the bytecode
210: ClassWriter writer = AsmHelper.newClassWriter(true);
211: m_reader.accept(new AttributeClassAdapter(writer),
212: Attributes.getDefaultAttributes(), false);
213:
214: // write the bytecode to disk
215: String path = destDir + File.separator + m_classFileName;
216: File file = new File(path);
217: File parentFile = file.getParentFile();
218: if (!parentFile.exists()) {
219: // directory does not exist create all directories in the path
220: if (!parentFile.mkdirs()) {
221: throw new RuntimeException(
222: "could not create dir structure needed to write file "
223: + path + " to disk");
224: }
225: }
226: FileOutputStream os = new FileOutputStream(destDir
227: + File.separator + m_classFileName);
228: os.write(writer.toByteArray());
229: os.close();
230: } catch (IOException e) {
231: throw new WrappedRuntimeException(e);
232: }
233: }
234:
235: /**
236: * Serializes the attribute to byte array.
237: *
238: * @param attribute the attribute
239: * @return the attribute as a byte array
240: */
241: public static byte[] serialize(final Object attribute) {
242: try {
243: ByteArrayOutputStream baos = new ByteArrayOutputStream();
244: ObjectOutputStream oos = new ObjectOutputStream(baos);
245: oos.writeObject(attribute);
246: return baos.toByteArray();
247: } catch (IOException e) {
248: throw new WrappedRuntimeException(e);
249: }
250: }
251:
252: /**
253: * Return the first interfaces implemented by a level in the class hierarchy (bottom top)
254: *
255: * @return nearest superclass (including itself) implemented interfaces
256: */
257: public String[] getNearestInterfacesInHierarchy(
258: final String innerClassName) {
259: if (m_loader == null) {
260: throw new IllegalStateException(
261: "attribute enhancer is not initialized");
262: }
263: try {
264: Class innerClass = Class.forName(innerClassName, false,
265: m_loader);
266: return getNearestInterfacesInHierarchy(innerClass);
267: } catch (ClassNotFoundException e) {
268: throw new RuntimeException(
269: "could not load mixin for mixin implicit interface: "
270: + e.toString());
271: } catch (NoClassDefFoundError er) {
272: // raised if extends / implements dependancies not found
273: throw new RuntimeException(
274: "could not find dependency for mixin implicit interface: "
275: + innerClassName + " due to: "
276: + er.toString());
277: }
278: }
279:
280: /**
281: * Return the first interfaces implemented by a level in the class hierarchy (bottom top)
282: *
283: * @return nearest superclass (including itself) implemented interfaces starting from root
284: */
285: private String[] getNearestInterfacesInHierarchy(final Class root) {
286: if (root == null) {
287: return new String[] {};
288: }
289: Class[] implementedClasses = root.getInterfaces();
290: String[] interfaces = null;
291: if (implementedClasses.length == 0) {
292: interfaces = getNearestInterfacesInHierarchy(root
293: .getSuperclass());
294: } else {
295: interfaces = new String[implementedClasses.length];
296: for (int i = 0; i < implementedClasses.length; i++) {
297: interfaces[i] = implementedClasses[i].getName();
298: }
299: }
300: return interfaces;
301: }
302:
303: /**
304: * Base class for the attribute adapter visitors.
305: *
306: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
307: */
308: private class AttributeClassAdapter extends ClassAdapter {
309: private static final String INIT_METHOD_NAME = "<init>";
310:
311: private boolean classLevelAnnotationDone = false;
312:
313: public AttributeClassAdapter(final ClassVisitor cv) {
314: super (cv);
315: }
316:
317: public void visitField(final int access, final String name,
318: final String desc, final Object value,
319: final Attribute attrs) {
320:
321: RuntimeInvisibleAnnotations invisible = CustomAttributeHelper
322: .linkRuntimeInvisibleAnnotations(attrs);
323: for (Iterator it = m_fieldAttributes.iterator(); it
324: .hasNext();) {
325: FieldAttributeInfo struct = (FieldAttributeInfo) it
326: .next();
327: if (name.equals(struct.field.getName())) {
328: invisible.annotations.add(CustomAttributeHelper
329: .createCustomAnnotation(struct.attribute));
330: }
331: }
332: if (invisible.annotations.size() == 0) {
333: invisible = null;
334: }
335: super .visitField(access, name, desc, value,
336: (attrs != null) ? attrs : invisible);
337: }
338:
339: public CodeVisitor visitMethod(final int access,
340: final String name, final String desc,
341: final String[] exceptions, final Attribute attrs) {
342:
343: RuntimeInvisibleAnnotations invisible = CustomAttributeHelper
344: .linkRuntimeInvisibleAnnotations(attrs);
345: if (!name.equals(INIT_METHOD_NAME)) {
346: for (Iterator it = m_methodAttributes.iterator(); it
347: .hasNext();) {
348: MethodAttributeInfo struct = (MethodAttributeInfo) it
349: .next();
350: JavaMethod method = struct.method;
351: String[] parameters = QDoxParser
352: .getJavaMethodParametersAsStringArray(method);
353: if (name.equals(method.getName())
354: && Arrays.equals(parameters, DescriptorUtil
355: .getParameters(desc))) {
356: invisible.annotations
357: .add(CustomAttributeHelper
358: .createCustomAnnotation(struct.attribute));
359: }
360: }
361: } else {
362: for (Iterator it = m_constructorAttributes.iterator(); it
363: .hasNext();) {
364: MethodAttributeInfo struct = (MethodAttributeInfo) it
365: .next();
366: JavaMethod method = struct.method;
367: String[] parameters = QDoxParser
368: .getJavaMethodParametersAsStringArray(method);
369: if (name.equals(INIT_METHOD_NAME)
370: && Arrays.equals(parameters, DescriptorUtil
371: .getParameters(desc))) {
372: invisible.annotations
373: .add(CustomAttributeHelper
374: .createCustomAnnotation(struct.attribute));
375: }
376: }
377: }
378: if (invisible.annotations.size() == 0) {
379: invisible = null;
380: }
381: return cv.visitMethod(access, name, desc, exceptions,
382: (attrs != null) ? attrs : invisible);
383: }
384:
385: public void visitAttribute(Attribute attrs) {
386: classLevelAnnotationDone = true;
387: RuntimeInvisibleAnnotations invisible = CustomAttributeHelper
388: .linkRuntimeInvisibleAnnotations(attrs);
389: for (Iterator it = m_classAttributes.iterator(); it
390: .hasNext();) {
391: byte[] bytes = (byte[]) it.next();
392: invisible.annotations.add(CustomAttributeHelper
393: .createCustomAnnotation(bytes));
394: }
395: if (invisible.annotations.size() == 0) {
396: invisible = null;
397: }
398: super .visitAttribute((attrs != null) ? attrs : invisible);
399: }
400:
401: public void visitEnd() {
402: if (!classLevelAnnotationDone) {
403: classLevelAnnotationDone = true;
404: RuntimeInvisibleAnnotations invisible = CustomAttributeHelper
405: .linkRuntimeInvisibleAnnotations(null);
406: for (Iterator it = m_classAttributes.iterator(); it
407: .hasNext();) {
408: byte[] bytes = (byte[]) it.next();
409: invisible.annotations.add(CustomAttributeHelper
410: .createCustomAnnotation(bytes));
411: }
412: if (invisible.annotations.size() > 0) {
413: super .visitAttribute(invisible);
414: }
415: super .visitEnd();
416: }
417: }
418: }
419:
420: /**
421: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
422: */
423: private static class FieldAttributeInfo {
424: public final byte[] attribute;
425: public final JavaField field;
426:
427: public FieldAttributeInfo(final JavaField field,
428: final byte[] attribute) {
429: this .field = field;
430: this .attribute = attribute;
431: }
432: }
433:
434: /**
435: * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
436: */
437: private static class MethodAttributeInfo {
438: public final byte[] attribute;
439: public final JavaMethod method;
440:
441: public MethodAttributeInfo(final JavaMethod method,
442: final byte[] attribute) {
443: this.method = method;
444: this.attribute = attribute;
445: }
446: }
447: }
|