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

com.atlassian.maven.plugin.clover.CloverInstrumentInternalMojo Maven / Gradle / Ivy

package com.atlassian.maven.plugin.clover;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.plugin.MojoExecutionException;
import com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo;
import com.atlassian.maven.plugin.clover.internal.CompilerConfiguration;
import com.atlassian.maven.plugin.clover.internal.instrumentation.MainInstrumenter;
import com.atlassian.maven.plugin.clover.internal.instrumentation.TestInstrumenter;

import java.io.File;
import java.util.*;

/**
 * Instrument source roots.
 *
 * 

Note 1: Do not call this MOJO directly. It is meant to be called in a custom forked lifecycle by the other * Clover plugin MOJOs.

*

Note 2: We bind this mojo to the "validate" phase so that it executes prior to any other mojos

* * @goal instrumentInternal * @phase validate * @requiresDependencyResolution test * */ public class CloverInstrumentInternalMojo extends AbstractCloverMojo implements CompilerConfiguration { /** * The directory where the Clover plugin will put all the files it generates during the build process. For * example the Clover plugin will put instrumented sources somewhere inside this directory. * * @parameter default-value="${project.build.directory}/clover" * @required */ private String cloverOutputDirectory; /** * List of all artifacts for this Clover plugin provided by Maven. This is used internally to get a handle on * the Clover JAR artifact. * *

Note: This is passed by Maven and must not be configured by the user.

* * @parameter expression="${plugin.artifacts}" * @required */ private List pluginArtifacts; /** * @parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}" * @required * @readonly */ private ArtifactFactory artifactFactory; /** * Artifact resolver used to find clovered artifacts (artifacts with a clover classifier). * * @component role="org.apache.maven.artifact.resolver.ArtifactResolver" * @required * @readonly */ private ArtifactResolver artifactResolver; /** * Local maven repository. * * @parameter expression="${localRepository}" * @required */ private ArtifactRepository localRepository; /** * The list of file to include in the instrumentation. * @parameter */ private Set includes = new HashSet(); /** * The list of file to exclude from the instrumentation. * @parameter */ private Set excludes = new HashSet(); /** * Specifies the custom method contexts to use for filtering specific methods from Clover reports. * * e.g.
<main>public static void main\(String args\[\]\).*</main>
* will define the context called 'main' which will match all public static void main methods. * * @parameter */ private Map methodContexts = new HashMap(); /** * Specifies the custom statement contexts to use for filtering specific statements from Clover reports. * * e.g.
<log>^LOG\..*</log>
     * defines a statement context called "log" which matches all LOG statements.
     * 
     * @parameter
     */
    private Map statementContexts = new HashMap();

    /**
     * Whether the Clover plugin should instrument all source roots (ie even
     * generated sources) or whether it should only instrument the main source
     * root.
     * @parameter expression="${maven.clover.includesAllSourceRoots}" default-value="false"
     */
    private boolean includesAllSourceRoots;

    /**
     * Whether the Clover plugin should instrument test source roots.
     * @parameter  expression="${maven.clover.includesTestSourceRoots}" default-value="true"
     */
    private boolean includesTestSourceRoots;

    /**
     * Use the fully qualified package name for java.lang.* classes.
     *
     * @parameter expression="${maven.clover.useFullyQualifiedJavaLang}" default-value="true"
     */
    private boolean useFullyQualifiedJavaLang;

    /**
     * The character encoding to use when parsing source files.
     *
     * @parameter expression="${maven.clover.encoding}" 
     */
    private String encoding;

    // HACK: this allows us to reset the source directories to the originals
    private static Map originalSrcMap = new HashMap();
    private static Map originalSrcTestMap = new HashMap();

    public static String getOriginalSrcDir(String module) {
        return (String) originalSrcMap.get(module);
    }

    public static String getOriginalSrcTestDir(String module) {
        return (String) originalSrcTestMap.get(module);
    }

    /**
     * {@inheritDoc}
     * @see com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo#execute()
     */
    public void execute()
        throws MojoExecutionException
    {

        if (skip) {
            getLog().info("Skipping clover instrumentation.");
            return;
        }

        resetSrcDirsOriginal(getProject().getArtifactId(), this);

        // Ensure output directories exist
        new File( this.cloverOutputDirectory ).mkdirs();
        String cloverOutputSourceDirectory = new File( this.cloverOutputDirectory, getSrcName()).getPath();
        String cloverOutputTestSourceDirectory = new File( this.cloverOutputDirectory, getSrcTestName()).getPath();
        new File( resolveCloverDatabase() ).getParentFile().mkdirs();

        super.execute();

        logArtifacts( "before changes" );

        // Instrument both the main sources and the test sources if the user has configured it
        MainInstrumenter mainInstrumenter =
            new MainInstrumenter( this, cloverOutputSourceDirectory );
        TestInstrumenter testInstrumenter =
            new TestInstrumenter( this, cloverOutputTestSourceDirectory );

        if ( isJavaProject() )
        {
            mainInstrumenter.instrument();
            if ( this.includesTestSourceRoots )
            {
                testInstrumenter.instrument();
            }
        }

        swizzleCloverDependencies();
        addCloverDependencyToCompileClasspath();

        // Modify Maven model so that it points to the new source directories and to the clovered
        // artifacts instead of the original values.
        String originalSrcDir = mainInstrumenter.redirectSourceDirectories();
        originalSrcMap.put(getProject().getArtifactId(), originalSrcDir);
        if ( this.includesTestSourceRoots )
        {
            String originalSrcTestDir = testInstrumenter.redirectSourceDirectories();
            originalSrcTestMap.put(getProject().getArtifactId(), originalSrcTestDir);
        }
        redirectOutputDirectories();
        redirectArtifact();

        logArtifacts( "after changes" );
    }

    public static void resetSrcDirsOriginal(String artefactId, CompilerConfiguration config) {
        if (originalSrcMap.containsKey(artefactId)) {
            final String sourceDirectory = (String) originalSrcMap.get(artefactId);
            MainInstrumenter mainInstrumenter =
                    new MainInstrumenter(config, sourceDirectory);
            mainInstrumenter.redirectSourceDirectories();

        }
        if (originalSrcTestMap.containsKey(artefactId)) {
            final String testDirectory = (String)originalSrcTestMap.get(artefactId);
            TestInstrumenter instrumenter =
                    new TestInstrumenter(config, testDirectory);
            instrumenter.redirectSourceDirectories();
        }
    }

    protected String getSrcTestName() {
        return "src-test";
    }

    protected String getSrcName() {
        return "src";
    }

    private boolean isJavaProject()
    {
        ArtifactHandler artifactHandler = getProject().getArtifact().getArtifactHandler();

        if ( !"java".equals( artifactHandler.getLanguage() ) )
        {
            getLog().warn( "The reported language of this project is " + artifactHandler.getLanguage() + ", attempting to instrument sources anyway.");
        }
        return true;
    }

    protected void redirectOutputDirectories()
    {
        // Explicitely set the output directory to be the Clover one so that all other plugins executing
        // thereafter output files in the Clover output directory and not in the main output directory.
        getProject().getBuild().setDirectory( this.cloverOutputDirectory );

        // TODO: Ugly hack below. Changing the directory should be enough for changing the values of all other
        // properties depending on it!
        getProject().getBuild().setOutputDirectory( new File( this.cloverOutputDirectory, "classes" ).getPath() );

        // TODO: This is a hack. Remove this when http://jira.codehaus.org/browse/MINSTALL-18 is fixed.
        new File( getProject().getBuild().getOutputDirectory() ).mkdirs();

        getProject().getBuild().setTestOutputDirectory(
            new File( this.cloverOutputDirectory, "test-classes" ).getPath() );
    }

    /**
     * Modify main artifact to add a "clover" classifier to it so that it's not mixed with the main artifact of
     * a normal build.
     */
    protected void redirectArtifact()
    {
        // Only redirect main artifact for non-pom projects
        if ( !getProject().getPackaging().equals( "pom" ) )
        {
            Artifact oldArtifact = getProject().getArtifact();
            Artifact newArtifact = this.artifactFactory.createArtifactWithClassifier( oldArtifact.getGroupId(),
                oldArtifact.getArtifactId(), oldArtifact.getVersion(), oldArtifact.getType(), "clover" );
            getProject().setArtifact( newArtifact );

            getProject().getBuild().setFinalName( getProject().getArtifactId() + "-" + getProject().getVersion()
                + "-clover" );
        }
    }

    /**
     * Browse through all project dependencies and try to find a clovered version of the dependency. If found
     * replace the main depedencency by the clovered version.
     */
    private void swizzleCloverDependencies()
    {
        getProject().setDependencyArtifacts(
            swizzleCloverDependencies( getProject().getDependencyArtifacts() ) );
        getProject().setArtifacts(
            swizzleCloverDependencies( getProject().getArtifacts() ) );
    }

    protected Set swizzleCloverDependencies( Set artifacts )
    {
        Set resolvedArtifacts = new LinkedHashSet();
        for ( Iterator i = artifacts.iterator(); i.hasNext(); )
        {
            Artifact artifact = (Artifact) i.next();

            // Do not try to find Clovered versions for artifacts with classifiers. This is because Maven2 only
            // supports a single classifier per artifact and thus if we replace the original classifier with
            // a Clover classifier the artifact will fail to perform properly as intended originally. This is a
            // limitation.
            if ( artifact.getClassifier() == null )
            {
                Artifact cloveredArtifact = this.artifactFactory.createArtifactWithClassifier( artifact.getGroupId(),
                    artifact.getArtifactId(), artifact.getVersion(), artifact.getType(), "clover" );

                // Try to resolve the artifact with a clover classifier. If it doesn't exist, simply add the original
                // artifact. If found, use the clovered artifact.
                try
                {
                    this.artifactResolver.resolve( cloveredArtifact, new ArrayList(), localRepository );

                    // Set the same scope as the main artifact as this is not set by createArtifactWithClassifier.
                    cloveredArtifact.setScope( artifact.getScope() );

                    // Check the timestamp of the artifact. If the found clovered version is older than the
                    // non-clovered one we need to use the non-clovered version. This is to handle use case such as:
                    // - Say you have a module B that depends on a module A
                    // - You run Clover on A
                    // - You make modifications on A such that B would fail if not built with the latest version of A
                    // - You try to run the Clover plugin on B. The build would fail if we didn't pick the latest
                    //   version between the original A version and the clovered version.
                    //
                    // We provide a 'fudge-factor' of 2 seconds, as the clover artifact is created first.
                    if ( cloveredArtifact.getFile().lastModified() + 2000l < artifact.getFile().lastModified() )
                    {
                        getLog().warn( "Using [" + artifact.getId() + "] even though a Clovered version exists "
                            + "but it's older and could fail the build. Please consider running Clover again on that "
                            + "dependency's project." );
                        resolvedArtifacts.add( artifact );

                    }
                    else
                    {
                        resolvedArtifacts.add( cloveredArtifact );
                    }
                }
                catch ( ArtifactResolutionException e )
                {
                    resolvedArtifacts.add( artifact );
                }
                catch ( ArtifactNotFoundException e )
                {
                    resolvedArtifacts.add( artifact );
                }
            }
            else
            {
                resolvedArtifacts.add( artifact );
            }
        }

        return resolvedArtifacts;
    }

    protected Artifact findCloverArtifact( List pluginArtifacts )
    {
        Artifact cloverArtifact = null;
        Iterator artifacts = pluginArtifacts.iterator();
        while ( artifacts.hasNext() && cloverArtifact == null )
        {
            Artifact artifact = (Artifact) artifacts.next();

            // We identify the clover JAR by checking the groupId and artifactId.
            if ( "com.cenqua.clover".equals( artifact.getGroupId() )
                && "clover".equals( artifact.getArtifactId() ) )
            {
                cloverArtifact = artifact;
            }
        }
        return cloverArtifact;
    }

    private void addCloverDependencyToCompileClasspath()
        throws MojoExecutionException
    {
        Artifact cloverArtifact = findCloverArtifact( this.pluginArtifacts );
        if ( cloverArtifact == null )
        {
            throw new MojoExecutionException(
                "Couldn't find [com.cenqua.clover:clover] artifact in plugin dependencies" );
        }

        cloverArtifact = artifactFactory.createArtifact( cloverArtifact.getGroupId(), cloverArtifact.getArtifactId(),
            cloverArtifact.getVersion(), Artifact.SCOPE_COMPILE, cloverArtifact.getType() );

        // TODO: use addArtifacts when it's implemented, see http://jira.codehaus.org/browse/MNG-2197
        Set set = new LinkedHashSet( getProject().getDependencyArtifacts() );
        set.add( cloverArtifact );
        getProject().setDependencyArtifacts( set );
    }

    private void logArtifacts( String message )
    {
        if ( getLog().isDebugEnabled() )
        {
            getLog().debug( "[Clover] List of dependency artifacts " + message + ":" );
            logArtifacts( getProject().getDependencyArtifacts() );

            getLog().debug( "[Clover] List of artifacts " + message + ":" );
            logArtifacts( getProject().getArtifacts() );
        }
    }

    private void logArtifacts( Set artifacts )
    {
        for ( Iterator i = artifacts.iterator(); i.hasNext(); )
        {
            Artifact artifact = (Artifact) i.next();
            getLog().debug( "[Clover]   Artifact [" + artifact.getId() + "], scope = [" + artifact.getScope() + "]" );
        }
    }

    protected void setArtifactFactory( ArtifactFactory artifactFactory )
    {
        this.artifactFactory = artifactFactory;
    }

    protected void setArtifactResolver( ArtifactResolver artifactResolver )
    {
        this.artifactResolver = artifactResolver;
    }

    public Set getIncludes()
    {
        return this.includes;
    }

    public Set getExcludes()
    {
        return this.excludes;
    }

    public boolean includesAllSourceRoots()
    {
        return this.includesAllSourceRoots;
    }

    public boolean isUseFullyQualifiedJavaLang() {
        return useFullyQualifiedJavaLang;
    }

    public String getEncoding() {
        return encoding;
    }

    public Map getMethodContexts() {
        return methodContexts;
    }

    public Map getStatementContexts() {
        return statementContexts;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy