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

com.github.goldin.plugins.duplicates.DuplicatesFinderMojo.groovy Maven / Gradle / Ivy

package com.github.goldin.plugins.duplicates

import static com.github.goldin.plugins.common.GMojoUtils.*
import com.github.goldin.plugins.common.BaseGroovyMojo
import org.apache.maven.artifact.Artifact
import org.apache.maven.plugin.MojoFailureException
import org.jfrog.maven.annomojo.annotations.MojoGoal
import org.jfrog.maven.annomojo.annotations.MojoParameter
import org.jfrog.maven.annomojo.annotations.MojoPhase
import org.jfrog.maven.annomojo.annotations.MojoRequiresDependencyResolution

import java.util.zip.ZipEntry
import java.util.zip.ZipFile


/**
 * A plugin that finds duplicate classes in the scope(s) specified
 */
@MojoGoal ( 'find-duplicates' )
@MojoPhase ( 'process-resources' )
@MojoRequiresDependencyResolution ( 'test' )
@SuppressWarnings( [ 'StatelessClass', 'PublicInstanceField', 'NonFinalPublicField' ] )
class DuplicatesFinderMojo extends BaseGroovyMojo
{
    /**
     * Cache of file on the disk to classes it contains
     */
    private static final Map> CLASSES_CACHE = [:]


    @MojoParameter
    public String scopes = 'compile'

    @MojoParameter
    public boolean verbose = false

    @MojoParameter
    public boolean fail = true


    @Override
    void doExecute ()
    {
        /**
         * Mapping of File to Maven Artifact
         */
        Map f2A = [:]
        def time                = System.currentTimeMillis()

       /**
        * Makes sure map specified has a new list entry and adds a new value to it
        */
        def updateMap = { Map m, Object key, Object value ->
                          m[ key ] = m[ key ]?: []
                          m[ key ] << value }

        /**
         * Mapping of class names to files they were found it
         */
        Set             scopes  = split( this.scopes ) as Set
        Map> classes =
            project.artifacts.findAll { Artifact a -> scopes.contains( a.scope ) && ( a.type != 'pom' ) }.
                              // Artifact => File
                              collect { Artifact a ->
                                        File f   = resolveArtifact( a, false, true ).file
                                        f2A[ f ] = a
                                        if ( verbose ) { log.info( "Checking [$a]" ) }
                                        f }.
                              // Files => Mapping of class names to files
                              inject( [:] ){ Map map, File f ->
                                             classNames( f ).each{ String className -> updateMap( map, className, f ) }
                                             map }

        /**
         * Mapping of violating artifacts (Stringified list) to duplicate class names
         */
        Map> violations =
            classes.findAll{ String className, List files -> files.size() > 1 }.
                    // Class names with more then 1 files they were found in => Mapping of violating artifacts
                    inject( [:] ){ Map map, Map.Entry> entry ->
                                   String className          = entry.key
                                   String violatingArtifacts = entry.value.collect{ File f -> f2A[ f ] }.toString()
                                   updateMap( map, violatingArtifacts, className )
                                   map }

        log.info( "[${ f2A.size() }] artifact${ general().s( f2A.size())} analyzed in [${ System.currentTimeMillis() - time }] ms" )
        if ( violations ) { reportViolations( violations ) }
        else              { log.info( 'No duplicate libraries found' ) }
    }


    /**
     * Reads Zip archive and returns a list of class names stored in it.
     *
     * @param file Zip archive to read
     * @return list of class names stored in it
     */
    private List classNames ( File file )
    {
        if ( CLASSES_CACHE.containsKey( file ))
        {
            return CLASSES_CACHE[ file ]
        }

        ZipFile zip = new ZipFile( file )

        try
        {
            CLASSES_CACHE[ file ] = verify().notNull( zip.entries().findAll{ ZipEntry entry -> entry.name.endsWith( '.class' ) }.
                                                                    collect{ ZipEntry entry -> entry.name.replace( '/', '.' ).
                                                                                               replaceAll( /\.class$/, '' ) } )
        }
        finally { zip.close() }
    }


    /**
     * Reports violations found by throwing an exception or logging an error message.
     *
     * @param violations violations found
     */
    @SuppressWarnings( 'UseCollectMany' )
    private void reportViolations( Map> violations )
    {
        def message =
            '\nDuplicates found in:\n' +
            violations.collect{ String artifacts, List classes ->
                                [ "-=-= $artifacts =-=-" ] + ( verbose ? classes.sort().collect { " --- [$it]" } : [] ) }.
                       flatten().
                       grep().
                       join( constants().CRLF )

        if ( fail ) { throw new MojoFailureException( message )}
        else        { log.error( message )}
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy