From 5039e56a307952897c21d4e7a91093d5455cc068 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Thu, 29 Sep 2022 17:13:35 +0200 Subject: [PATCH 1/2] Upgrade to JUnit 5 --- pom.xml | 6 +++--- .../analyzer/ClassFileVisitorUtilsTest.java | 12 ++++++------ .../analyzer/CollectorClassFileVisitorTest.java | 7 ++++--- .../analyzer/DefaultClassAnalyzerTest.java | 14 +++++++------- .../analyzer/ProjectDependencyAnalysisTest.java | 2 +- .../ProjectDependencyAnalyzerExceptionTest.java | 3 ++- .../analyzer/asm/ASMDependencyAnalyzerTest.java | 2 +- .../analyzer/asm/DependencyVisitorTest.java | 14 ++++++++++---- .../analyzer/asm/ResultCollectorTest.java | 3 ++- 9 files changed, 36 insertions(+), 27 deletions(-) diff --git a/pom.xml b/pom.xml index d3ce7342..70e0abfd 100644 --- a/pom.xml +++ b/pom.xml @@ -104,9 +104,9 @@ test - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter + 5.9.0 test diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java index e1d1f878..00120889 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java @@ -19,11 +19,6 @@ * under the License. */ -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.junit.Before; -import org.junit.Test; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -37,6 +32,11 @@ import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -71,7 +71,7 @@ public void visitClass( String className, InputStream in ) } } - @Before + @BeforeEach public void setUp() { visitor = new MockVisitor(); diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java index 37579b17..5ccf9aa7 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitorTest.java @@ -19,12 +19,13 @@ * under the License. */ -import org.junit.Before; -import org.junit.Test; import java.util.HashSet; import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -37,7 +38,7 @@ public class CollectorClassFileVisitorTest { private CollectorClassFileVisitor visitor; - @Before + @BeforeEach public void setUp() { visitor = new CollectorClassFileVisitor(); diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java index 9bdfdb41..2316b645 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java @@ -19,11 +19,6 @@ * under the License. */ -import org.codehaus.plexus.util.IOUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -37,6 +32,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipException; +import org.codehaus.plexus.util.IOUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -50,7 +50,7 @@ public class DefaultClassAnalyzerTest { private Path file; - @Before + @BeforeEach public void setUp() throws IOException { file = Files.createTempFile( "test", ".jar" ); @@ -61,7 +61,7 @@ public void setUp() throws IOException } } - @After + @AfterEach public void cleanup() throws IOException { if ( file != null ) diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java index 09e519dd..1214e9d3 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java @@ -26,7 +26,7 @@ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.versioning.VersionRange; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerExceptionTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerExceptionTest.java index eece33e1..60e6f07c 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerExceptionTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerExceptionTest.java @@ -19,7 +19,8 @@ * under the License. */ -import org.junit.Test; + +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java index 27623e33..1ba6fe7f 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzerTest.java @@ -23,7 +23,7 @@ import java.util.Set; import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java index 4a51b154..ca0c47cb 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java @@ -19,9 +19,15 @@ * under the License. */ -import org.junit.Before; -import org.junit.Test; -import org.objectweb.asm.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureVisitor; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +43,7 @@ public class DependencyVisitorTest private DefaultClassVisitor visitor; private MethodVisitor mv; - @Before + @BeforeEach public void setUp() { AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor( resultCollector ); diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java index 723025b3..edec3463 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java @@ -26,8 +26,9 @@ import org.apache.maven.shared.dependency.analyzer.testcases.ArrayCases; import org.apache.maven.shared.dependency.analyzer.testcases.InnerClassCase; import org.apache.maven.shared.dependency.analyzer.testcases.MethodHandleCases; +import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; public class ResultCollectorTest { From 2c888109a0a58503e22c1e609c954c44d9ac0f3b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 2 Oct 2022 22:56:04 +0200 Subject: [PATCH 2/2] Switch to maven 4 and the new api --- pom.xml | 21 +- src/it/setup-mock-plugin/pom.xml | 26 +- .../main/java/it/test/MockAnalyzeMojo.java | 68 ++-- .../dependency/analyzer/ClassAnalyzer.java | 11 + .../dependency/analyzer/ClassFileVisitor.java | 11 +- .../analyzer/ClassFileVisitorUtils.java | 140 ++++++--- .../analyzer/CollectorClassFileVisitor.java | 3 +- .../analyzer/DefaultClassAnalyzer.java | 24 ++ .../DefaultProjectDependencyAnalyzer.java | 291 ++++++------------ .../analyzer/DependencyAnalyzer.java | 11 + .../analyzer/ProjectDependencyAnalysis.java | 74 ++--- .../analyzer/ProjectDependencyAnalyzer.java | 7 +- .../ProjectDependencyAnalyzerException.java | 4 +- .../analyzer/asm/ASMDependencyAnalyzer.java | 12 + .../asm/DependencyClassFileVisitor.java | 7 +- .../analyzer/ClassFileVisitorUtilsTest.java | 35 +-- .../analyzer/DefaultClassAnalyzerTest.java | 31 +- .../ProjectDependencyAnalysisTest.java | 33 +- .../analyzer/asm/ResultCollectorTest.java | 5 +- 19 files changed, 420 insertions(+), 394 deletions(-) diff --git a/pom.xml b/pom.xml index 70e0abfd..4c81f685 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ maven-dependency-analyzer jar - 1.13.1-SNAPSHOT + 2.0.0-SNAPSHOT Apache Maven Dependency Analyzer @@ -59,7 +59,8 @@ - 3.2.5 + 4.0.0-alpha-1-SNAPSHOT + 4.0.0-SNAPSHOT 8 2022-08-20T15:25:47Z @@ -68,12 +69,7 @@ org.apache.maven - maven-core - ${mavenVersion} - - - org.apache.maven - maven-artifact + maven-api-core ${mavenVersion} @@ -86,7 +82,7 @@ org.codehaus.plexus plexus-utils - 3.4.2 + ${mavenVersion} @@ -115,6 +111,12 @@ 3.23.1 test + + org.mockito + mockito-core + 4.8.0 + test + @@ -149,6 +151,7 @@ src/it/settings.xml verify + -e verify diff --git a/src/it/setup-mock-plugin/pom.xml b/src/it/setup-mock-plugin/pom.xml index 509185e2..ff691865 100644 --- a/src/it/setup-mock-plugin/pom.xml +++ b/src/it/setup-mock-plugin/pom.xml @@ -27,31 +27,19 @@ maven-plugin 1.0 + + 1.8 + 1.8 + 8 + + - - javax.inject - javax.inject - 1 - provided - - - org.apache.maven - maven-core - @mavenVersion@ - provided - org.apache.maven - maven-plugin-api + maven-api-core @mavenVersion@ provided - - org.apache.maven.plugin-tools - maven-plugin-tools-annotations - @maven.plugin.tools.version@ - provided - org.apache.maven.shared maven-dependency-analyzer diff --git a/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java b/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java index 8d9fe65c..aec1e5bd 100644 --- a/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java +++ b/src/it/setup-mock-plugin/src/main/java/it/test/MockAnalyzeMojo.java @@ -18,33 +18,34 @@ * under the License. */ -import javax.inject.Inject; - -import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.nio.file.Files; +import java.nio.file.Path; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.LifecyclePhase; -import org.apache.maven.plugins.annotations.Mojo; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.plugins.annotations.ResolutionScope; -import org.apache.maven.project.MavenProject; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Project; +import org.apache.maven.api.ResolutionScope; +import org.apache.maven.api.Session; +import org.apache.maven.api.plugin.Log; +import org.apache.maven.api.plugin.MojoException; +import org.apache.maven.api.plugin.annotations.Component; +import org.apache.maven.api.plugin.annotations.LifecyclePhase; +import org.apache.maven.api.plugin.annotations.Mojo; +import org.apache.maven.api.plugin.annotations.Parameter; import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis; import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer; -@Mojo( name = "mock-analyze", requiresDependencyResolution = ResolutionScope.TEST, +@Mojo( name = "mock-analyze", + requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.VERIFY ) -public class MockAnalyzeMojo extends AbstractMojo +public class MockAnalyzeMojo implements org.apache.maven.api.plugin.Mojo { class UnixPrintWiter extends PrintWriter { - public UnixPrintWiter( File file ) throws FileNotFoundException + public UnixPrintWiter( Path file ) throws FileNotFoundException { - super( file ); + super( file.toFile() ); } @Override @@ -54,51 +55,62 @@ public void println() } } - @Inject + @Component private ProjectDependencyAnalyzer analyzer; - @Inject - private MavenProject project; + @Parameter( defaultValue = "${session}", readonly = true, required = true ) + private Session session; + + @Parameter( defaultValue = "${project}", readonly = true, required = true ) + private Project project; @Parameter( defaultValue = "${project.build.directory}/analysis.txt", readonly = true ) - private File output; + private Path output; + + @Component + private Log log; @Override - public void execute() throws MojoExecutionException, MojoFailureException + public void execute() throws MojoException { try { - ProjectDependencyAnalysis analysis = analyzer.analyze( project ); + ProjectDependencyAnalysis analysis = analyzer.analyze( session, project ); - Files.createDirectories( output.toPath().getParent() ); + Files.createDirectories( output.getParent() ); try ( PrintWriter printWriter = new UnixPrintWiter( output ) ) { printWriter.println(); printWriter.println( "UsedDeclaredArtifacts:" ); - analysis.getUsedDeclaredArtifacts().forEach( a -> printWriter.println( " " + a ) ); + analysis.getUsedDeclaredArtifacts().forEach( a -> printWriter.println( " " + toString( a ) ) ); printWriter.println(); printWriter.println( "UsedUndeclaredArtifactsWithClasses:" ); analysis.getUsedUndeclaredArtifactsWithClasses().forEach( ( a, c ) -> { - printWriter.println( " " + a ); + printWriter.println( " " + toString( a ) ); c.forEach( i -> printWriter.println( " " + i ) ); } ); printWriter.println(); printWriter.println( "UnusedDeclaredArtifacts:" ); - analysis.getUnusedDeclaredArtifacts().forEach( a -> printWriter.println( " " + a ) ); + analysis.getUnusedDeclaredArtifacts().forEach( a -> printWriter.println( " " + toString( a ) ) ); printWriter.println(); printWriter.println( "TestArtifactsWithNonTestScope:" ); - analysis.getTestArtifactsWithNonTestScope().forEach( a -> printWriter.println( " " + a ) ); + analysis.getTestArtifactsWithNonTestScope().forEach( a -> printWriter.println( " " + toString( a ) ) ); } } catch ( Exception e ) { - throw new MojoExecutionException( "analyze failed", e ); + throw new MojoException( "analyze failed", e ); } - getLog().info( "Analyze done" ); + log.info( "Analyze done" ); + } + + private String toString( Dependency a ) + { + return a.key() + ":" + a.getScope().id(); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java index 0ba08dfb..14d35306 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassAnalyzer.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.Set; /** @@ -40,4 +41,14 @@ public interface ClassAnalyzer */ Set analyze( URL url ) throws IOException; + + /** + *

analyze.

+ * + * @param path the JAR file or directory to analyze + * @return a {@link java.util.Set} object + * @throws java.io.IOException if any + */ + Set analyze( Path path ) + throws IOException; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitor.java index ffa87d59..e6923869 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitor.java @@ -19,6 +19,7 @@ * under the License. */ +import java.io.IOException; import java.io.InputStream; /** @@ -34,5 +35,13 @@ public interface ClassFileVisitor * @param className a {@link java.lang.String} object. * @param in a {@link java.io.InputStream} object. */ - void visitClass( String className, InputStream in ); + void visitClass( String className, InputStreamProvider in ); + + /** + * Provider for the input stream on the class file + */ + interface InputStreamProvider + { + InputStream open() throws IOException; + } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtils.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtils.java index 412b02aa..2cd2bb34 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtils.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtils.java @@ -19,17 +19,20 @@ * under the License. */ -import org.codehaus.plexus.util.DirectoryScanner; - import java.io.File; -import java.io.FileInputStream; +import java.io.FilterInputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import java.util.zip.ZipFile; + +import org.apache.maven.shared.dependency.analyzer.ClassFileVisitor.InputStreamProvider; +import org.codehaus.plexus.util.DirectoryScanner; /** * Utility to visit classes in a library given either as a jar file or an exploded directory. @@ -52,90 +55,137 @@ private ClassFileVisitorUtils() * @throws java.io.IOException if any. */ public static void accept( URL url, ClassFileVisitor visitor ) - throws IOException + throws IOException { - if ( url.getPath().endsWith( ".jar" ) ) - { - acceptJar( url, visitor ); - } - else if ( url.getProtocol().equalsIgnoreCase( "file" ) ) + if ( url.getProtocol().equals( "file" ) ) { try { - File file = new File( new URI( url.toString() ) ); - - if ( file.isDirectory() ) - { - acceptDirectory( file, visitor ); - } - else if ( file.exists() ) - { - throw new IllegalArgumentException( "Cannot accept visitor on URL: " + url ); - } + Path path = Paths.get( url.toURI() ); + accept( path, visitor ); } catch ( URISyntaxException exception ) { - throw new IllegalArgumentException( "Cannot accept visitor on URL: " + url, - exception ); + throw new IllegalArgumentException( "Cannot accept visitor on URL: " + url, exception ); } } + else if ( url.getPath().endsWith( ".jar" ) ) + { + acceptJarUrl( url, visitor ); + } else { throw new IllegalArgumentException( "Cannot accept visitor on URL: " + url ); } } + /** + *

accept.

+ * + * @param path a {@link Path} object. + * @param visitor a {@link org.apache.maven.shared.dependency.analyzer.ClassFileVisitor} object. + * @throws java.io.IOException if any. + */ + public static void accept( Path path, ClassFileVisitor visitor ) + throws IOException + { + if ( Files.isDirectory( path ) ) + { + acceptDirectory( path, visitor ); + } + else if ( Files.isRegularFile( path ) && path.toString().endsWith( ".jar" ) ) + { + acceptJarFile( path, visitor ); + } + else if ( Files.exists( path ) ) + { + throw new IllegalArgumentException( "Cannot accept visitor on path: " + path ); + } + } + // private methods -------------------------------------------------------- - private static void acceptJar( URL url, ClassFileVisitor visitor ) - throws IOException + private static void acceptJarFile( Path file, ClassFileVisitor visitor ) + throws IOException + { + try ( ZipFile zip = new ZipFile( file.toFile() ) ) + { + zip.stream().forEach( entry -> + { + String name = entry.getName(); + // ignore files like package-info.class and module-info.class + if ( name.endsWith( ".class" ) && name.indexOf( '-' ) == -1 ) + { + visitClass( name, () -> zip.getInputStream( entry ), visitor ); + } + } ); + } + catch ( Exception e ) + { + throw new ProjectDependencyAnalyzerException( "Cannot process jar entry on path: " + file, e ); + } + } + + private static void acceptJarUrl( URL url, ClassFileVisitor visitor ) + throws IOException { - try ( JarInputStream in = new JarInputStream( url.openStream() ) ) + try ( JarInputStream jis = new JarInputStream( url.openStream() ) ) { JarEntry entry; - while ( ( entry = in.getNextJarEntry() ) != null ) + while ( ( entry = jis.getNextJarEntry() ) != null ) { String name = entry.getName(); // ignore files like package-info.class and module-info.class if ( name.endsWith( ".class" ) && name.indexOf( '-' ) == -1 ) { - visitClass( name, in, visitor ); + visitClass( name, () -> new FilterInputStream( jis ) + { + @Override + public void close() throws IOException + { + jis.closeEntry(); + } + }, visitor ); } } } + catch ( Exception e ) + { + throw new ProjectDependencyAnalyzerException( "Cannot process jar entry on url: " + url, e ); + } } - private static void acceptDirectory( File directory, ClassFileVisitor visitor ) - throws IOException + private static void acceptDirectory( Path directory, ClassFileVisitor visitor ) { - if ( !directory.isDirectory() ) + if ( !Files.isDirectory( directory ) ) { throw new IllegalArgumentException( "File is not a directory" ); } - DirectoryScanner scanner = new DirectoryScanner(); - - scanner.setBasedir( directory ); - scanner.setIncludes( new String[] { "**/*.class" } ); - - scanner.scan(); + try + { + DirectoryScanner scanner = new DirectoryScanner(); - String[] paths = scanner.getIncludedFiles(); + scanner.setBasedir( directory.toFile() ); + scanner.setIncludes( new String[] {"**/*.class"} ); - for ( String path : paths ) - { - path = path.replace( File.separatorChar, '/' ); + scanner.scan(); - File file = new File( directory, path ); + String[] paths = scanner.getIncludedFiles(); - try ( InputStream in = new FileInputStream( file ) ) + for ( String path : paths ) { - visitClass( path, in, visitor ); + String normpath = path.replace( File.separatorChar, '/' ); + visitClass( normpath, () -> Files.newInputStream( directory.resolve( normpath ) ), visitor ); } } + catch ( Exception e ) + { + throw new ProjectDependencyAnalyzerException( "Cannot process directory on path: " + directory, e ); + } } - private static void visitClass( String path, InputStream in, ClassFileVisitor visitor ) + private static void visitClass( String path, InputStreamProvider in, ClassFileVisitor visitor ) { if ( !path.endsWith( ".class" ) ) { diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java index d9226a8c..e094812e 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/CollectorClassFileVisitor.java @@ -19,7 +19,6 @@ * under the License. */ -import java.io.InputStream; import java.util.HashSet; import java.util.Set; @@ -43,7 +42,7 @@ public CollectorClassFileVisitor() } /** {@inheritDoc} */ - public void visitClass( String className, InputStream in ) + public void visitClass( String className, InputStreamProvider in ) { // inner classes have equivalent compilation requirement as container class if ( className.indexOf( '$' ) < 0 ) diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java index 6a79d4d1..f75ce3e3 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzer.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.Set; import java.util.zip.ZipException; @@ -60,4 +61,27 @@ public Set analyze( URL url ) return visitor.getClasses(); } + + /** {@inheritDoc} */ + public Set analyze( Path path ) + throws IOException + { + CollectorClassFileVisitor visitor = new CollectorClassFileVisitor(); + + try + { + ClassFileVisitorUtils.accept( path, visitor ); + } + catch ( ZipException e ) + { + // since the current ZipException gives no indication what jar file is corrupted + // we prefer to wrap another ZipException for better error visibility + ZipException ze = + new ZipException( "Cannot process Jar entry on path: " + path + " due to " + e.getMessage() ); + ze.initCause( e ); + throw ze; + } + + return visitor.getClasses(); + } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java index 61153345..f5a6416a 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java @@ -23,22 +23,25 @@ import javax.inject.Named; import javax.inject.Singleton; -import java.io.File; import java.io.IOException; -import java.net.URL; -import java.util.Collections; -import java.util.Enumeration; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.project.MavenProject; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Node; +import org.apache.maven.api.Project; +import org.apache.maven.api.Scope; +import org.apache.maven.api.Session; /** *

DefaultProjectDependencyAnalyzer class.

@@ -53,55 +56,84 @@ public class DefaultProjectDependencyAnalyzer /** * ClassAnalyzer */ - @Inject - private ClassAnalyzer classAnalyzer; + private final ClassAnalyzer classAnalyzer; /** * DependencyAnalyzer */ + private final DependencyAnalyzer dependencyAnalyzer; + @Inject - private DependencyAnalyzer dependencyAnalyzer; + public DefaultProjectDependencyAnalyzer( ClassAnalyzer classAnalyzer, DependencyAnalyzer dependencyAnalyzer ) + { + this.classAnalyzer = classAnalyzer; + this.dependencyAnalyzer = dependencyAnalyzer; + } /** {@inheritDoc} */ - public ProjectDependencyAnalysis analyze( MavenProject project ) + public ProjectDependencyAnalysis analyze( Session session, Project project ) throws ProjectDependencyAnalyzerException { try { - Map> artifactClassMap = buildArtifactClassMap( project ); - - Set mainDependencyClasses = buildMainDependencyClasses( project ); - Set testDependencyClasses = buildTestDependencyClasses( project ); - - Set dependencyClasses = new HashSet<>(); - dependencyClasses.addAll( mainDependencyClasses ); - dependencyClasses.addAll( testDependencyClasses ); - - Set testOnlyDependencyClasses = buildTestOnlyDependencyClasses( mainDependencyClasses, - testDependencyClasses ); - - Map> usedArtifacts = buildUsedArtifacts( artifactClassMap, dependencyClasses ); - Set mainUsedArtifacts = buildUsedArtifacts( artifactClassMap, mainDependencyClasses ).keySet(); - - Set testArtifacts = buildUsedArtifacts( artifactClassMap, testOnlyDependencyClasses ).keySet(); - Set testOnlyArtifacts = removeAll( testArtifacts, mainUsedArtifacts ); - - Set declaredArtifacts = buildDeclaredArtifacts( project ); - Set usedDeclaredArtifacts = new LinkedHashSet<>( declaredArtifacts ); - usedDeclaredArtifacts.retainAll( usedArtifacts.keySet() ); - - Map> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>( usedArtifacts ); - Set usedUndeclaredArtifacts = removeAll( - usedUndeclaredArtifactsWithClasses.keySet(), declaredArtifacts ); + // Collect dependencies + Node root = session.collectDependencies( project ); + + // Make sure all artifacts are resolved + session.resolveArtifacts( root.stream() + .skip( 1 ) // skip the root + .map( Node::getDependency ) + .map( session::createArtifactCoordinate ) + .collect( Collectors.toList() ) ); + + // Build dependency --> classes mapping and class -> dependency + Map> artifactClassMap = root.stream() + .skip( 1 ) + .map( Node::getDependency ) + .collect( Collectors.toMap( + d -> d, + d -> getClasses( session.getArtifactPath( d ).get() ), + DefaultProjectDependencyAnalyzer::union, + LinkedHashMap::new ) // note that we want to keep the ordering + ); + Map classToDependencyMap = new HashMap<>(); + artifactClassMap.forEach( ( u, vs ) -> vs.forEach( v -> classToDependencyMap.putIfAbsent( v, u ) ) ); + + // Compute set of classes for main and test + Set mainDependencyClasses = buildDependencyClasses( project.getBuild().getOutputDirectory() ); + Set testDependencyClasses = buildDependencyClasses( project.getBuild().getTestOutputDirectory() ); + Set dependencyClasses = union( mainDependencyClasses, testDependencyClasses ); + + // Compute dependencies used by main and test classes + Map> usedArtifactsWithClasses = + buildUsedArtifacts( classToDependencyMap, dependencyClasses ); + Set usedArtifacts = usedArtifactsWithClasses.keySet(); + Set mainUsedArtifacts = + buildUsedArtifacts( classToDependencyMap, mainDependencyClasses ).keySet(); + Set testArtifacts = + buildUsedArtifacts( classToDependencyMap, testDependencyClasses ).keySet(); + Set testOnlyArtifacts = remove( testArtifacts, mainUsedArtifacts ); + + // Compute direct dependencies, keep order + Set declaredArtifacts = root.getChildren().stream() + .map( Node::getDependency ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ); + + Set usedDeclaredArtifacts = retain( declaredArtifacts, usedArtifacts ); + + Set usedUndeclaredArtifacts = remove( usedArtifacts, declaredArtifacts ); + Map> usedUndeclaredArtifactsWithClasses = + new LinkedHashMap<>( usedArtifactsWithClasses ); usedUndeclaredArtifactsWithClasses.keySet().retainAll( usedUndeclaredArtifacts ); - Set unusedDeclaredArtifacts = new LinkedHashSet<>( declaredArtifacts ); - unusedDeclaredArtifacts = removeAll( unusedDeclaredArtifacts, usedArtifacts.keySet() ); + Set unusedDeclaredArtifacts = remove( declaredArtifacts, usedArtifacts ); - Set testArtifactsWithNonTestScope = getTestArtifactsWithNonTestScope( testOnlyArtifacts ); + Set testArtifactsWithNonTestScope = getTestArtifactsWithNonTestScope( testOnlyArtifacts ); - return new ProjectDependencyAnalysis( usedDeclaredArtifacts, usedUndeclaredArtifactsWithClasses, - unusedDeclaredArtifacts, testArtifactsWithNonTestScope ); + return new ProjectDependencyAnalysis( usedDeclaredArtifacts, + usedUndeclaredArtifactsWithClasses, + unusedDeclaredArtifacts, + testArtifactsWithNonTestScope ); } catch ( IOException exception ) { @@ -109,178 +141,59 @@ public ProjectDependencyAnalysis analyze( MavenProject project ) } } - /** - * This method defines a new way to remove the artifacts by using the conflict id. We don't care about the version - * here because there can be only 1 for a given artifact anyway. - * - * @param start initial set - * @param remove set to exclude - * @return set with remove excluded - */ - private static Set removeAll( Set start, Set remove ) + private static Set remove( Set start, Set remove ) { - Set results = new LinkedHashSet<>( start.size() ); - - for ( Artifact artifact : start ) - { - boolean found = false; - - for ( Artifact artifact2 : remove ) - { - if ( artifact.getDependencyConflictId().equals( artifact2.getDependencyConflictId() ) ) - { - found = true; - break; - } - } - - if ( !found ) - { - results.add( artifact ); - } - } - - return results; + return start.stream().filter( a -> !remove.contains( a ) ).collect( toLinkedSet() ); } - private static Set getTestArtifactsWithNonTestScope( Set testOnlyArtifacts ) + private static Set union( Set s1, Set s2 ) { - Set nonTestScopeArtifacts = new LinkedHashSet<>(); - - for ( Artifact artifact : testOnlyArtifacts ) - { - if ( artifact.getScope().equals( "compile" ) ) - { - nonTestScopeArtifacts.add( artifact ); - } - } - - return nonTestScopeArtifacts; + return Stream.concat( s1.stream(), s2.stream() ).collect( toLinkedSet() ); } - private Map> buildArtifactClassMap( MavenProject project ) - throws IOException + private static Set retain( Set s1, Set s2 ) { - Map> artifactClassMap = new LinkedHashMap<>(); - - Set dependencyArtifacts = project.getArtifacts(); - - for ( Artifact artifact : dependencyArtifacts ) - { - File file = artifact.getFile(); - - if ( file != null && file.getName().endsWith( ".jar" ) ) - { - // optimized solution for the jar case - - try ( JarFile jarFile = new JarFile( file ) ) - { - Enumeration jarEntries = jarFile.entries(); - - Set classes = new HashSet<>(); - - while ( jarEntries.hasMoreElements() ) - { - String entry = jarEntries.nextElement().getName(); - if ( entry.endsWith( ".class" ) ) - { - String className = entry.replace( '/', '.' ); - className = className.substring( 0, className.length() - ".class".length() ); - classes.add( className ); - } - } - - artifactClassMap.put( artifact, classes ); - } - } - else if ( file != null && file.isDirectory() ) - { - URL url = file.toURI().toURL(); - Set classes = classAnalyzer.analyze( url ); - - artifactClassMap.put( artifact, classes ); - } - } - - return artifactClassMap; + return s1.stream().filter( s2::contains ).collect( toLinkedSet() ); } - private static Set buildTestOnlyDependencyClasses( Set mainDependencyClasses, - Set testDependencyClasses ) + private static Collector> toLinkedSet() { - Set testOnlyDependencyClasses = new HashSet<>( testDependencyClasses ); - testOnlyDependencyClasses.removeAll( mainDependencyClasses ); - return testOnlyDependencyClasses; + return Collectors.toCollection( LinkedHashSet::new ); } - private Set buildMainDependencyClasses( MavenProject project ) - throws IOException + private static Set getTestArtifactsWithNonTestScope( Set testOnlyArtifacts ) { - String outputDirectory = project.getBuild().getOutputDirectory(); - return buildDependencyClasses( outputDirectory ); + return testOnlyArtifacts.stream() + .filter( d -> d.getScope().equals( Scope.COMPILE ) ) + .collect( toLinkedSet() ); } - private Set buildTestDependencyClasses( MavenProject project ) - throws IOException + private Set getClasses( Path file ) { - String testOutputDirectory = project.getBuild().getTestOutputDirectory(); - return buildDependencyClasses( testOutputDirectory ); + try + { + return classAnalyzer.analyze( file ); + } + catch ( IOException e ) + { + throw new ProjectDependencyAnalyzerException( "Error while processing jar " + file, e ); + } } private Set buildDependencyClasses( String path ) throws IOException { - URL url = new File( path ).toURI().toURL(); - - return dependencyAnalyzer.analyze( url ); - } - - private static Set buildDeclaredArtifacts( MavenProject project ) - { - Set declaredArtifacts = project.getDependencyArtifacts(); - - if ( declaredArtifacts == null ) - { - declaredArtifacts = Collections.emptySet(); - } - - return declaredArtifacts; + return dependencyAnalyzer.analyze( Paths.get( path ) ); } - private static Map> buildUsedArtifacts( Map> artifactClassMap, - Set dependencyClasses ) + private static Map> buildUsedArtifacts( Map classToDependencyMap, + Set dependencyClasses ) { - Map> usedArtifacts = new HashMap<>(); - - for ( String className : dependencyClasses ) - { - Artifact artifact = findArtifactForClassName( artifactClassMap, className ); - - if ( artifact != null ) - { - Set classesFromArtifact = usedArtifacts.get( artifact ); - if ( classesFromArtifact == null ) - { - classesFromArtifact = new HashSet(); - usedArtifacts.put( artifact, classesFromArtifact ); - } - classesFromArtifact.add( className ); - } - } - + Map> usedArtifacts = new HashMap<>(); + dependencyClasses.forEach( cl -> + Optional.ofNullable( classToDependencyMap.get( cl ) ) + .ifPresent( dep -> usedArtifacts.computeIfAbsent( dep, k -> new HashSet<>() ).add( cl ) ) ); return usedArtifacts; } - private static Artifact findArtifactForClassName( Map> artifactClassMap, String className ) - { - for ( Map.Entry> entry : artifactClassMap.entrySet() ) - { - if ( entry.getValue().contains( className ) ) - { - return entry.getKey(); - } - } - - return null; - } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java index aed1fb06..40babafc 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.Set; /** @@ -40,4 +41,14 @@ public interface DependencyAnalyzer */ Set analyze( URL url ) throws IOException; + + /** + *

analyze.

+ * + * @param path the JAR file or directory to analyze + * @return the set of class names referenced by the library + * @throws IOException if an error occurs reading a JAR or .class file + */ + Set analyze( Path path ) + throws IOException; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java index 0be4ffd3..4cfc38bc 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java @@ -28,7 +28,9 @@ import java.util.Map; import java.util.Set; -import org.apache.maven.artifact.Artifact; +import org.apache.maven.api.Artifact; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Scope; /** * Project dependencies analysis result. @@ -39,20 +41,20 @@ public class ProjectDependencyAnalysis { // fields ----------------------------------------------------------------- - private final Set usedDeclaredArtifacts; + private final Set usedDeclaredArtifacts; - private final Map> usedUndeclaredArtifacts; + private final Map> usedUndeclaredArtifacts; - private final Set unusedDeclaredArtifacts; + private final Set unusedDeclaredArtifacts; - private final Set testArtifactsWithNonTestScope; + private final Set testArtifactsWithNonTestScope; /** *

Constructor for ProjectDependencyAnalysis.

*/ public ProjectDependencyAnalysis() { - this( null, (Map>) null, null, null ); + this( null, (Map>) null, null, null ); } /** @@ -62,11 +64,12 @@ public ProjectDependencyAnalysis() * @param usedUndeclaredArtifacts artifacts used but not declared * @param unusedDeclaredArtifacts artifacts declared but not used */ - public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, Set usedUndeclaredArtifacts, - Set unusedDeclaredArtifacts ) + public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, + Set usedUndeclaredArtifacts, + Set unusedDeclaredArtifacts ) { this( usedDeclaredArtifacts, usedUndeclaredArtifacts, - unusedDeclaredArtifacts, Collections.emptySet() ); + unusedDeclaredArtifacts, Collections.emptySet() ); } /** @@ -77,9 +80,10 @@ public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, Set usedDeclaredArtifacts, Set usedUndeclaredArtifacts, - Set unusedDeclaredArtifacts, - Set testArtifactsWithNonTestScope ) + public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, + Set usedUndeclaredArtifacts, + Set unusedDeclaredArtifacts, + Set testArtifactsWithNonTestScope ) { this( usedDeclaredArtifacts, mapWithKeys( usedUndeclaredArtifacts ), @@ -87,10 +91,10 @@ public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, Set usedDeclaredArtifacts, - Map> usedUndeclaredArtifacts, - Set unusedDeclaredArtifacts, - Set testArtifactsWithNonTestScope ) + public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, + Map> usedUndeclaredArtifacts, + Set unusedDeclaredArtifacts, + Set testArtifactsWithNonTestScope ) { this.usedDeclaredArtifacts = safeCopy( usedDeclaredArtifacts ); this.usedUndeclaredArtifacts = safeCopy( usedUndeclaredArtifacts ); @@ -103,7 +107,7 @@ public ProjectDependencyAnalysis( Set usedDeclaredArtifacts, * * @return artifacts both used and declared */ - public Set getUsedDeclaredArtifacts() + public Set getUsedDeclaredArtifacts() { return safeCopy( usedDeclaredArtifacts ); } @@ -113,7 +117,7 @@ public Set getUsedDeclaredArtifacts() * * @return artifacts used but not declared */ - public Set getUsedUndeclaredArtifacts() + public Set getUsedUndeclaredArtifacts() { return safeCopy( usedUndeclaredArtifacts.keySet() ); } @@ -123,7 +127,7 @@ public Set getUsedUndeclaredArtifacts() * * @return artifacts used but not declared */ - public Map> getUsedUndeclaredArtifactsWithClasses() + public Map> getUsedUndeclaredArtifactsWithClasses() { return safeCopy( usedUndeclaredArtifacts ); } @@ -133,7 +137,7 @@ public Map> getUsedUndeclaredArtifactsWithClasses() * * @return artifacts declared but not used */ - public Set getUnusedDeclaredArtifacts() + public Set getUnusedDeclaredArtifacts() { return safeCopy( unusedDeclaredArtifacts ); } @@ -143,7 +147,7 @@ public Set getUnusedDeclaredArtifacts() * * @return artifacts only used in tests but not declared with test scope */ - public Set getTestArtifactsWithNonTestScope() + public Set getTestArtifactsWithNonTestScope() { return safeCopy( testArtifactsWithNonTestScope ); } @@ -156,8 +160,8 @@ public Set getTestArtifactsWithNonTestScope() */ public ProjectDependencyAnalysis ignoreNonCompile() { - Set filteredUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts ); - filteredUnusedDeclared.removeIf( artifact -> !artifact.getScope().equals( Artifact.SCOPE_COMPILE ) ); + Set filteredUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts ); + filteredUnusedDeclared.removeIf( artifact -> !artifact.getScope().equals( Scope.COMPILE ) ); return new ProjectDependencyAnalysis( usedDeclaredArtifacts, usedUndeclaredArtifacts, filteredUnusedDeclared, testArtifactsWithNonTestScope ); @@ -180,13 +184,13 @@ public ProjectDependencyAnalysis forceDeclaredDependenciesUsage( String[] forceU { Set forced = new HashSet<>( Arrays.asList( forceUsedDependencies ) ); - Set forcedUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts ); - Set forcedUsedDeclared = new HashSet<>( usedDeclaredArtifacts ); + Set forcedUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts ); + Set forcedUsedDeclared = new HashSet<>( usedDeclaredArtifacts ); - Iterator iter = forcedUnusedDeclared.iterator(); + Iterator iter = forcedUnusedDeclared.iterator(); while ( iter.hasNext() ) { - Artifact artifact = iter.next(); + Dependency artifact = iter.next(); if ( forced.remove( artifact.getGroupId() + ':' + artifact.getArtifactId() ) ) { @@ -314,21 +318,21 @@ public String toString() // private methods -------------------------------------------------------- - private Set safeCopy( Set set ) + private Set safeCopy( Set set ) { return ( set == null ) ? Collections.emptySet() : Collections.unmodifiableSet( new LinkedHashSet<>( set ) ); } - private static Map> safeCopy( Map> origMap ) + private static Map> safeCopy( Map> origMap ) { if ( origMap == null ) { return Collections.emptyMap(); } - Map> map = new HashMap<>(); + Map> map = new HashMap<>(); - for ( Map.Entry> e : origMap.entrySet() ) + for ( Map.Entry> e : origMap.entrySet() ) { map.put( e.getKey(), Collections.unmodifiableSet( new LinkedHashSet<>( e.getValue() ) ) ); } @@ -336,18 +340,18 @@ private static Map> safeCopy( Map> o return map; } - private static Map> mapWithKeys( Set keys ) + private static Map> mapWithKeys( Set keys ) { if ( keys == null ) { return Collections.emptyMap(); } - Map> map = new HashMap<>(); + Map> map = new HashMap<>(); - for ( Artifact k : keys ) + for ( Dependency k : keys ) { - map.put( k, Collections.emptySet() ); + map.put( k, Collections.emptySet() ); } return map; diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java index ca7f4b8a..560f2294 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzer.java @@ -19,7 +19,8 @@ * under the License. */ -import org.apache.maven.project.MavenProject; +import org.apache.maven.api.Project; +import org.apache.maven.api.Session; /** * Analyze a project's declared dependencies and effective classes used to find which artifacts are: @@ -37,10 +38,10 @@ public interface ProjectDependencyAnalyzer /** *

analyze.

* - * @param project a {@link org.apache.maven.project.MavenProject} object + * @param project a {@link Project} object * @return a {@link org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis} object * @throws org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException if any */ - ProjectDependencyAnalysis analyze( MavenProject project ) + ProjectDependencyAnalysis analyze( Session session, Project project ) throws ProjectDependencyAnalyzerException; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerException.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerException.java index de7abaa1..8dc4cdf5 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerException.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalyzerException.java @@ -19,13 +19,15 @@ * under the License. */ +import org.apache.maven.api.services.MavenException; + /** *

ProjectDependencyAnalyzerException class.

* * @author Mark Hobson */ public class ProjectDependencyAnalyzerException - extends Exception + extends MavenException { /** * The serialisation unique ID. diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java index ebe27381..3a5f5c4b 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.Set; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitorUtils; @@ -49,4 +50,15 @@ public Set analyze( URL url ) return visitor.getDependencies(); } + + /** {@inheritDoc} */ + public Set analyze( Path path ) + throws IOException + { + DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); + + ClassFileVisitorUtils.accept( path, visitor ); + + return visitor.getDependencies(); + } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java index f8a1e795..e1f79b20 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitor; +import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException; import org.codehaus.plexus.util.IOUtil; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; @@ -52,9 +53,9 @@ public DependencyClassFileVisitor() } /** {@inheritDoc} */ - public void visitClass( String className, InputStream in ) + public void visitClass( String className, InputStreamProvider provider ) { - try + try ( InputStream in = provider.open() ) { byte[] byteCode = IOUtil.toByteArray( in ); ClassReader reader = new ClassReader( byteCode ); @@ -76,7 +77,7 @@ public void visitClass( String className, InputStream in ) } catch ( IOException exception ) { - exception.printStackTrace(); + throw new ProjectDependencyAnalyzerException( "Unable to process class " + className, exception ); } catch ( IndexOutOfBoundsException e ) { diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java index 00120889..2b623c65 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ClassFileVisitorUtilsTest.java @@ -20,7 +20,6 @@ */ import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -38,7 +37,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Tests ClassFileVisitorUtils. @@ -56,10 +55,10 @@ private static class MockVisitor implements ClassFileVisitor final List data = new ArrayList<>(); @Override - public void visitClass( String className, InputStream in ) + public void visitClass( String className, InputStreamProvider provider ) { classNames.add( className ); - try + try ( InputStream in = provider.open() ) { List lines = IOUtils.readLines( in, StandardCharsets.UTF_8 ); data.addAll( lines ); @@ -83,7 +82,7 @@ public void testAcceptJar() throws IOException File file = File.createTempFile( "test", ".jar" ); file.deleteOnExit(); - try ( JarOutputStream out = new JarOutputStream( new FileOutputStream( file ) ) ) + try ( JarOutputStream out = new JarOutputStream( Files.newOutputStream( file.toPath() ) ) ) { addZipEntry( out, "a/b/c.class", "class a.b.c" ); addZipEntry( out, "x/y/z.class", "class x.y.z" ); @@ -103,7 +102,7 @@ public void testAcceptJarWithNonClassEntry() throws IOException File file = File.createTempFile( "test", ".jar" ); file.deleteOnExit(); - try ( JarOutputStream out = new JarOutputStream( new FileOutputStream( file ) ) ) + try ( JarOutputStream out = new JarOutputStream( Files.newOutputStream( file.toPath() ) ) ) { addZipEntry( out, "a/b/c.jpg", "jpeg a.b.c" ); } @@ -157,15 +156,9 @@ public void testAcceptWithFile() throws IOException URL url = file.toURI().toURL(); - try - { - ClassFileVisitorUtils.accept( url, visitor ); - fail( "expected IllegalArgumentException" ); - } - catch ( IllegalArgumentException exception ) - { - assertThat( exception ).hasMessage( "Cannot accept visitor on URL: " + url ); - } + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, () -> ClassFileVisitorUtils.accept( url, visitor ) ); + assertThat( exception ).hasMessage( "Cannot accept visitor on path: " + file ); } @Test @@ -173,15 +166,9 @@ public void testAcceptWithUnsupportedScheme() throws IOException { URL url = new URL( "http://localhost/" ); - try - { - ClassFileVisitorUtils.accept( url, visitor ); - fail( "expected IllegalArgumentException" ); - } - catch ( IllegalArgumentException exception ) - { - assertThat( exception ).hasMessage( "Cannot accept visitor on URL: " + url ); - } + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, () -> ClassFileVisitorUtils.accept( url, visitor ) ); + assertThat( exception ).hasMessage( "Cannot accept visitor on URL: " + url ); } private void writeToFile( Path parent, String file, String data ) throws IOException diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java index 2316b645..0e8e1cc2 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultClassAnalyzerTest.java @@ -19,6 +19,7 @@ * under the License. */ +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -26,12 +27,15 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.HashSet; import java.util.Set; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; +import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer; import org.codehaus.plexus.util.IOUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -39,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Tests DefaultClassAnalyzer. @@ -88,27 +93,19 @@ public void testAnalyzeBadJar() throws IOException { //to reproduce MDEP-143 // corrupt the jar file by altering its contents - FileInputStream fis = new FileInputStream( file.toFile() ); ByteArrayOutputStream baos = new ByteArrayOutputStream( 100 ); - IOUtil.copy( fis, baos, 100 ); - fis.close(); + Files.copy( file, baos ); byte[] ba = baos.toByteArray(); - ba[50] = 1; - FileOutputStream fos = new FileOutputStream( file.toFile() ); - IOUtil.copy( ba, fos ); - fos.close(); + ba[49] = 1; + Files.copy( new ByteArrayInputStream( ba ), file, StandardCopyOption.REPLACE_EXISTING ); - ClassAnalyzer analyzer = new DefaultClassAnalyzer(); + DependencyAnalyzer analyzer = new ASMDependencyAnalyzer(); - try - { - analyzer.analyze( file.toUri().toURL() ); - fail( "Exception expected" ); - } - catch ( ZipException e ) - { - assertThat( e ).hasMessageStartingWith( "Cannot process Jar entry on URL:" ); - } + ProjectDependencyAnalyzerException exception = assertThrows( ProjectDependencyAnalyzerException.class, () -> analyzer.analyze( file.toUri().toURL() ) ); + assertThat( exception ).hasMessageStartingWith( "Cannot process jar entry on path:" ); + assertThat( exception.getCause() ).isInstanceOf( ProjectDependencyAnalyzerException.class ) + .hasMessageStartingWith( "Unable to process class " ); + assertThat( exception.getCause().getCause() ).isInstanceOf( ZipException.class ); } private void addZipEntry( JarOutputStream out, String fileName, String content ) throws IOException diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java index 1214e9d3..70a78e91 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysisTest.java @@ -23,12 +23,13 @@ import java.util.HashSet; import java.util.Set; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.DefaultArtifact; -import org.apache.maven.artifact.versioning.VersionRange; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Scope; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; /** * Tests ProjectDependencyAnalysis. @@ -41,10 +42,10 @@ public class ProjectDependencyAnalysisTest @Test public void testConstructor() { - Set usedDeclaredArtifacts = new HashSet<>(); - Set usedUndeclaredArtifacts = new HashSet<>(); - Set unusedDeclaredArtifacts = new HashSet<>(); - Set testArtifactsWithNonTestScope = new HashSet<>(); + Set usedDeclaredArtifacts = new HashSet<>(); + Set usedUndeclaredArtifacts = new HashSet<>(); + Set unusedDeclaredArtifacts = new HashSet<>(); + Set testArtifactsWithNonTestScope = new HashSet<>(); ProjectDependencyAnalysis analysis = new ProjectDependencyAnalysis( usedDeclaredArtifacts, usedUndeclaredArtifacts, unusedDeclaredArtifacts, @@ -58,9 +59,9 @@ public void testConstructor() @Test public void ignoreNonCompileShouldFilterOnlyUnusedDeclare() { - Artifact artifactCompile = aTestArtifact( "test1", Artifact.SCOPE_COMPILE ); - Artifact artifactProvided = aTestArtifact( "test2", Artifact.SCOPE_PROVIDED ); - Artifact artifactTest = aTestArtifact( "test3", Artifact.SCOPE_TEST ); + Dependency artifactCompile = aTestArtifact( "test1", Scope.COMPILE ); + Dependency artifactProvided = aTestArtifact( "test2", Scope.PROVIDED ); + Dependency artifactTest = aTestArtifact( "test3", Scope.TEST ); ProjectDependencyAnalysis analysis = new ProjectDependencyAnalysis( asSet( artifactCompile, artifactProvided, artifactTest ), @@ -75,7 +76,7 @@ public void ignoreNonCompileShouldFilterOnlyUnusedDeclare() assertThat( compileOnlyAnalysis.getUnusedDeclaredArtifacts() ) .hasSize( 1 ) - .allSatisfy( a -> assertThat( a.getScope() ).isEqualTo( Artifact.SCOPE_COMPILE ) ); + .allSatisfy( a -> assertThat( a.getScope() ).isEqualTo( Scope.COMPILE ) ); assertThat( compileOnlyAnalysis.getTestArtifactsWithNonTestScope() ) .hasSize( 3 ); @@ -86,9 +87,13 @@ private Set asSet( T... items ) return new HashSet<>( Arrays.asList( items ) ); } - private Artifact aTestArtifact( String artifactId, String scope ) + private Dependency aTestArtifact( String artifactId, Scope scope ) { - return new DefaultArtifact( "groupId", artifactId, VersionRange.createFromVersion( "1.0" ), - scope, "jar", "", null ); + Dependency dependency = Mockito.mock( Dependency.class ); + when( dependency.getGroupId() ).thenReturn( "groupId" ); + when( dependency.getArtifactId() ).thenReturn( artifactId ); + when( dependency.getScope() ).thenReturn( scope ); + when( dependency.key() ).thenReturn( artifactId ); + return dependency; } } diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java index edec3463..40d3f63f 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollectorTest.java @@ -38,10 +38,7 @@ Set getDependencies( Class inspectClass ) String className = inspectClass.getName(); String path = '/' + className.replace( '.', '/' ) + ".class"; DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); - try ( InputStream is = inspectClass.getResourceAsStream( path ) ) - { - visitor.visitClass( className, is ); - } + visitor.visitClass( className, () -> inspectClass.getResourceAsStream( path ) ); return visitor.getDependencies(); }