Package org.jnode.ant.taskdefs

Source Code of org.jnode.ant.taskdefs.AnnotateTask$MarkerClassVisitor

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.ant.taskdefs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.tools.ant.BuildException;
import org.jnode.annotation.MagicPermission;
import org.jnode.annotation.SharedStatics;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.attrs.Annotation;
import org.objectweb.asm.attrs.Attributes;
import org.objectweb.asm.attrs.RuntimeVisibleAnnotations;
import org.objectweb.asm.util.TraceClassVisitor;

/**
* That ant task will add some annotations to some compiled classes
* mentioned in a property file.
* For now, it's only necessary to add annotations to some
* openjdk classes to avoid modifying the original source code.
*
* @author Fabien DUMINY (fduminy at jnode dot org)
*/
public class AnnotateTask extends FileSetTask {
    private static final String SHAREDSTATICS_TYPE_DESC = Type.getDescriptor(SharedStatics.class);
    private static final String MAGICPERMISSION_TYPE_DESC = Type.getDescriptor(MagicPermission.class);

    private File annotationFile;
    private String[] classesFiles;

    private String buildStartTime = "";
    private String pattern = "";
    private long startTime = 0;
    private String baseDir;

    private Properties annotations = new Properties();

    protected void doExecute() throws BuildException {
        try {
            SimpleDateFormat format = new SimpleDateFormat(pattern);
            startTime = format.parse(buildStartTime).getTime();
        } catch (Exception e) {
            throw new BuildException("invalid buildStartTime or pattern", e);
        }

        try {
            if (readProperties()) {
                for (String file : classesFiles) {
                    File classFile = new File(baseDir, file);
                    processFile(classFile);
                }
            }
        } catch (IOException ioe) {
            throw new BuildException(ioe);
        }
    }

    /**
     * Defines the annotation property file where are specified annotations to add.
     *
     * @param annotationFile
     */
    public final void setAnnotationFile(File annotationFile) {
        this.annotationFile = annotationFile;
    }

    /**
     * Define the time at which build started.
     *
     * @param buildStartTime
     */
    public final void setBuildStartTime(String buildStartTime) {
        this.buildStartTime = buildStartTime;
    }

    /**
     * Define the pattern with which buildStartTime is defined.
     *
     * @param pattern
     */
    public final void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public void setBaseDir(String baseDir) {
        this.baseDir = baseDir;
    }

    /**
     * Read the properties file. For now, it simply contains a list of
     * classes that need the SharedStatics annotation.
     *
     * @return
     * @throws BuildException
     */
    private boolean readProperties() throws BuildException {
        readProperties("annotationFile", annotationFile, annotations);
        if (annotations.isEmpty()) {
            System.err.println("WARNING: annotationFile is empty (or doesn't exist)");
            return false;
        }

        classesFiles = (String[]) annotations.keySet().toArray(new String[annotations.size()]);

        // we must sort the classes in reverse order so that
        // classes with longest package name will be used first
        // (that is only necessary for classes whose name is the same
        // but package is different ; typical such class name : "Constants")
        Arrays.sort(classesFiles, Collections.reverseOrder());

        return true;
    }

    /**
     * Generic method that read properties from a given file.
     *
     * @param name
     * @param file
     * @param properties
     * @throws BuildException
     */
    private void readProperties(String name, File file, Properties properties) throws BuildException {
        if (file == null) {
            throw new BuildException(name + " is mandatory");
        }

        if (!file.exists()) {
            return;
        }

        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            properties.load(fis);
        } catch (IOException e) {
            throw new BuildException(e);
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new BuildException(e);
                }
            }
        }
    }

    /**
     * Get the list of annotations for the given class file.
     *
     * @param classFile list of annotations with ',' as separator. null if no annotation for that class.
     * @return
     */
    private String getAnnotations(File classFile) {
        String annotations = null;
        String classFilePath = classFile.getAbsolutePath();
        for (String f : classesFiles) {
            if (classFilePath.endsWith(f)) {
                annotations = this.annotations.getProperty(f);
                break;
            }
        }

        return annotations;
    }

    /**
     * Actually process a class file (called from parent class).
     */
    @Override
    protected void processFile(File classFile) throws IOException {
        if (classFile.lastModified() < startTime) {
            return;
        }

        String annotations = getAnnotations(classFile);
        if (annotations == null) {
            return;
        }

        File tmpFile = new File(classFile.getParentFile(), classFile.getName() + ".tmp");
        FileInputStream fis = null;
        boolean classIsModified = false;

        try {
            fis = new FileInputStream(classFile);
            classIsModified = addAnnotation(classFile, fis, tmpFile, annotations);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }

        if (classIsModified) {
            if (trace) {
                traceClass(classFile, "before");

                traceClass(tmpFile, "after");
            }

            if (!classFile.delete()) {
                throw new IOException("can't delete " + classFile.getAbsolutePath());
            }

            if (!tmpFile.renameTo(classFile)) {
                throw new IOException("can't rename " + tmpFile.getAbsolutePath());
            }
        }
    }

    /**
     * Simple debug method that trace a class file.
     * It can be used to visually check that the annotations has been
     * properly added
     *
     * @param file
     * @param message
     * @throws IOException
     */
    private void traceClass(File file, String message) throws IOException {
        System.out.println("===== (" + message + ") trace for " + file.getAbsolutePath() + " =====");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);

            ClassReader cr = new ClassReader(fis);
            TraceClassVisitor tcv = new TraceClassVisitor(null, new PrintWriter(System.out));
            cr.accept(tcv, Attributes.getDefaultAttributes(), true);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
        System.out.println("----- end trace -----");
    }

    /**
     * Add an annotation to a class file.
     *
     * @param classFile
     * @param inputClass
     * @param tmpFile
     * @param annotations
     * @return {@code true} if the class file was modified
     * @throws BuildException
     */
    private boolean addAnnotation(File classFile, InputStream inputClass, File tmpFile, String annotations)
        throws BuildException {
        boolean classIsModified = false;
        FileOutputStream outputClass = null;

        ClassWriter cw = new ClassWriter(false);
        try {
            ClassReader cr = new ClassReader(inputClass);

            List<String> annotationTypeDescs = new ArrayList<String>(2);
            if (annotations.contains("SharedStatics")) {
                annotationTypeDescs.add(SHAREDSTATICS_TYPE_DESC);
            }
            if (annotations.contains("MagicPermission")) {
                annotationTypeDescs.add(MAGICPERMISSION_TYPE_DESC);
            }

            MarkerClassVisitor mcv = new MarkerClassVisitor(cw, annotationTypeDescs);
            cr.accept(mcv, Attributes.getDefaultAttributes(), true);

            if (mcv.classIsModified()) {
                System.out.println("adding annotations " + annotations + " to file " + classFile.getName());
                classIsModified = true;

                outputClass = new FileOutputStream(tmpFile);

                byte[] b = cw.toByteArray();
                outputClass.write(b);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new BuildException("Unable to add annotations to file " + classFile.getName(), ex);
        } finally {
            if (outputClass != null) {
                try {
                    outputClass.close();
                } catch (IOException e) {
                    System.err.println("Can't close stream for file " + classFile.getName());
                }

                long timestamp = classFile.lastModified();
                tmpFile.setLastModified(timestamp);
            }
        }

        return classIsModified;
    }

    /**
     * Visitor for a class file that actually do the job of adding annotations in the class.
     *
     * @author fabien
     */
    private static class MarkerClassVisitor extends ClassAdapter {
        private final List<String> annotationTypeDescs;

        private boolean classIsModified = false;

        public MarkerClassVisitor(ClassVisitor cv, List<String> annotationTypeDescs) {
            super(cv);

            this.annotationTypeDescs = annotationTypeDescs;
        }

        @Override
        public void visit(int version, int access, String name,
                          String superName, String[] interfaces, String sourceFile) {
            super.visit(org.objectweb.asm.Constants.V1_5, access,
                name, superName, interfaces, sourceFile);
        }

        @Override
        public void visitAttribute(Attribute attr) {
            if (attr instanceof RuntimeVisibleAnnotations) {
                RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
                for (Object annotation : rva.annotations) {
                    if (annotation instanceof Annotation) {
                        Annotation ann = (Annotation) annotation;
                        for (String annTypeDesc : annotationTypeDescs) {
                            if (ann.type.equals(annTypeDesc)) {
                                // we have found one of the annotations -> we won't need to add it again !
                                annotationTypeDescs.remove(annTypeDesc);
                                break;
                            }
                        }
                    }
                }
            }

            super.visitAttribute(attr);
        }

        @SuppressWarnings("unchecked")
        public void visitEnd() {
            if (!annotationTypeDescs.isEmpty()) {
                // we have not found the annotation -> we will add it and so modify the class
                classIsModified = true;
                RuntimeVisibleAnnotations attr = new RuntimeVisibleAnnotations();

                for (String annTypeDesc : annotationTypeDescs) {

                    Annotation ann = new Annotation(annTypeDesc);
                    ann.add("name", "");

                    attr.annotations.add(ann);
                }

                cv.visitAttribute(attr);
            }

            super.visitEnd();
        }

        public boolean classIsModified() {
            return classIsModified;
        }
    }
}
TOP

Related Classes of org.jnode.ant.taskdefs.AnnotateTask$MarkerClassVisitor

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.