All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.electriccloud.maven.javac2.Javac2MojoSupport Maven / Gradle / Ivy

The newest version!

//
// Copyright (c) 2012 Electric Cloud.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
//

package com.electriccloud.maven.javac2;

import com.intellij.ant.AntClassWriter;
import com.intellij.ant.InstrumentationUtil;
import com.intellij.ant.PseudoClassLoader;
import com.intellij.compiler.notNullVerification.NonnullVerifyingInstrumenter;
import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jfrog.maven.annomojo.annotations.MojoParameter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.EmptyVisitor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;

import static java.io.File.pathSeparator;

//
// Copyright (c) 2012 Electric Cloud.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
//
public abstract class Javac2MojoSupport
    extends AbstractMojo
{

    //~ Instance fields --------------------------------------------------------

    @MojoParameter(
        expression = "${project}",
        readonly   = true,
        required   = true
    )
    protected MavenProject m_project;

    //~ Methods ----------------------------------------------------------------

    protected void instrumentNotNull(
            @NotNull File             outputDirectory,
            @NotNull Iterable classPathElements)
        throws MojoExecutionException
    {

        if (!outputDirectory.isDirectory()) {

            // Nothing to do
            return;
        }

        // Make sure instrumented output directory exists
        File             instrumentedDirectory = new File(
                outputDirectory.getParentFile(),
                outputDirectory.getName() + "-instrumented");
        InstrumentResult result                = instrumentNotNull(
                outputDirectory, instrumentedDirectory,
                buildClasspathClassLoader(classPathElements));

        getLog().info("Up-to-date: " + result.getSkipped()
                + " Updated: " + result.getUpdated()
                + " Created: " + result.getInstrumented());
    }

    /**
     * Create class loader based on classpath, bootclasspath, and sourcepath.
     *
     * @param   classpathElements
     *
     * @return  a URL classLoader
     *
     * @throws  MojoExecutionException
     */
    private PseudoClassLoader buildClasspathClassLoader(
            @NotNull Iterable classpathElements)
        throws MojoExecutionException
    {
        StringBuilder classPath = new StringBuilder();

        for (String pathElement : classpathElements) {
            classPath.append(pathSeparator);
            classPath.append(pathElement);
        }

        if (getLog().isDebugEnabled()) {
            getLog().debug("classpath=" + classPath);
        }

        try {
            return InstrumentationUtil.createPseudoClassLoader(
                classPath.toString());
        }
        catch (MalformedURLException e) {
            throw new MojoExecutionException(e.getMessage());
        }
    }

    /**
     * @param   classDir       The output directory (e.g. target/classes,
     *                         target/test-classes
     * @param   instrumentDir  The shadow directory used to keep timestamps of
     *                         existing classes (e.g.
     *                         target/classes-instrumented,
     *                         target/test-classes-instrumented)
     * @param   loader
     *
     * @return
     *
     * @throws  MojoExecutionException
     */
    @NotNull
    @SuppressWarnings(
        {"ConstantConditions", "OverlyLongMethod", "OverlyComplexMethod"}
    )
    private InstrumentResult instrumentNotNull(
            @NotNull File              classDir,
            @NotNull File              instrumentDir,
            @NotNull PseudoClassLoader loader)
        throws MojoExecutionException
    {

        // Create the shadow directory if it doesn't exist
        if (!instrumentDir.isDirectory() && !instrumentDir.mkdirs()) {
            throw new MojoExecutionException(instrumentDir.getPath()
                    + " is not a directory, or cannot be created");
        }

        File[]           files  = classDir.listFiles();
        InstrumentResult result = new InstrumentResult();

        for (File file : files) {

            if (file.isDirectory()) {
                result.add(instrumentNotNull(file,
                        new File(instrumentDir, file.getName()), loader));

                continue;
            }

            @NonNls String name = file.getName();

            if (!name.endsWith(".class")) {
                continue;
            }

            // Check the timestamp in the shadow directory and skip if the
            // class file is not newer
            File shadowFile = new File(instrumentDir, file.getName());

            // If the shadow file doesn't exist, this is a new class file.
            // Create the shadow file
            if (shadowFile.exists()) {

                // Skip up-to-date files
                if (shadowFile.lastModified() >= file.lastModified()) {

                    if (getLog().isDebugEnabled()) {
                        getLog().debug("Up-to-date @NotNull/@Nonnull assertions: "
                                + file.getPath());
                    }

                    result.skipped();

                    continue;
                }

                if (getLog().isDebugEnabled()) {
                    getLog().debug("Updating @NotNull assertions: "
                            + file.getPath());
                }

                result.updated();
            }
            else {

                try {

                    // noinspection ResultOfMethodCallIgnored
                    shadowFile.createNewFile();
                }
                catch (IOException e) {
                    throw new MojoExecutionException("Unable to create "
                            + shadowFile.getPath() + ": " + e.getMessage(), e);
                }

                if (getLog().isDebugEnabled()) {
                    getLog().debug("Creating @NotNull assertions: "
                            + file.getPath());
                }

                result.instrumented();
            }

            try {
                instrumentClassFileNotNull(loader, file);
                instrumentClassFileNonnull(loader, file);

                // Set the timestamp of the shadow file
                if (!shadowFile.setLastModified(file.lastModified())) {
                    throw new MojoExecutionException(
                        "Unable to set lastModified on "
                            + shadowFile.getAbsolutePath());
                }
            }
            catch (IOException e) {
                getLog().warn("Failed to instrument @NotNull/@Nonnull assertion for "
                        + file.getPath() + ": " + e.getMessage(), e);
            }
            catch (MojoExecutionException e) {
                getLog().warn("Failed to instrument @NotNull/@Nonnull assertion for "
                        + file.getPath() + ": " + e.getMessage(), e);
            }
            catch (Exception e) {
                throw new MojoExecutionException(
                    "@NotNull/@Nonnull instrumentation failed for " + file.getPath()
                        + ": " + e.toString());
            }
        }

        return result;
    }

    //~ Methods ----------------------------------------------------------------

    private static void instrumentClassFileNotNull(
            @NotNull PseudoClassLoader loader,
            @NotNull File file)
        throws IOException
    {
        FileInputStream inputStream = new FileInputStream(file);

        try {
            ClassReader reader = new ClassReader(inputStream);
            ClassWriter writer = new AntClassWriter(getAsmClassWriterFlags(
                        getClassFileVersion(reader)), loader);

            //
            NotNullVerifyingInstrumenter instrumenter =
                new NotNullVerifyingInstrumenter(writer);

            reader.accept(instrumenter, 0);

            if (!instrumenter.isModification()) {
                return;
            }

            FileOutputStream fileOutputStream = new FileOutputStream(file);

            try {
                fileOutputStream.write(writer.toByteArray());
            }
            finally {
                fileOutputStream.close();
            }
        }
        finally {
            inputStream.close();
        }
    }

    /**
     * Copy of {@link Javac2MojoSupport#instrumentClassFileNotNull(com.intellij.ant.PseudoClassLoader, java.io.File)} method
     * which instruments JSR 205 Nonnull annotation
     */
    private static void instrumentClassFileNonnull(
            @NotNull PseudoClassLoader loader,
            @NotNull File file)
            throws IOException
    {
        FileInputStream inputStream = new FileInputStream(file);

        try {
            ClassReader reader = new ClassReader(inputStream);
            ClassWriter writer = new AntClassWriter(getAsmClassWriterFlags(
                    getClassFileVersion(reader)), loader);

            //
            NonnullVerifyingInstrumenter instrumenter =
                    new NonnullVerifyingInstrumenter(writer);

            reader.accept(instrumenter, 0);

            if (!instrumenter.isModification()) {
                return;
            }

            FileOutputStream fileOutputStream = new FileOutputStream(file);

            try {
                fileOutputStream.write(writer.toByteArray());
            }
            finally {
                fileOutputStream.close();
            }
        }
        finally {
            inputStream.close();
        }
    }


    /**
     * @param   version
     *
     * @return  the flags for class writer
     */
    private static int getAsmClassWriterFlags(int version)
    {
        return version >= Opcodes.V1_6 && version != Opcodes.V1_1
        ? ClassWriter.COMPUTE_FRAMES
        : ClassWriter.COMPUTE_MAXS;
    }

    private static int getClassFileVersion(@NotNull ClassReader reader)
    {
        final int[] classfileVersion = new int[1];

        reader.accept(new EmptyVisitor() {
                @Override
                @SuppressWarnings("RefusedBequest")
                public void visit(
                        int      version,
                        int      access,
                        String   name,
                        String   signature,
                        String   superName,
                        String[] interfaces)
                {
                    classfileVersion[0] = version;
                }
            }, 0);

        return classfileVersion[0];
    }

    //~ Inner Classes ----------------------------------------------------------

    private static class InstrumentResult
    {

        //~ Instance fields ----------------------------------------------------

        private int m_instrumented;
        private int m_skipped;
        private int m_updated;

        //~ Methods ------------------------------------------------------------

        public void add(@NotNull InstrumentResult result)
        {
            m_instrumented += result.m_instrumented;
            m_skipped      += result.m_skipped;
            m_updated      += result.m_updated;
        }

        public void instrumented()
        {
            m_instrumented++;
        }

        public void skipped()
        {
            m_skipped++;
        }

        public void updated()
        {
            m_updated++;
        }

        public int getInstrumented()
        {
            return m_instrumented;
        }

        public int getSkipped()
        {
            return m_skipped;
        }

        public int getUpdated()
        {
            return m_updated;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy