Package org.codehaus.backport175.compiler.bytecode

Source Code of org.codehaus.backport175.compiler.bytecode.AnnotationEnhancer$MethodAnnotationInfo

/*******************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved.                      *
* http://backport175.codehaus.org                                                         *
* --------------------------------------------------------------------------------------- *
* The software in this package is published under the terms of Apache License Version 2.0 *
* a copy of which has been included with this distribution in the license.txt file.       *
*******************************************************************************************/
package org.codehaus.backport175.compiler.bytecode;

import org.codehaus.backport175.compiler.CompilerException;
import org.codehaus.backport175.compiler.SourceLocation;
import org.codehaus.backport175.compiler.MessageHandler;
import org.codehaus.backport175.compiler.javadoc.RawAnnotation;
import org.codehaus.backport175.compiler.parser.AnnotationParser;
import org.codehaus.backport175.compiler.parser.ParseException;
import org.objectweb.asm.*;
import org.objectweb.asm.Type;

import java.net.URLClassLoader;
import java.net.URL;
import java.util.*;
import java.io.InputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import com.thoughtworks.qdox.model.*;

/**
* Enhances the target class with the JavaDoc annotations by putting them  in the class bytecode as Java 5 {@link
* java.lang.reader.RetentionPolicy.RUNTIME} annotations.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
*/
public class AnnotationEnhancer {

    /**
     * The class reader.
     */
    private ClassReader m_reader = null;

    /**
     * The name of the class file.
     */
    private String m_classFileName = null;

    /**
     * The class name.
     */
    private String m_className = null;

    /**
     * Compiled class' class loader
     */
    private ClassLoader m_loader = null;

    /**
     * The class annotations.
     */
    private List m_classAnnotations = new ArrayList();

    /**
     * The constructor annotations.
     */
    private List m_constructorAnnotations = new ArrayList();

    /**
     * The method annotations.
     */
    private List m_methodAnnotations = new ArrayList();

    /**
     * The field annotations.
     */
    private List m_fieldAnnotations = new ArrayList();

    /**
     * The message handler we report success to
     */
    private final MessageHandler m_messageHandler;

    /**
     * Constructor
     * @param messageHandler
     */
    public AnnotationEnhancer(MessageHandler messageHandler) {
        m_messageHandler = messageHandler;
    }

    /**
     * Initializes the enhancer. Must always be called before use.
     *
     * @param className the class name
     * @param classPath the class path
     * @return true if the class was succefully loaded, false otherwise
     */
    public boolean initialize(final String className, final URL[] classPath) {
        return initialize(className, new URLClassLoader(classPath));
    }

    /**
     * Initializes the enhancer. Must always be called before use.
     *
     * @param className the class name
     * @param loader
     * @return true if the class was succefully loaded, false otherwise
     */
    public boolean initialize(final String className, final ClassLoader loader) {
        try {
            m_className = className;
            m_loader = loader;
            m_classFileName = className.replace('.', '/') + ".class";
            InputStream classAsStream = m_loader.getResourceAsStream(m_classFileName);
            if (classAsStream == null) {
                return false;
            }
            // setup the ASM stuff in init, but only parse at write time
            try {
                m_reader = new ClassReader(classAsStream);
            } catch (Exception e) {
                throw new ClassNotFoundException(m_className, e);
            } finally {
                classAsStream.close();
            }
        } catch (Exception e) {
            throw new InstrumentationException(
                    "could not add annotations to bytecode of class [" + className + "] due to: " + e.toString(), e
            );
        }
        return true;
    }

    /**
     * Inserts an annotation on class level.
     *
     * @param annotation the annotation
     */
    public void insertClassAnnotation(final RawAnnotation annotation) {
        if (m_reader == null) {
            throw new IllegalStateException("annotation enhancer is not initialized");
        }
        if (hasClassAnnotation(annotation)) {
            throw new CompilerException(
                    "duplicate class annotation " + annotation,
                    SourceLocation.render(annotation)
            );
        }
        m_classAnnotations.add(annotation);
    }

    /**
     * Inserts an annotation on field level.
     *
     * @param field      the QDox java field
     * @param annotation the annotation
     */
    public void insertFieldAnnotation(final JavaField field, final RawAnnotation annotation) {
        if (m_reader == null) {
            throw new IllegalStateException("annotation enhancer is not initialized");
        }
        FieldAnnotationInfo info = new FieldAnnotationInfo(field, annotation);
        if (hasFieldAnnotation(info)) {
            throw new CompilerException(
                    "duplicate field annotation " + annotation,
                    SourceLocation.render(annotation)
            );
        }
        m_fieldAnnotations.add(new FieldAnnotationInfo(field, annotation));
    }

    /**
     * Inserts an annotation on method level.
     *
     * @param method     the QDox java method
     * @param annotation the annotation
     */
    public void insertMethodAnnotation(final JavaMethod method, final RawAnnotation annotation) {
        if (m_reader == null) {
            throw new IllegalStateException("annotation enhancer is not initialized");
        }
        MethodAnnotationInfo info = new MethodAnnotationInfo(method, annotation);
        if (hasMethodAnnotation(info)) {
            throw new ParseException(
                    "duplicate method annotation " + annotation,
                    SourceLocation.render(annotation)
            );
        }
        m_methodAnnotations.add(new MethodAnnotationInfo(method, annotation));
    }

    /**
     * Inserts an annotation on constructor level.
     *
     * @param constructor the QDox java method
     * @param annotation  the annotation
     */
    public void insertConstructorAnnotation(final JavaMethod constructor, final RawAnnotation annotation) {
        if (m_reader == null) {
            throw new IllegalStateException("annotation enhancer is not initialized");
        }
        MethodAnnotationInfo info = new MethodAnnotationInfo(constructor, annotation);
        if (hasConstructorAnnotation(info)) {
            throw new CompilerException(
                    "duplicate constructor annotation " + annotation,
                    SourceLocation.render(annotation)
            );
        }
        m_constructorAnnotations.add(new MethodAnnotationInfo(constructor, annotation));
    }

    /**
     * Writes the enhanced class to file.
     *
     * @param destDir the destination directory
     */
    public void write(final String destDir) {
        if (m_reader == null) {
            throw new IllegalStateException("annotation enhancer is not initialized");
        }

        ClassWriter writer = new ClassWriter(true);
        // note: annotation validation exception will be throwned here - if any
        m_reader.accept(new AnnotationMungingVisitor(writer), false);

        final String filename = destDir + File.separator + m_classFileName;
        File file = new File(filename);
        File parentFile = file.getParentFile();
        if (!parentFile.exists()) {
            // directory does not exist create all directories in the filename
            if (!parentFile.mkdirs()) {
                throw new CompilerException(
                        "could not create dir structure needed to write file " + filename + " to disk"
                );
            }
        }
        FileOutputStream os = null;
        try {
            os = new FileOutputStream(filename);
            os.write(writer.toByteArray());
        } catch (IOException e) {
            throw new CompilerException("could not write compiled class file to disk [" + filename + "]", e);
        } finally {
            try {
                os.close();
            } catch (IOException e) {
                throw new CompilerException("could not close file output stream for [" + filename + "]", e);
            }
        }
    }

    /**
     * Returns the VM desc for a QDox field.
     *
     * @param field the QDox field
     * @return the desc
     */
    public static String getFieldDesc(final JavaField field) {
        return getDescForQDoxType(field.getType());
    }

    /**
     * Returns the VM desc for a QDox method.
     *
     * @param method the QDox method
     * @return the desc
     */
    public static String getMethodDesc(final JavaMethod method) {
        StringBuffer sig = new StringBuffer();
        sig.append('(');
        final String[] methodParamTypes = new String[method.getParameters().length];
        for (int i = 0; i < methodParamTypes.length; i++) {
            sig.append(getDescForQDoxType(method.getParameters()[i].getType()));
        }
        sig.append(')');
        com.thoughtworks.qdox.model.Type returns = method.getReturns();
        sig.append(getDescForQDoxType(returns));
        return sig.toString();
    }

    /**
     * Returns the VM desc for a QDox Type.
     *
     * @param type the qdox type
     * @return the desc
     */
    public static String getDescForQDoxType(final com.thoughtworks.qdox.model.Type type) {
        if (type == null) {
            return "V"; // constructor
        }
        final StringBuffer desc = new StringBuffer();
        if (type.isArray()) {
            for (int i = type.getDimensions(); i > 0; --i) {
                desc.append('[');
            }
        }
        String value = type.getValue();
        if (value.equals("void")) {
            desc.append("V");
        } else if (value.equals("int")) {
            desc.append("I");
        } else if (value.equals("long")) {
            desc.append("J");
        } else if (value.equals("short")) {
            desc.append("S");
        } else if (value.equals("float")) {
            desc.append("F");
        } else if (value.equals("double")) {
            desc.append("D");
        } else if (value.equals("boolean")) {
            desc.append("Z");
        } else if (value.equals("char")) {
            desc.append("C");
        } else if (value.equals("byte")) {
            desc.append("B");
        } else {
            desc.append('L').append(value.replace('.', '/')).append(';');
        }
        return desc.toString();
    }

    /**
     * Puts the annotations in the class bytecode as Java 5 RuntimeVisibleAnnotations.
     *
     * @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
     */
    private class AnnotationMungingVisitor extends ClassAdapter {
        private static final String INIT_METHOD_NAME = "<init>";

        public AnnotationMungingVisitor(final ClassVisitor cv) {
            super(cv);
        }

        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            // handles runtime retention to expose 1.4 annotations under 1.5
            boolean hasRetention = false;
            for (Iterator iterator = m_classAnnotations.iterator(); iterator.hasNext();) {
                RawAnnotation rawAnnotation = (RawAnnotation) iterator.next();
                if (rawAnnotation.getName().equals("java.lang.annotation.Retention")) {
                    hasRetention = true;
                    break;
                }
            }
            if (hasRetention) {
                access |= org.objectweb.asm.Opcodes.ACC_ANNOTATION;
                interfaces = new String[]{"java/lang/annotation/Annotation"};
            }
            super.visit(version, access, name, signature, superName, interfaces);
        }

        public FieldVisitor visitField(
                final int access,
                final String name,
                final String desc,
                final String signature,
                final Object value) {
            FieldVisitor fieldVisitor = super.visitField(access, name, desc, signature, value);
            for (Iterator it = m_fieldAnnotations.iterator(); it.hasNext();) {
                final FieldAnnotationInfo annotationInfo = (FieldAnnotationInfo)it.next();
                String fieldDesc = getFieldDesc(annotationInfo.field);
                if (annotationInfo.field.getName().equals(name) &&
                    fieldDesc.equals(desc)) {
                    final Class annotationInterface = annotationInfo.annotation.getAnnotationClass();
                    final AnnotationVisitor bytecodeMunger = fieldVisitor.visitAnnotation(
                            Type.getDescriptor(annotationInterface),
                            true
                    );
                    AnnotationParser.parse(bytecodeMunger, annotationInterface, annotationInfo.annotation);
                    bytecodeMunger.visitEnd();
                    m_messageHandler.accept(SourceLocation.render(annotationInfo.annotation));
                }
            }
            return fieldVisitor;
        }

        public MethodVisitor visitMethod(
                final int access,
                final String name,
                final String desc,
                final String signature,
                final String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
            if (name.equals(INIT_METHOD_NAME)) {
                for (Iterator it = m_constructorAnnotations.iterator(); it.hasNext();) {
                    final MethodAnnotationInfo annotationInfo = (MethodAnnotationInfo)it.next();
                    if (getMethodDesc(annotationInfo.method).equals(desc)) {
                        final Class annIntf = annotationInfo.annotation.getAnnotationClass();
                        final AnnotationVisitor bytecodeMunger = methodVisitor.visitAnnotation(
                                Type.getDescriptor(annIntf),
                                true
                        );
                        AnnotationParser.parse(bytecodeMunger, annIntf, annotationInfo.annotation);
                        bytecodeMunger.visitEnd();
                        m_messageHandler.accept(SourceLocation.render(annotationInfo.annotation));
                    }
                }
            } else {
                for (Iterator it = m_methodAnnotations.iterator(); it.hasNext();) {
                    final MethodAnnotationInfo annotationInfo = (MethodAnnotationInfo)it.next();
                    if (annotationInfo.method.getName().equals(name) &&
                        getMethodDesc(annotationInfo.method).equals(desc)) {
                        final Class annIntf = annotationInfo.annotation.getAnnotationClass();
                        final AnnotationVisitor bytecodeMunger = methodVisitor.visitAnnotation(
                                Type.getDescriptor(annIntf),
                                true
                        );
                        AnnotationParser.parse(bytecodeMunger, annIntf, annotationInfo.annotation);
                        bytecodeMunger.visitEnd();
                        m_messageHandler.accept(SourceLocation.render(annotationInfo.annotation));
                    }
                }
            }
            return methodVisitor;
        }

        public void visitEnd() {
            for (Iterator it = m_classAnnotations.iterator(); it.hasNext();) {
                final RawAnnotation rawAnnotation = (RawAnnotation)it.next();
                final Class annIntf = rawAnnotation.getAnnotationClass();
                final AnnotationVisitor bytecodeMunger = visitAnnotation(
                        Type.getDescriptor(annIntf),
                        true
                );
                AnnotationParser.parse(bytecodeMunger, annIntf, rawAnnotation);
                bytecodeMunger.visitEnd();
                m_messageHandler.accept(SourceLocation.render(rawAnnotation));
            }
            super.visitEnd();
        }

    }

    /**
     * Two FieldAnnotationInfo are equals if method equals and annotation class is equals ie RawAnnotation is equals.
     * Used to ensure no annotation duplicate
     *
     * @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
     */
    private static class FieldAnnotationInfo {
        public final RawAnnotation annotation;
        public final JavaField field;

        public FieldAnnotationInfo(final JavaField field, final RawAnnotation attribute) {
            this.field = field;
            this.annotation = attribute;
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof FieldAnnotationInfo)) {
                return false;
            }

            final FieldAnnotationInfo fieldAnnotationInfo = (FieldAnnotationInfo)o;

            if (!annotation.equals(fieldAnnotationInfo.annotation)) {
                return false;
            }
            if (!field.equals(fieldAnnotationInfo.field)) {
                return false;
            }

            return true;
        }

        public int hashCode() {
            int result;
            result = annotation.hashCode();
            result = 29 * result + field.hashCode();
            return result;
        }
    }

    /**
     * Two MethodAnnotationInfo are equals if method equals and annotation class is equals ie RawAnnotation is equals.
     * Used to ensure no annotation duplicate.
     *
     * @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
     */
    private static class MethodAnnotationInfo {
        public final RawAnnotation annotation;
        public final JavaMethod method;

        public MethodAnnotationInfo(final JavaMethod method, final RawAnnotation attribute) {
            this.method = method;
            this.annotation = attribute;
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof MethodAnnotationInfo)) {
                return false;
            }

            final MethodAnnotationInfo methodAnnotationInfo = (MethodAnnotationInfo)o;

            if (!annotation.equals(methodAnnotationInfo.annotation)) {
                return false;
            }
            if (!method.equals(methodAnnotationInfo.method)) {
                return false;
            }

            return true;
        }

        public int hashCode() {
            int result;
            result = annotation.hashCode();
            result = 29 * result + method.hashCode();
            return result;
        }
    }

    /**
     * Checks if the class has already the given annotation
     * <p/>
     * Note: rely on RawAnnotation.equals
     *
     * @param annotation
     * @return
     */
    private boolean hasClassAnnotation(RawAnnotation annotation) {
        return m_classAnnotations.contains(annotation);
    }

    /**
     * Checks if the method has already the given annotation
     * <p/>
     * Note: rely on RawAnnotation.equals
     *
     * @param annotation
     * @return
     */
    private boolean hasMethodAnnotation(MethodAnnotationInfo annotation) {
        return m_methodAnnotations.contains(annotation);
    }

    /**
     * Checks if the field has already the given annotation
     * <p/>
     * Note: rely on RawAnnotation.equals
     *
     * @param annotation
     * @return
     */
    private boolean hasFieldAnnotation(FieldAnnotationInfo annotation) {
        return m_fieldAnnotations.contains(annotation);
    }

    /**
     * Checks if the constructor has already the given annotation
     * <p/>
     * Note: rely on RawAnnotation.equals
     *
     * @param annotation
     * @return
     */
    private boolean hasConstructorAnnotation(MethodAnnotationInfo annotation) {
        return m_constructorAnnotations.contains(annotation);
    }

    public String getClassName() {
        return m_className;
    }

    public String getClassFileName() {
        return m_classFileName;
    }

    public ClassLoader getClassLoader() {
        return m_loader;
    }
}
TOP

Related Classes of org.codehaus.backport175.compiler.bytecode.AnnotationEnhancer$MethodAnnotationInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.