From 1b4891985cd4005663d32829632d30067c1f6c25 Mon Sep 17 00:00:00 2001 From: cesarsotovalero Date: Fri, 12 Mar 2021 15:59:59 +0100 Subject: [PATCH 1/5] Add Licence --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1c1be6d7..b91fa48a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=castor-software_depclean&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=castor-software_depclean) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=castor-software_depclean&metric=security_rating)](https://sonarcloud.io/dashboard?id=castor-software_depclean) [![Maven Central](https://img.shields.io/maven-central/v/se.kth.castor/depclean-core.svg)](https://search.maven.org/search?q=g:se.kth.castor%20AND%20a:depclean*) +[![Licence](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/castor-software/depclean/blob/master/LICENSE.md) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=castor-software_depclean&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=castor-software_depclean) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=castor-software_depclean&metric=bugs)](https://sonarcloud.io/dashboard?id=castor-software_depclean) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=castor-software_depclean&metric=code_smells)](https://sonarcloud.io/dashboard?id=castor-software_depclean) From 92c4854456b104d8b8bb12630b120f1c39b355ae Mon Sep 17 00:00:00 2001 From: cesarsotovalero Date: Sat, 13 Mar 2021 13:12:47 +0100 Subject: [PATCH 2/5] Update jacoco --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index efe62afb..46e8fee4 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 11 11 3.2.0 - 0.8.5 + 0.8.6 4.3.0 4.12 1.18.16 From 911446734373a1fb21f007db480171038d485e6e Mon Sep 17 00:00:00 2001 From: cesarsotovalero Date: Sun, 14 Mar 2021 00:21:57 +0100 Subject: [PATCH 3/5] Add checkstyle --- checkstyle.xml | 354 +++++++++++++++++++++++ depclean-core/pom.xml | 44 +++ depclean-maven-plugin/pom.xml | 229 +++++++++------ pom.xml | 530 +++++++++++++++++----------------- 4 files changed, 797 insertions(+), 360 deletions(-) create mode 100644 checkstyle.xml diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000..b8d1ed10 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/depclean-core/pom.xml b/depclean-core/pom.xml index f0a3a476..7d08e32b 100644 --- a/depclean-core/pom.xml +++ b/depclean-core/pom.xml @@ -22,6 +22,50 @@ target/site/jacoco/jacoco.xml + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 8.41 + + + + + + + error + true + + ../checkstyle.xml + UTF-8 + true + true + + checkstyle + validate + + check + + + + verify + + + checkstyle + + + + + + + diff --git a/depclean-maven-plugin/pom.xml b/depclean-maven-plugin/pom.xml index d0b70286..9634fe6d 100644 --- a/depclean-maven-plugin/pom.xml +++ b/depclean-maven-plugin/pom.xml @@ -1,106 +1,145 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 - - - se.kth.castor - depclean-parent-pom - 2.0.1-SNAPSHOT - + + + se.kth.castor + depclean-parent-pom + 2.0.1-SNAPSHOT + - - depclean-maven-plugin - 2.0.1-SNAPSHOT - maven-plugin - depclean-maven-plugin + + depclean-maven-plugin + 2.0.1-SNAPSHOT + maven-plugin + depclean-maven-plugin - - target/site/jacoco/jacoco.xml - + + target/site/jacoco/jacoco.xml + - DepClean is a plugin for automatically remove unused dependencies in Maven projects + DepClean is a plugin for automatically remove unused dependencies in Maven projects - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.6.0 + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.6.0 + + depclean + false + + + + mojo-descriptor + process-classes + + descriptor + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + + com.puppycrawl.tools + checkstyle + 8.41 + + + + - depclean - false + + error + true + + ../checkstyle.xml + UTF-8 + true + true - - - mojo-descriptor - process-classes - - descriptor - - - - - - + checkstyle + validate + + check + + + + verify + + + checkstyle + + + + + + - - - - se.kth.castor - depclean-core - 2.0.1-SNAPSHOT - - - - org.apache.maven - maven-core - 3.6.0 - - - org.apache.maven - maven-plugin-api - 3.6.0 - - - org.apache.maven - maven-project - 3.0-alpha-2 - - - org.apache.maven.plugin-tools - maven-plugin-annotations - 3.6.0 - - - org.apache.maven.shared - maven-dependency-tree - 3.0.1 - - - - commons-io - commons-io - 2.5 - - - com.google.code.gson - gson - 2.8.6 - - - org.whitesource - maven-dependency-tree-parser - 1.0.6 - - - - org.apache.maven.plugin-testing - maven-plugin-testing-tools - 3.3.0 - compile - - + + + + se.kth.castor + depclean-core + 2.0.1-SNAPSHOT + + + + org.apache.maven + maven-core + 3.6.0 + + + org.apache.maven + maven-plugin-api + 3.6.0 + + + org.apache.maven + maven-project + 3.0-alpha-2 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + + + org.apache.maven.shared + maven-dependency-tree + 3.0.1 + + + + commons-io + commons-io + 2.5 + + + com.google.code.gson + gson + 2.8.6 + + + org.whitesource + maven-dependency-tree-parser + 1.0.6 + + + + org.apache.maven.plugin-testing + maven-plugin-testing-tools + 3.3.0 + compile + + diff --git a/pom.xml b/pom.xml index 46e8fee4..a41c76fe 100644 --- a/pom.xml +++ b/pom.xml @@ -1,284 +1,284 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - - se.kth.castor - depclean-parent-pom - 2.0.1-SNAPSHOT - pom - 4.0.0 + + se.kth.castor + depclean-parent-pom + 2.0.1-SNAPSHOT + pom + 4.0.0 - - depclean-parent-pom + + depclean-parent-pom - - - UTF-8 - UTF-8 - 11 - 11 - 11 - 3.2.0 - 0.8.6 - 4.3.0 - 4.12 - 1.18.16 - 2.0.0-alpha1 - 1.8.0-beta4 - + + + UTF-8 + UTF-8 + 11 + 11 + 11 + 3.2.0 + 0.8.6 + 4.3.0 + 4.12 + 1.18.16 + 2.0.0-alpha1 + 1.8.0-beta4 + - - DepClean is a tool that automatically debloats dependencies in Java projects - https://github.com/castor-software/depclean + + DepClean is a tool that automatically debloats dependencies in Java projects + https://github.com/castor-software/depclean - - - GitHub Issues - https://github.com/castor-software/depclean/issues - + + + GitHub Issues + https://github.com/castor-software/depclean/issues + - - - https://github.com/castor-software/depclean/ - scm:git:git:github.com/castor-software/depclean.git - scm:git:git@github.com:castor-software/depclean.git - + + + https://github.com/castor-software/depclean/ + scm:git:git:github.com/castor-software/depclean.git + scm:git:git@github.com:castor-software/depclean.git + - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - repo - - + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + - - - - cesarsotovalero - César Soto Valero - cesarsotovlaero@gmail.com - Castor Software Research Centre - https://www.castor.kth.se/ - - + + + + cesarsotovalero + César Soto Valero + cesarsotovlaero@gmail.com + Castor Software Research Centre + https://www.castor.kth.se/ + + - - - depclean-core - depclean-maven-plugin - + + + depclean-core + depclean-maven-plugin + - - - - - org.projectlombok - lombok - ${maven.lombok.version} - provided - - - - org.slf4j - slf4j-api - ${maven.slf4j-api.version} - - - org.slf4j - slf4j-log4j12 - ${maven.slf4j-log4j12.version} - - - - junit - junit - ${maven.junit.version} - test - - + + + + + org.projectlombok + lombok + ${maven.lombok.version} + provided + + + + org.slf4j + slf4j-api + ${maven.slf4j-api.version} + + + org.slf4j + slf4j-log4j12 + ${maven.slf4j-log4j12.version} + + + + junit + junit + ${maven.junit.version} + test + + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - ${maven.coveralls.plugin.version} - - - javax.xml.bind - jaxb-api - 2.3.1 - - - - - - ${project.basedir}/depclean-core/target/site/jacoco/jacoco.xml - - - ${project.basedir}/depclean-maven-plugin/target/site/jacoco/jacoco.xml - - - false - - - - + + + + + + + org.eluder.coveralls + coveralls-maven-plugin + ${maven.coveralls.plugin.version} + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + + ${project.basedir}/depclean-core/target/site/jacoco/jacoco.xml + + + ${project.basedir}/depclean-maven-plugin/target/site/jacoco/jacoco.xml + + + false + + + + + + + + maven-compiler-plugin + 3.8.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.0.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.21.0 + + + **/resources/**/*.java + + + + + + org.jacoco + jacoco-maven-plugin + ${maven.jacoco.plugin.version} + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + + + + + deploy-artifacts + - - - maven-compiler-plugin - 3.8.1 - - ${maven.compiler.source} - ${maven.compiler.target} - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.21.0 - - - **/resources/**/*.java - - - - - - org.jacoco - jacoco-maven-plugin - ${maven.jacoco.plugin.version} - - - - prepare-agent - - - - report - prepare-package - - report - - - - + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.maven-javadoc-plugin.version} + + ${maven.javadoc.source} + + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + true + false + release + deploy + + - - - - - - deploy-artifacts - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven.maven-javadoc-plugin.version} - - ${maven.javadoc.source} - - - - attach-javadocs - - jar - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - true - false - release - deploy - - - - - - + + + From 10414f9cf6d949c69ee213425b85b9ff8eb1ec9d Mon Sep 17 00:00:00 2001 From: cesarsotovalero Date: Sun, 14 Mar 2021 00:22:19 +0100 Subject: [PATCH 4/5] Reorganize code --- .../depclean/core/analysis/ArtifactTypes.java | 15 +- .../depclean/core/analysis/ClassAnalyzer.java | 11 +- .../core/analysis/ClassFileVisitor.java | 6 +- .../core/analysis/ClassFileVisitorUtils.java | 157 +-- .../analysis/CollectorClassFileVisitor.java | 42 +- .../core/analysis/DefaultClassAnalyzer.java | 37 +- .../DefaultProjectDependencyAnalyzer.java | 407 +++--- .../core/analysis/DependencyAnalyzer.java | 2 +- .../analysis/ProjectDependencyAnalysis.java | 231 ++- .../analysis/ProjectDependencyAnalyzer.java | 11 +- .../ProjectDependencyAnalyzerException.java | 25 +- .../analysis/asm/ASMDependencyAnalyzer.java | 29 +- .../core/analysis/asm/ConstantPoolParser.java | 234 ++-- .../asm/DefaultAnnotationVisitor.java | 56 +- .../analysis/asm/DefaultClassVisitor.java | 170 +-- .../analysis/asm/DefaultFieldVisitor.java | 28 +- .../analysis/asm/DefaultMethodVisitor.java | 184 +-- .../analysis/asm/DefaultSignatureVisitor.java | 30 +- .../asm/DependencyClassFileVisitor.java | 111 +- .../core/analysis/asm/ResultCollector.java | 97 +- .../graph/ClassMembersVisitorCounter.java | 138 +- .../core/analysis/graph/DefaultCallGraph.java | 121 +- .../analysis/ClassFileVisitorUtilsTest.java | 20 +- .../java/se/kth/depclean/DepCleanMojo.java | 1241 +++++++++-------- .../java/se/kth/depclean/util/FileUtils.java | 111 +- .../java/se/kth/depclean/util/JarUtils.java | 130 +- .../se/kth/depclean/util/MavenInvoker.java | 146 +- .../kth/depclean/util/json/NodeAdapter.java | 283 ++-- .../util/json/ParsedDependencies.java | 115 +- .../core/maven/plugin/FileUtilsTest.java | 19 +- 30 files changed, 2108 insertions(+), 2099 deletions(-) diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ArtifactTypes.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ArtifactTypes.java index a5458adc..02a03731 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ArtifactTypes.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ArtifactTypes.java @@ -1,17 +1,16 @@ package se.kth.depclean.core.analysis; -import lombok.Data; - import java.util.Set; +import lombok.Data; @Data public class ArtifactTypes { - private Set allTypes; - private Set usedTypes; + private Set allTypes; + private Set usedTypes; - public ArtifactTypes(Set allTypes, Set usedTypes) { - this.allTypes = allTypes; - this.usedTypes = usedTypes; - } + public ArtifactTypes(Set allTypes, Set usedTypes) { + this.allTypes = allTypes; + this.usedTypes = usedTypes; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassAnalyzer.java index 8075534f..6c23e1b8 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassAnalyzer.java @@ -26,15 +26,14 @@ /** * Gets the set of classes contained in a library given either as a jar file or an exploded directory. */ -public interface ClassAnalyzer -{ +public interface ClassAnalyzer { - // fields ----------------------------------------------------------------- + // fields ----------------------------------------------------------------- - String ROLE = ClassAnalyzer.class.getName(); + String ROLE = ClassAnalyzer.class.getName(); - // public methods --------------------------------------------------------- + // public methods --------------------------------------------------------- - Set analyze(URL url) + Set analyze(URL url) throws IOException; } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitor.java index fed7aa9a..129eaf43 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitor.java @@ -21,7 +21,7 @@ import java.io.InputStream; -public interface ClassFileVisitor -{ - void visitClass(String className, InputStream in); +public interface ClassFileVisitor { + + void visitClass(String className, InputStream in); } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java index bc1a9ca9..80990ca4 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java @@ -19,8 +19,6 @@ * under the License. */ -import org.codehaus.plexus.util.DirectoryScanner; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -30,105 +28,100 @@ import java.net.URL; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import org.codehaus.plexus.util.DirectoryScanner; /** * Utility to visit classes in a library given either as a jar file or an exploded directory. */ -public final class ClassFileVisitorUtils -{ - // constants -------------------------------------------------------------- - - private static final String[] CLASS_INCLUDES = {"**/*.class"}; - public static final String CLASS = ".class"; - - // constructors ----------------------------------------------------------- - - private ClassFileVisitorUtils() - { - // private constructor for utility class - } - - // public methods --------------------------------------------------------- - - public static void accept(URL url, ClassFileVisitor visitor) - throws IOException - { - if (url.getPath().endsWith(".jar")) { - acceptJar(url, visitor); - } else { - final String message = "Cannot accept visitor on URL: "; - if (url.getProtocol().equalsIgnoreCase("file")) { - try { - File file = new File(new URI(url.toString())); - - if (file.isDirectory()) { - acceptDirectory(file, visitor); - } else if (file.exists()) { - throw new IllegalArgumentException(message + url); - } - } catch (URISyntaxException exception) { - throw new IllegalArgumentException(message + url, exception); - } - } else { - throw new IllegalArgumentException(message + url); - } +public final class ClassFileVisitorUtils { + // constants -------------------------------------------------------------- + + private static final String[] CLASS_INCLUDES = {"**/*.class"}; + public static final String CLASS = ".class"; + + // constructors ----------------------------------------------------------- + + private ClassFileVisitorUtils() { + // private constructor for utility class + } + + // public methods --------------------------------------------------------- + + public static void accept(URL url, ClassFileVisitor visitor) + throws IOException { + if (url.getPath().endsWith(".jar")) { + acceptJar(url, visitor); + } else { + final String message = "Cannot accept visitor on URL: "; + if (url.getProtocol().equalsIgnoreCase("file")) { + try { + File file = new File(new URI(url.toString())); + + if (file.isDirectory()) { + acceptDirectory(file, visitor); + } else if (file.exists()) { + throw new IllegalArgumentException(message + url); + } + } catch (URISyntaxException exception) { + throw new IllegalArgumentException(message + url, exception); } + } else { + throw new IllegalArgumentException(message + url); + } } - - // private methods -------------------------------------------------------- - - private static void acceptJar(URL url, ClassFileVisitor visitor) - throws IOException - { - try (JarInputStream in = new JarInputStream(url.openStream())) { - JarEntry entry = null; - while ((entry = in.getNextJarEntry()) != null) {//NOSONAR - 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); - } - } + } + + // private methods -------------------------------------------------------- + + private static void acceptJar(URL url, ClassFileVisitor visitor) + throws IOException { + try (JarInputStream in = new JarInputStream(url.openStream())) { + JarEntry entry = null; + while ((entry = in.getNextJarEntry()) != null) {//NOSONAR + 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); } + } } + } - private static void acceptDirectory(File directory, ClassFileVisitor visitor) - throws IOException - { - if (!directory.isDirectory()) { - throw new IllegalArgumentException("File is not a directory"); - } + private static void acceptDirectory(File directory, ClassFileVisitor visitor) + throws IOException { + if (!directory.isDirectory()) { + throw new IllegalArgumentException("File is not a directory"); + } - DirectoryScanner scanner = new DirectoryScanner(); + DirectoryScanner scanner = new DirectoryScanner(); - scanner.setBasedir(directory); - scanner.setIncludes(CLASS_INCLUDES); + scanner.setBasedir(directory); + scanner.setIncludes(CLASS_INCLUDES); - scanner.scan(); + scanner.scan(); - String[] paths = scanner.getIncludedFiles(); + String[] paths = scanner.getIncludedFiles(); - for (String path : paths) { - path = path.replace(File.separatorChar, '/'); + for (String path : paths) { + path = path.replace(File.separatorChar, '/'); - File file = new File(directory, path); + File file = new File(directory, path); - try (FileInputStream in = new FileInputStream(file)) { - visitClass(path, in, visitor); - } - } + try (FileInputStream in = new FileInputStream(file)) { + visitClass(path, in, visitor); + } } + } - private static void visitClass(String path, InputStream in, ClassFileVisitor visitor) - { - if (!path.endsWith(CLASS)) { - throw new IllegalArgumentException("Path is not a class"); - } + private static void visitClass(String path, InputStream in, ClassFileVisitor visitor) { + if (!path.endsWith(CLASS)) { + throw new IllegalArgumentException("Path is not a class"); + } - String className = path.substring(0, path.length() - CLASS.length()); + String className = path.substring(0, path.length() - CLASS.length()); - className = className.replace('/', '.'); + className = className.replace('/', '.'); - visitor.visitClass(className, in); - } + visitor.visitClass(className, in); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/CollectorClassFileVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/CollectorClassFileVisitor.java index 5567c50c..5567cddf 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/CollectorClassFileVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/CollectorClassFileVisitor.java @@ -29,34 +29,30 @@ * @see #getClasses() */ public class CollectorClassFileVisitor - implements ClassFileVisitor -{ - // fields ----------------------------------------------------------------- + implements ClassFileVisitor { + // fields ----------------------------------------------------------------- - private final Set classes; + private final Set classes; - // constructors ----------------------------------------------------------- + // constructors ----------------------------------------------------------- - public CollectorClassFileVisitor() - { - classes = new HashSet<>(); - } + public CollectorClassFileVisitor() { + classes = new HashSet<>(); + } - // ClassFileVisitor methods ----------------------------------------------- + // ClassFileVisitor methods ----------------------------------------------- - /* - * @see org.apache.invoke.shared.dependency.analyzer.ClassFileVisitor#visitClass(java.lang.String, - * java.io.InputStream) - */ - public void visitClass(String className, InputStream in) - { - classes.add(className); - } + /* + * @see org.apache.invoke.shared.dependency.analyzer.ClassFileVisitor#visitClass(java.lang.String, + * java.io.InputStream) + */ + public void visitClass(String className, InputStream in) { + classes.add(className); + } - // public methods --------------------------------------------------------- + // public methods --------------------------------------------------------- - public Set getClasses() - { - return classes; - } + public Set getClasses() { + return classes; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultClassAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultClassAnalyzer.java index 9670103c..41ec57bc 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultClassAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultClassAnalyzer.java @@ -23,31 +23,28 @@ import java.net.URL; import java.util.Set; import java.util.zip.ZipException; - import org.codehaus.plexus.component.annotations.Component; @Component(role = ClassAnalyzer.class) public class DefaultClassAnalyzer - implements ClassAnalyzer -{ - // ClassAnalyzer methods -------------------------------------------------- + implements ClassAnalyzer { + // ClassAnalyzer methods -------------------------------------------------- - public Set analyze(URL url) - throws IOException - { - CollectorClassFileVisitor visitor = new CollectorClassFileVisitor(); + public Set analyze(URL url) + throws IOException { + CollectorClassFileVisitor visitor = new CollectorClassFileVisitor(); - try { - ClassFileVisitorUtils.accept(url, 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 URL: " + url + " due to " + e.getMessage()); - ze.initCause(e); - throw ze; - } + try { + ClassFileVisitorUtils.accept(url, 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 URL: " + url + " due to " + e.getMessage()); + ze.initCause(e); + throw ze; + } - return visitor.getClasses(); - } + return visitor.getClasses(); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java index f841753c..18504255 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java @@ -16,13 +16,6 @@ */ package se.kth.depclean.core.analysis; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.component.annotations.Component; -import org.codehaus.plexus.component.annotations.Requirement; -import se.kth.depclean.core.analysis.asm.ASMDependencyAnalyzer; -import se.kth.depclean.core.analysis.graph.DefaultCallGraph; - import java.io.File; import java.io.IOException; import java.net.URL; @@ -35,213 +28,219 @@ import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.component.annotations.Requirement; +import se.kth.depclean.core.analysis.asm.ASMDependencyAnalyzer; +import se.kth.depclean.core.analysis.graph.DefaultCallGraph; @Component(role = ProjectDependencyAnalyzer.class) public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyzer { - /** - * If true, the project's classes in target/test-classes are not going to be analyzed. - */ - private boolean isIgnoredTest; - - /** - * ClassAnalyzer - */ - @Requirement - private final ClassAnalyzer classAnalyzer = new DefaultClassAnalyzer(); - - /** - * DependencyAnalyzer - */ - @Requirement - private final DependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer(); - - private final Map> artifactUsedClassesMap = new HashMap<>(); - - /** - * Ctor. - */ - public DefaultProjectDependencyAnalyzer(boolean isIgnoredTest) { - this.isIgnoredTest = isIgnoredTest; - } - - /** - * A map [dependency] -> [dependency classes]. - */ - private Map> artifactClassesMap; - - /** - * Analyze the dependencies in a project. - * - * @param project The Maven project to be analyzed. - * @return An object with the usedDeclaredArtifacts, usedUndeclaredArtifacts, and unusedDeclaredArtifacts. - * @throws ProjectDependencyAnalyzerException if the analysis fails. - * @see ProjectDependencyAnalyzer#analyze(org.apache.invoke.project.MavenProject) - */ - public ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException { - try { - // a map of [dependency] -> [classes] - artifactClassesMap = buildArtifactClassMap(project); - - // direct dependencies of the project - Set declaredArtifacts = project.getDependencyArtifacts(); - - // transitive dependencies of the project - Set transitiveArtifacts = removeAll(project.getArtifacts(), declaredArtifacts); - - /* ******************** bytecode analysis ********************* */ - - // execute the analysis (note that the order of these operations matters!) - buildProjectDependencyClasses(project); - Set projectClasses = new HashSet<>(DefaultCallGraph.getProjectVertices()); - buildDependenciesDependencyClasses(project); - - /* ******************** usage analysis ********************* */ - - // search for the dependencies used by the project - Set usedArtifacts = collectUsedArtifacts( - artifactClassesMap, - DefaultCallGraph.referencedClassMembers(projectClasses) - ); - - /* ******************** results as statically used at the bytecode *********************** */ - - // for the used dependencies, get the ones that are declared - Set usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts); - usedDeclaredArtifacts.retainAll(usedArtifacts); - - // for the used dependencies, remove the ones that are declared - Set usedUndeclaredArtifacts = new LinkedHashSet<>(usedArtifacts); - usedUndeclaredArtifacts = removeAll(usedUndeclaredArtifacts, declaredArtifacts); - - // for the declared dependencies, get the ones that are not used - Set unusedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts); - unusedDeclaredArtifacts = removeAll(unusedDeclaredArtifacts, usedArtifacts); - - return new ProjectDependencyAnalysis(usedDeclaredArtifacts, usedUndeclaredArtifacts, unusedDeclaredArtifacts); - } catch (IOException exception) { - throw new ProjectDependencyAnalyzerException("Cannot analyze dependencies", exception); - } - } - - public Map> buildArtifactClassMap(MavenProject project) throws IOException { - 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); + /** + * If true, the project's classes in target/test-classes are not going to be analyzed. + */ + private boolean isIgnoredTest; + + /** + * ClassAnalyzer + */ + @Requirement + private final ClassAnalyzer classAnalyzer = new DefaultClassAnalyzer(); + + /** + * DependencyAnalyzer + */ + @Requirement + private final DependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer(); + + private final Map> artifactUsedClassesMap = new HashMap<>(); + + /** + * Ctor. + */ + public DefaultProjectDependencyAnalyzer(boolean isIgnoredTest) { + this.isIgnoredTest = isIgnoredTest; + } + + /** + * A map [dependency] -> [dependency classes]. + */ + private Map> artifactClassesMap; + + /** + * Analyze the dependencies in a project. + * + * @param project The Maven project to be analyzed. + * @return An object with the usedDeclaredArtifacts, usedUndeclaredArtifacts, and unusedDeclaredArtifacts. + * @throws ProjectDependencyAnalyzerException if the analysis fails. + * @see ProjectDependencyAnalyzer#analyze(org.apache.invoke.project.MavenProject) + */ + public ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException { + try { + // a map of [dependency] -> [classes] + artifactClassesMap = buildArtifactClassMap(project); + + // direct dependencies of the project + Set declaredArtifacts = project.getDependencyArtifacts(); + + // transitive dependencies of the project + Set transitiveArtifacts = removeAll(project.getArtifacts(), declaredArtifacts); + + /* ******************** bytecode analysis ********************* */ + + // execute the analysis (note that the order of these operations matters!) + buildProjectDependencyClasses(project); + Set projectClasses = new HashSet<>(DefaultCallGraph.getProjectVertices()); + buildDependenciesDependencyClasses(project); + + /* ******************** usage analysis ********************* */ + + // search for the dependencies used by the project + Set usedArtifacts = collectUsedArtifacts( + artifactClassesMap, + DefaultCallGraph.referencedClassMembers(projectClasses) + ); + + /* ******************** results as statically used at the bytecode *********************** */ + + // for the used dependencies, get the ones that are declared + Set usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts); + usedDeclaredArtifacts.retainAll(usedArtifacts); + + // for the used dependencies, remove the ones that are declared + Set usedUndeclaredArtifacts = new LinkedHashSet<>(usedArtifacts); + usedUndeclaredArtifacts = removeAll(usedUndeclaredArtifacts, declaredArtifacts); + + // for the declared dependencies, get the ones that are not used + Set unusedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts); + unusedDeclaredArtifacts = removeAll(unusedDeclaredArtifacts, usedArtifacts); + + return new ProjectDependencyAnalysis(usedDeclaredArtifacts, usedUndeclaredArtifacts, unusedDeclaredArtifacts); + } catch (IOException exception) { + throw new ProjectDependencyAnalyzerException("Cannot analyze dependencies", exception); + } + } + + public Map> buildArtifactClassMap(MavenProject project) throws IOException { + 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); } - } else if (file != null && file.isDirectory()) { - URL url = file.toURI().toURL(); - Set classes = classAnalyzer.analyze(url); - artifactClassMap.put(artifact, classes); - } + } + 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; - } - - private void buildProjectDependencyClasses(MavenProject project) throws IOException { - // Analyze src classes in the project - String outputDirectory = project.getBuild().getOutputDirectory(); - collectDependencyClasses(outputDirectory); - // Analyze test classes in the project - if (!isIgnoredTest) { - String testOutputDirectory = project.getBuild().getTestOutputDirectory(); - collectDependencyClasses(testOutputDirectory); + } + return artifactClassMap; + } + + private void buildProjectDependencyClasses(MavenProject project) throws IOException { + // Analyze src classes in the project + String outputDirectory = project.getBuild().getOutputDirectory(); + collectDependencyClasses(outputDirectory); + // Analyze test classes in the project + if (!isIgnoredTest) { + String testOutputDirectory = project.getBuild().getTestOutputDirectory(); + collectDependencyClasses(testOutputDirectory); + } + } + + private void buildDependenciesDependencyClasses(MavenProject project) throws IOException { + String dependenciesDirectory = project.getBuild().getDirectory() + File.separator + "dependency"; + collectDependencyClasses(dependenciesDirectory); + } + + private Set collectUsedArtifacts(Map> artifactClassMap, + Set referencedClasses) { + Set usedArtifacts = new HashSet<>(); + // find for used members in each class in the dependency classes + for (String clazz : referencedClasses) { + Artifact artifact = findArtifactForClassName(artifactClassMap, clazz); + if (artifact != null) { + if (!artifactUsedClassesMap.containsKey(artifact)) { + artifactUsedClassesMap.put(artifact, new HashSet<>()); + } + artifactUsedClassesMap.get(artifact).add(clazz); + usedArtifacts.add(artifact); } - } - - private void buildDependenciesDependencyClasses(MavenProject project) throws IOException { - String dependenciesDirectory = project.getBuild().getDirectory() + File.separator + "dependency"; - collectDependencyClasses(dependenciesDirectory); - } - - private Set collectUsedArtifacts(Map> artifactClassMap, - Set referencedClasses) { - Set usedArtifacts = new HashSet<>(); - // find for used members in each class in the dependency classes - for (String clazz : referencedClasses) { - Artifact artifact = findArtifactForClassName(artifactClassMap, clazz); - if (artifact != null) { - if (!artifactUsedClassesMap.containsKey(artifact)) { - artifactUsedClassesMap.put(artifact, new HashSet<>()); - } - artifactUsedClassesMap.get(artifact).add(clazz); - usedArtifacts.add(artifact); - } + } + return usedArtifacts; + } + + private Artifact findArtifactForClassName(Map> artifactClassMap, String className) { + for (Map.Entry> entry : artifactClassMap.entrySet()) { + if (entry.getValue().contains(className)) { + return entry.getKey(); } - return usedArtifacts; - } - - private Artifact findArtifactForClassName(Map> artifactClassMap, String className) { - for (Map.Entry> entry : artifactClassMap.entrySet()) { - if (entry.getValue().contains(className)) { - return entry.getKey(); - } + } + return null; + } + + /** + * 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 Set removeAll(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; + } } - return null; - } - - /** - * 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 Set removeAll(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); - } + if (!found) { + results.add(artifact); } - return results; - } - - private Set collectDependencyClasses(String path) throws IOException { - URL url = new File(path).toURI().toURL(); - return dependencyAnalyzer.analyze(url); - } - - public Map getArtifactClassesMap() { - Map output = new HashMap<>(); - for (Map.Entry> entry : artifactClassesMap.entrySet()) { - Artifact key = entry.getKey(); - if (artifactUsedClassesMap.containsKey(key)) { - output.put(key.toString(), new ArtifactTypes( - artifactClassesMap.get(key), // get all the types - artifactUsedClassesMap.get(key) // get used types - )); - } else { - output.put(key.toString(), new ArtifactTypes( - artifactClassesMap.get(key), // get all the types - new HashSet<>() // get used types - )); - } + } + return results; + } + + private Set collectDependencyClasses(String path) throws IOException { + URL url = new File(path).toURI().toURL(); + return dependencyAnalyzer.analyze(url); + } + + public Map getArtifactClassesMap() { + Map output = new HashMap<>(); + for (Map.Entry> entry : artifactClassesMap.entrySet()) { + Artifact key = entry.getKey(); + if (artifactUsedClassesMap.containsKey(key)) { + output.put(key.toString(), new ArtifactTypes( + artifactClassesMap.get(key), // get all the types + artifactUsedClassesMap.get(key) // get used types + )); + } else { + output.put(key.toString(), new ArtifactTypes( + artifactClassesMap.get(key), // get all the types + new HashSet<>() // get used types + )); } - return output; - } + } + return output; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DependencyAnalyzer.java index 567a50fd..6baead12 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DependencyAnalyzer.java @@ -28,6 +28,6 @@ */ public interface DependencyAnalyzer { - Set analyze(URL url) throws IOException; + Set analyze(URL url) throws IOException; } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalysis.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalysis.java index 113b50b4..fd3e10ae 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalysis.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalysis.java @@ -19,155 +19,154 @@ * under the License. */ -import org.apache.maven.artifact.Artifact; - import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; +import org.apache.maven.artifact.Artifact; /** * Project dependencies analysis result. */ public class ProjectDependencyAnalysis { - // fields ----------------------------------------------------------------- + // fields ----------------------------------------------------------------- - private final Set usedDeclaredArtifacts; + private final Set usedDeclaredArtifacts; - private final Set usedUndeclaredArtifacts; + private final Set usedUndeclaredArtifacts; - private final Set unusedDeclaredArtifacts; + private final Set unusedDeclaredArtifacts; - // constructors ----------------------------------------------------------- + // constructors ----------------------------------------------------------- - public ProjectDependencyAnalysis() { - this(null, null, null); - } + public ProjectDependencyAnalysis() { + this(null, null, null); + } - public ProjectDependencyAnalysis(Set usedDeclaredArtifacts, - Set usedUndeclaredArtifacts, - Set unusedDeclaredArtifacts) { - this.usedDeclaredArtifacts = safeCopy(usedDeclaredArtifacts); - this.usedUndeclaredArtifacts = safeCopy(usedUndeclaredArtifacts); - this.unusedDeclaredArtifacts = safeCopy(unusedDeclaredArtifacts); - } + public ProjectDependencyAnalysis(Set usedDeclaredArtifacts, + Set usedUndeclaredArtifacts, + Set unusedDeclaredArtifacts) { + this.usedDeclaredArtifacts = safeCopy(usedDeclaredArtifacts); + this.usedUndeclaredArtifacts = safeCopy(usedUndeclaredArtifacts); + this.unusedDeclaredArtifacts = safeCopy(unusedDeclaredArtifacts); + } - // public methods --------------------------------------------------------- + // public methods --------------------------------------------------------- - private Set safeCopy(Set set) { - return (set == null) ? Collections.emptySet() - : Collections.unmodifiableSet(new LinkedHashSet(set)); - } + private Set safeCopy(Set set) { + return (set == null) ? Collections.emptySet() + : Collections.unmodifiableSet(new LinkedHashSet(set)); + } - /** - * Filter not-compile scoped artifacts from unused declared. - * - * @return updated project dependency analysis - * @since 1.3 - */ - public ProjectDependencyAnalysis ignoreNonCompile() { - Set filteredUnusedDeclared = new HashSet<>(unusedDeclaredArtifacts); - for (Iterator iter = filteredUnusedDeclared.iterator(); iter.hasNext(); ) { - Artifact artifact = iter.next(); - if (!artifact.getScope().equals(Artifact.SCOPE_COMPILE)) { - iter.remove(); - } - } - - return new ProjectDependencyAnalysis(usedDeclaredArtifacts, usedUndeclaredArtifacts, filteredUnusedDeclared); + /** + * Filter not-compile scoped artifacts from unused declared. + * + * @return updated project dependency analysis + * @since 1.3 + */ + public ProjectDependencyAnalysis ignoreNonCompile() { + Set filteredUnusedDeclared = new HashSet<>(unusedDeclaredArtifacts); + for (Iterator iter = filteredUnusedDeclared.iterator(); iter.hasNext(); ) { + Artifact artifact = iter.next(); + if (!artifact.getScope().equals(Artifact.SCOPE_COMPILE)) { + iter.remove(); + } } - /* - * @see java.lang.Object#hashCode() - */ - public int hashCode() { - int hashCode = getUsedDeclaredArtifacts().hashCode(); - hashCode = (hashCode * 37) + getUsedUndeclaredArtifacts().hashCode(); - hashCode = (hashCode * 37) + getUnusedDeclaredArtifacts().hashCode(); - - return hashCode; + return new ProjectDependencyAnalysis(usedDeclaredArtifacts, usedUndeclaredArtifacts, filteredUnusedDeclared); + } + + /* + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + int hashCode = getUsedDeclaredArtifacts().hashCode(); + hashCode = (hashCode * 37) + getUsedUndeclaredArtifacts().hashCode(); + hashCode = (hashCode * 37) + getUnusedDeclaredArtifacts().hashCode(); + + return hashCode; + } + + /** + * Used and declared artifacts. + * + * @return {@link Artifact} + */ + public Set getUsedDeclaredArtifacts() { + return usedDeclaredArtifacts; + } + + // Object methods --------------------------------------------------------- + + /** + * Used but not declared artifacts. + * + * @return {@link Artifact} + */ + public Set getUsedUndeclaredArtifacts() { + return usedUndeclaredArtifacts; + } + + /** + * Unused but declared artifacts. + * + * @return {@link Artifact} + */ + public Set getUnusedDeclaredArtifacts() { + return unusedDeclaredArtifacts; + } + + /* + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object object) { + if (object instanceof ProjectDependencyAnalysis) { + ProjectDependencyAnalysis analysis = (ProjectDependencyAnalysis) object; + + return getUsedDeclaredArtifacts().equals(analysis.getUsedDeclaredArtifacts()) + && getUsedUndeclaredArtifacts().equals(analysis.getUsedUndeclaredArtifacts()) + && getUnusedDeclaredArtifacts().equals(analysis.getUnusedDeclaredArtifacts()); } - /** - * Used and declared artifacts. - * - * @return {@link Artifact} - */ - public Set getUsedDeclaredArtifacts() { - return usedDeclaredArtifacts; - } + return false; + } - // Object methods --------------------------------------------------------- + //--------------------------------/ + //------ PRIVATE METHOD/S -------/ + //------------------------------/ + /* + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder buffer = new StringBuilder(); - /** - * Used but not declared artifacts. - * - * @return {@link Artifact} - */ - public Set getUsedUndeclaredArtifacts() { - return usedUndeclaredArtifacts; + if (!getUsedDeclaredArtifacts().isEmpty()) { + buffer.append("usedDeclaredArtifacts=").append(getUsedDeclaredArtifacts()); } - /** - * Unused but declared artifacts. - * - * @return {@link Artifact} - */ - public Set getUnusedDeclaredArtifacts() { - return unusedDeclaredArtifacts; - } + if (!getUsedUndeclaredArtifacts().isEmpty()) { + if (buffer.length() > 0) { + buffer.append(","); + } - /* - * @see java.lang.Object#equals(java.lang.Object) - */ - public boolean equals(Object object) { - if (object instanceof ProjectDependencyAnalysis) { - ProjectDependencyAnalysis analysis = (ProjectDependencyAnalysis) object; - - return getUsedDeclaredArtifacts().equals(analysis.getUsedDeclaredArtifacts()) - && getUsedUndeclaredArtifacts().equals(analysis.getUsedUndeclaredArtifacts()) - && getUnusedDeclaredArtifacts().equals(analysis.getUnusedDeclaredArtifacts()); - } - - return false; + buffer.append("usedUndeclaredArtifacts=").append(getUsedUndeclaredArtifacts()); } - //--------------------------------/ - //------ PRIVATE METHOD/S -------/ - //------------------------------/ - /* - * @see java.lang.Object#toString() - */ - public String toString() { - StringBuilder buffer = new StringBuilder(); - - if (!getUsedDeclaredArtifacts().isEmpty()) { - buffer.append("usedDeclaredArtifacts=").append(getUsedDeclaredArtifacts()); - } - - if (!getUsedUndeclaredArtifacts().isEmpty()) { - if (buffer.length() > 0) { - buffer.append(","); - } + if (!getUnusedDeclaredArtifacts().isEmpty()) { + if (buffer.length() > 0) { + buffer.append(","); + } - buffer.append("usedUndeclaredArtifacts=").append(getUsedUndeclaredArtifacts()); - } - - if (!getUnusedDeclaredArtifacts().isEmpty()) { - if (buffer.length() > 0) { - buffer.append(","); - } - - buffer.append("unusedDeclaredArtifacts=").append(getUnusedDeclaredArtifacts()); - } + buffer.append("unusedDeclaredArtifacts=").append(getUnusedDeclaredArtifacts()); + } - buffer.insert(0, "["); - buffer.insert(0, getClass().getName()); + buffer.insert(0, "["); + buffer.insert(0, getClass().getName()); - buffer.append("]"); + buffer.append("]"); - return buffer.toString(); - } + return buffer.toString(); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java index 0da67caf..9dccb616 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java @@ -24,13 +24,12 @@ /** * Analyze a project's declared dependencies and effective classes */ -public interface ProjectDependencyAnalyzer -{ - // fields ----------------------------------------------------------------- +public interface ProjectDependencyAnalyzer { + // fields ----------------------------------------------------------------- - String ROLE = ProjectDependencyAnalyzer.class.getName(); + String ROLE = ProjectDependencyAnalyzer.class.getName(); - // public methods --------------------------------------------------------- + // public methods --------------------------------------------------------- - ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException; + ProjectDependencyAnalysis analyze(MavenProject project) throws ProjectDependencyAnalyzerException; } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzerException.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzerException.java index 2f2e0c9e..d05395e3 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzerException.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzerException.java @@ -19,21 +19,18 @@ * under the License. */ -public class ProjectDependencyAnalyzerException extends Exception -{ +public class ProjectDependencyAnalyzerException extends Exception { - /** - * The serialisation unique ID. - */ - private static final long serialVersionUID = -5954447543668196977L; + /** + * The serialisation unique ID. + */ + private static final long serialVersionUID = -5954447543668196977L; - public ProjectDependencyAnalyzerException(String message) - { - super(message); - } + public ProjectDependencyAnalyzerException(String message) { + super(message); + } - public ProjectDependencyAnalyzerException(String message, Throwable cause) - { - super(message, cause); - } + public ProjectDependencyAnalyzerException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ASMDependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ASMDependencyAnalyzer.java index 31d68564..4646e0b3 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ASMDependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ASMDependencyAnalyzer.java @@ -19,28 +19,27 @@ package se.kth.depclean.core.analysis.asm; +import java.io.IOException; +import java.net.URL; +import java.util.Set; import org.codehaus.plexus.component.annotations.Component; import se.kth.depclean.core.analysis.ClassFileVisitorUtils; import se.kth.depclean.core.analysis.DependencyAnalyzer; import se.kth.depclean.core.analysis.graph.ClassMembersVisitorCounter; -import java.io.IOException; -import java.net.URL; -import java.util.Set; - @Component(role = DependencyAnalyzer.class) public class ASMDependencyAnalyzer implements DependencyAnalyzer { - // DependencyAnalyzer methods --------------------------------------------- + // DependencyAnalyzer methods --------------------------------------------- - /* - * @see org.apache.invoke.shared.dependency.analyzer.DependencyAnalyzer#analyze(java.net.URL) - */ - @Override - public Set analyze(URL url) throws IOException { - ClassMembersVisitorCounter.resetClassCounters(); - DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); - ClassFileVisitorUtils.accept(url, visitor); - return visitor.getDependencies(); - } + /* + * @see org.apache.invoke.shared.dependency.analyzer.DependencyAnalyzer#analyze(java.net.URL) + */ + @Override + public Set analyze(URL url) throws IOException { + ClassMembersVisitorCounter.resetClassCounters(); + DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(); + ClassFileVisitorUtils.accept(url, visitor); + return visitor.getDependencies(); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java index 55ab8040..be9ed485 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java @@ -19,8 +19,6 @@ package se.kth.depclean.core.analysis.asm; -import org.jetbrains.annotations.NotNull; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Collections; @@ -28,10 +26,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.jetbrains.annotations.NotNull; /** - * A small parser to read the constant pool directly, in case it contains references - * ASM does not support. + * A small parser to read the constant pool directly, in case it contains references ASM does not support. *

* Adapted from http://stackoverflow.com/a/32278587/23691 *

@@ -42,143 +40,143 @@ */ public class ConstantPoolParser { - public static final int HEAD = 0xcafebabe; + public static final int HEAD = 0xcafebabe; - // Constant pool types - public static final byte CONSTANT_UTF8 = 1; + // Constant pool types + public static final byte CONSTANT_UTF8 = 1; - public static final byte CONSTANT_INTEGER = 3; + public static final byte CONSTANT_INTEGER = 3; - public static final byte CONSTANT_FLOAT = 4; + public static final byte CONSTANT_FLOAT = 4; - public static final byte CONSTANT_LONG = 5; + public static final byte CONSTANT_LONG = 5; - public static final byte CONSTANT_DOUBLE = 6; + public static final byte CONSTANT_DOUBLE = 6; - public static final byte CONSTANT_CLASS = 7; + public static final byte CONSTANT_CLASS = 7; - public static final byte CONSTANT_STRING = 8; + public static final byte CONSTANT_STRING = 8; - public static final byte CONSTANT_FIELDREF = 9; + public static final byte CONSTANT_FIELDREF = 9; - public static final byte CONSTANT_METHODREF = 10; + public static final byte CONSTANT_METHODREF = 10; - public static final byte CONSTANT_INTERFACEMETHODREF = 11; + public static final byte CONSTANT_INTERFACEMETHODREF = 11; - public static final byte CONSTANT_NAME_AND_TYPE = 12; + public static final byte CONSTANT_NAME_AND_TYPE = 12; - public static final byte CONSTANT_METHODHANDLE = 15; + public static final byte CONSTANT_METHODHANDLE = 15; - public static final byte CONSTANT_METHOD_TYPE = 16; + public static final byte CONSTANT_METHOD_TYPE = 16; - public static final byte CONSTANT_INVOKE_DYNAMIC = 18; + public static final byte CONSTANT_INVOKE_DYNAMIC = 18; - public static final byte CONSTANT_MODULE = 19; + public static final byte CONSTANT_MODULE = 19; - public static final byte CONSTANT_PACKAGE = 20; + public static final byte CONSTANT_PACKAGE = 20; - private static final int OXF0 = 0xf0; + private static final int OXF0 = 0xf0; - private static final int OXE0 = 0xe0; + private static final int OXE0 = 0xe0; - private static final int OX3F = 0x3F; + private static final int OX3F = 0x3F; - private ConstantPoolParser() { - throw new IllegalStateException("Utility class"); - } + private ConstantPoolParser() { + throw new IllegalStateException("Utility class"); + } - static Set getConstantPoolClassReferences(byte[] b) { - return parseConstantPoolClassReferences(ByteBuffer.wrap(b)); - } + static Set getConstantPoolClassReferences(byte[] b) { + return parseConstantPoolClassReferences(ByteBuffer.wrap(b)); + } - static Set parseConstantPoolClassReferences(ByteBuffer buf) { - if (buf.order(ByteOrder.BIG_ENDIAN) - .getInt() != HEAD) { - return Collections.emptySet(); - } - buf.getChar(); - buf.getChar(); // minor + ver - Set classes = new HashSet<>(); - Map stringConstants = new HashMap<>(); - for (int ix = 1, num = buf.getChar(); ix < num; ix++) { - byte tag = buf.get(); - switch (tag) { - case CONSTANT_UTF8: - stringConstants.put(ix, decodeString(buf)); - continue; - case CONSTANT_CLASS: - case CONSTANT_STRING: - case CONSTANT_METHOD_TYPE: - classes.add((int) buf.getChar()); - break; - case CONSTANT_FIELDREF: - case CONSTANT_METHODREF: - case CONSTANT_INTERFACEMETHODREF: - case CONSTANT_NAME_AND_TYPE: - buf.getChar(); - buf.getChar(); - break; - case CONSTANT_INTEGER: - buf.getInt(); - break; - case CONSTANT_FLOAT: - buf.getFloat(); - break; - case CONSTANT_DOUBLE: - buf.getDouble(); - ix++; - break; - case CONSTANT_LONG: - buf.getLong(); - ix++; - break; - case CONSTANT_METHODHANDLE: - buf.get(); - buf.getChar(); - break; - case CONSTANT_INVOKE_DYNAMIC: - buf.getChar(); - buf.getChar(); - break; - case CONSTANT_MODULE: - buf.getChar(); - break; - case CONSTANT_PACKAGE: - buf.getChar(); - break; - default: - throw new RuntimeException("Unknown constant pool type '" + tag + "'"); - } - } - Set result = new HashSet<>(); - for (Integer aClass : classes) { - result.add(stringConstants.get(aClass)); - } - return result; + static Set parseConstantPoolClassReferences(ByteBuffer buf) { + if (buf.order(ByteOrder.BIG_ENDIAN) + .getInt() != HEAD) { + return Collections.emptySet(); } - - @NotNull - private static String decodeString(ByteBuffer buf) { - int size = buf.getChar(); - // Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer - int oldLimit = buf.limit(); - buf.limit(buf.position() + size); - StringBuilder sb = new StringBuilder(size + (size >> 1) + 16); - while (buf.hasRemaining()) { - byte b = buf.get(); - if (b > 0) { - sb.append((char) b); - } else { - int b2 = buf.get(); - if ((b & OXF0) != OXE0) { - sb.append((char) ((b & 0x1F) << 6 | b2 & OX3F)); - } else { - int b3 = buf.get(); - sb.append((char) ((b & 0x0F) << 12 | (b2 & OX3F) << 6 | b3 & OX3F)); - } - } + buf.getChar(); + buf.getChar(); // minor + ver + Set classes = new HashSet<>(); + Map stringConstants = new HashMap<>(); + for (int ix = 1, num = buf.getChar(); ix < num; ix++) { + byte tag = buf.get(); + switch (tag) { + case CONSTANT_UTF8: + stringConstants.put(ix, decodeString(buf)); + continue; + case CONSTANT_CLASS: + case CONSTANT_STRING: + case CONSTANT_METHOD_TYPE: + classes.add((int) buf.getChar()); + break; + case CONSTANT_FIELDREF: + case CONSTANT_METHODREF: + case CONSTANT_INTERFACEMETHODREF: + case CONSTANT_NAME_AND_TYPE: + buf.getChar(); + buf.getChar(); + break; + case CONSTANT_INTEGER: + buf.getInt(); + break; + case CONSTANT_FLOAT: + buf.getFloat(); + break; + case CONSTANT_DOUBLE: + buf.getDouble(); + ix++; + break; + case CONSTANT_LONG: + buf.getLong(); + ix++; + break; + case CONSTANT_METHODHANDLE: + buf.get(); + buf.getChar(); + break; + case CONSTANT_INVOKE_DYNAMIC: + buf.getChar(); + buf.getChar(); + break; + case CONSTANT_MODULE: + buf.getChar(); + break; + case CONSTANT_PACKAGE: + buf.getChar(); + break; + default: + throw new RuntimeException("Unknown constant pool type '" + tag + "'"); + } + } + Set result = new HashSet<>(); + for (Integer aClass : classes) { + result.add(stringConstants.get(aClass)); + } + return result; + } + + @NotNull + private static String decodeString(ByteBuffer buf) { + int size = buf.getChar(); + // Explicit cast for compatibility with covariant return type on JDK 9's ByteBuffer + int oldLimit = buf.limit(); + buf.limit(buf.position() + size); + StringBuilder sb = new StringBuilder(size + (size >> 1) + 16); + while (buf.hasRemaining()) { + byte b = buf.get(); + if (b > 0) { + sb.append((char) b); + } else { + int b2 = buf.get(); + if ((b & OXF0) != OXE0) { + sb.append((char) ((b & 0x1F) << 6 | b2 & OX3F)); + } else { + int b3 = buf.get(); + sb.append((char) ((b & 0x0F) << 12 | (b2 & OX3F) << 6 | b3 & OX3F)); } - buf.limit(oldLimit); - return sb.toString(); + } } + buf.limit(oldLimit); + return sb.toString(); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java index 19ff60c5..a584e403 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java @@ -24,42 +24,42 @@ import org.objectweb.asm.Type; /** - * Computes the set of classes referenced by visited code. - * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor + * in the ASM dependencies example. */ public class DefaultAnnotationVisitor extends AnnotationVisitor { - private final ResultCollector resultCollector; + private final ResultCollector resultCollector; - public DefaultAnnotationVisitor(ResultCollector resultCollector) { - super(Opcodes.ASM7); - this.resultCollector = resultCollector; - } + public DefaultAnnotationVisitor(ResultCollector resultCollector) { + super(Opcodes.ASM7); + this.resultCollector = resultCollector; + } - @Override - public void visit(final String name, final Object value) { - if (value instanceof Type) { - resultCollector.addType((Type) value); - } + @Override + public void visit(final String name, final Object value) { + if (value instanceof Type) { + resultCollector.addType((Type) value); } + } - @Override - public void visitEnum(final String name, final String desc, final String value) { - resultCollector.addDesc(desc); - } + @Override + public void visitEnum(final String name, final String desc, final String value) { + resultCollector.addDesc(desc); + } - @Override - public AnnotationVisitor visitAnnotation(final String name, final String desc) { - resultCollector.addDesc(desc); + @Override + public AnnotationVisitor visitAnnotation(final String name, final String desc) { + resultCollector.addDesc(desc); - return this; - } + return this; + } - /* - * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String) - */ - @Override - public AnnotationVisitor visitArray(final String name) { - return this; - } + /* + * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String) + */ + @Override + public AnnotationVisitor visitArray(final String name) { + return this; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java index 14f17751..3872645f 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java @@ -30,102 +30,102 @@ import se.kth.depclean.core.analysis.graph.ClassMembersVisitorCounter; /** - * Computes the set of classes referenced by visited code. - * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor + * in the ASM dependencies example. */ public class DefaultClassVisitor extends ClassVisitor { - private final ResultCollector resultCollector; - private final SignatureVisitor signatureVisitor; - private final AnnotationVisitor annotationVisitor; - private final FieldVisitor fieldVisitor; - private final MethodVisitor methodVisitor; + private final ResultCollector resultCollector; + private final SignatureVisitor signatureVisitor; + private final AnnotationVisitor annotationVisitor; + private final FieldVisitor fieldVisitor; + private final MethodVisitor methodVisitor; - public DefaultClassVisitor(SignatureVisitor signatureVisitor, - AnnotationVisitor annotationVisitor, - FieldVisitor fieldVisitor, - MethodVisitor methodVisitor, - ResultCollector resultCollector) { - super(Opcodes.ASM7); - this.signatureVisitor = signatureVisitor; - this.annotationVisitor = annotationVisitor; - this.fieldVisitor = fieldVisitor; - this.methodVisitor = methodVisitor; - this.resultCollector = resultCollector; - } + public DefaultClassVisitor(SignatureVisitor signatureVisitor, + AnnotationVisitor annotationVisitor, + FieldVisitor fieldVisitor, + MethodVisitor methodVisitor, + ResultCollector resultCollector) { + super(Opcodes.ASM7); + this.signatureVisitor = signatureVisitor; + this.annotationVisitor = annotationVisitor; + this.fieldVisitor = fieldVisitor; + this.methodVisitor = methodVisitor; + this.resultCollector = resultCollector; + } - @Override - public void visit(final int version, final int access, final String name, final String signature, - final String superName, final String[] interfaces) { - // System.out.println("Visiting class: " + name); - ClassMembersVisitorCounter.addVisitedClass(); - if (signature == null) { - resultCollector.addName(superName); - resultCollector.addNames(interfaces); - } else { - addSignature(signature); - } - } + @Override + public void visit(final int version, final int access, final String name, final String signature, + final String superName, final String[] interfaces) { + // System.out.println("Visiting class: " + name); + ClassMembersVisitorCounter.addVisitedClass(); + if (signature == null) { + resultCollector.addName(superName); + resultCollector.addNames(interfaces); + } else { + addSignature(signature); + } + } - @Override - public void visitNestHost(final String nestHost) { - resultCollector.addName(nestHost); - } + @Override + public void visitNestHost(final String nestHost) { + resultCollector.addName(nestHost); + } - @Override - public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - // System.out.println("\t" + "visiting annotation: " + desc); - ClassMembersVisitorCounter.addVisitedAnnotation(); - resultCollector.addDesc(desc); - return annotationVisitor; - } + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + // System.out.println("\t" + "visiting annotation: " + desc); + ClassMembersVisitorCounter.addVisitedAnnotation(); + resultCollector.addDesc(desc); + return annotationVisitor; + } - @Override - public void visitNestMember(final String nestMember) { - resultCollector.addName(nestMember); - } + @Override + public void visitNestMember(final String nestMember) { + resultCollector.addName(nestMember); + } - @Override - public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, - final Object value) { - // System.out.println("\t" + "visiting field: " + name); - ClassMembersVisitorCounter.addVisitedField(); - if (signature == null) { - resultCollector.addDesc(desc); - } else { - addTypeSignature(signature); - } - if (value instanceof Type) { - resultCollector.addType((Type) value); - } - return fieldVisitor; - } + @Override + public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, + final Object value) { + // System.out.println("\t" + "visiting field: " + name); + ClassMembersVisitorCounter.addVisitedField(); + if (signature == null) { + resultCollector.addDesc(desc); + } else { + addTypeSignature(signature); + } + if (value instanceof Type) { + resultCollector.addType((Type) value); + } + return fieldVisitor; + } - @Override - public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, - final String[] exceptions) { - // System.out.println("\t" + "visiting method: " + name); - ClassMembersVisitorCounter.addVisitedMethod(); - if (signature == null) { - resultCollector.addMethodDesc(desc); - } else { - addSignature(signature); - } - resultCollector.addNames(exceptions); - return methodVisitor; - } + @Override + public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, + final String[] exceptions) { + // System.out.println("\t" + "visiting method: " + name); + ClassMembersVisitorCounter.addVisitedMethod(); + if (signature == null) { + resultCollector.addMethodDesc(desc); + } else { + addSignature(signature); + } + resultCollector.addNames(exceptions); + return methodVisitor; + } - // private methods -------------------------------------------------------- + // private methods -------------------------------------------------------- - private void addTypeSignature(final String signature) { - if (signature != null) { - new SignatureReader(signature).acceptType(signatureVisitor); - } - } + private void addTypeSignature(final String signature) { + if (signature != null) { + new SignatureReader(signature).acceptType(signatureVisitor); + } + } - private void addSignature(final String signature) { - if (signature != null) { - new SignatureReader(signature).accept(signatureVisitor); - } - } + private void addSignature(final String signature) { + if (signature != null) { + new SignatureReader(signature).accept(signatureVisitor); + } + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java index 8fce37d8..7d5c65c1 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java @@ -24,23 +24,23 @@ import org.objectweb.asm.Opcodes; /** - * Computes the set of classes referenced by visited code. - * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor + * in the ASM dependencies example. */ public class DefaultFieldVisitor extends FieldVisitor { - private final AnnotationVisitor annotationVisitor; - private final ResultCollector resultCollector; + private final AnnotationVisitor annotationVisitor; + private final ResultCollector resultCollector; - public DefaultFieldVisitor(AnnotationVisitor annotationVisitor, ResultCollector resultCollector) { - super(Opcodes.ASM7); - this.annotationVisitor = annotationVisitor; - this.resultCollector = resultCollector; - } + public DefaultFieldVisitor(AnnotationVisitor annotationVisitor, ResultCollector resultCollector) { + super(Opcodes.ASM7); + this.annotationVisitor = annotationVisitor; + this.resultCollector = resultCollector; + } - @Override - public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - resultCollector.addDesc(desc); - return annotationVisitor; - } + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + resultCollector.addDesc(desc); + return annotationVisitor; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java index 84ecd30a..a5672ad1 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java @@ -29,103 +29,103 @@ import org.objectweb.asm.signature.SignatureVisitor; /** - * Computes the set of classes referenced by visited code. - * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor + * in the ASM dependencies example. */ public class DefaultMethodVisitor extends MethodVisitor { - private final AnnotationVisitor annotationVisitor; - private final SignatureVisitor signatureVisitor; - private final ResultCollector resultCollector; - - public DefaultMethodVisitor(AnnotationVisitor annotationVisitor, SignatureVisitor signatureVisitor, - ResultCollector resultCollector) { - super(Opcodes.ASM7); - this.annotationVisitor = annotationVisitor; - this.signatureVisitor = signatureVisitor; - this.resultCollector = resultCollector; - } - - @Override - public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - resultCollector.addDesc(desc); - return annotationVisitor; - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - resultCollector.addDesc(desc); - return annotationVisitor; - } - - @Override - public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { - resultCollector.addDesc(desc); - return annotationVisitor; - } - - @Override - public void visitTypeInsn(final int opcode, final String desc) { - if (desc.charAt(0) == '[') { - resultCollector.addDesc(desc); - } else { - resultCollector.addName(desc); - } - } - - @Override - public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { - resultCollector.addName(owner); - /* - * NOTE: Merely accessing a field does not impose a direct dependency on its type. For example, the code line - * java.lang.Object var = bean.field; does not directly depend on the type of the field. A direct - * dependency is only introduced when the code explicitly references the field's type by means of a variable - * declaration or a type check/cast. Those cases are handled by other visitor callbacks. - */ + private final AnnotationVisitor annotationVisitor; + private final SignatureVisitor signatureVisitor; + private final ResultCollector resultCollector; + + public DefaultMethodVisitor(AnnotationVisitor annotationVisitor, SignatureVisitor signatureVisitor, + ResultCollector resultCollector) { + super(Opcodes.ASM7); + this.annotationVisitor = annotationVisitor; + this.signatureVisitor = signatureVisitor; + this.resultCollector = resultCollector; + } + + @Override + public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { + resultCollector.addDesc(desc); + return annotationVisitor; + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + resultCollector.addDesc(desc); + return annotationVisitor; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { + resultCollector.addDesc(desc); + return annotationVisitor; + } + + @Override + public void visitTypeInsn(final int opcode, final String desc) { + if (desc.charAt(0) == '[') { + resultCollector.addDesc(desc); + } else { + resultCollector.addName(desc); } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - resultCollector.addName(owner); - } - - @Override - public void visitLdcInsn(final Object cst) { - if (cst instanceof Type) { - resultCollector.addType((Type) cst); - } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + resultCollector.addName(owner); + /* + * NOTE: Merely accessing a field does not impose a direct dependency on its type. For example, the code line + * java.lang.Object var = bean.field; does not directly depend on the type of the field. A direct + * dependency is only introduced when the code explicitly references the field's type by means of a variable + * declaration or a type check/cast. Those cases are handled by other visitor callbacks. + */ + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + resultCollector.addName(owner); + } + + @Override + public void visitLdcInsn(final Object cst) { + if (cst instanceof Type) { + resultCollector.addType((Type) cst); } - - @Override - public void visitMultiANewArrayInsn(final String desc, final int dims) { - resultCollector.addDesc(desc); - } - - @Override - public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { - resultCollector.addName(type); + } + + @Override + public void visitMultiANewArrayInsn(final String desc, final int dims) { + resultCollector.addDesc(desc); + } + + @Override + public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { + resultCollector.addName(type); + } + + @Override + public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, + final Label end, final int index) { + if (signature == null) { + resultCollector.addDesc(desc); + } else { + addTypeSignature(signature); } - - @Override - public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, - final Label end, final int index) { - if (signature == null) { - resultCollector.addDesc(desc); - } else { - addTypeSignature(signature); - } - } - - @Override - public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, - int[] index, String desc, boolean visible) { - resultCollector.addDesc(desc); - return annotationVisitor; - } - - private void addTypeSignature(final String signature) { - if (signature != null) { - new SignatureReader(signature).acceptType(signatureVisitor); - } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, + int[] index, String desc, boolean visible) { + resultCollector.addDesc(desc); + return annotationVisitor; + } + + private void addTypeSignature(final String signature) { + if (signature != null) { + new SignatureReader(signature).acceptType(signatureVisitor); } + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java index 3d188886..b0c586ce 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java @@ -23,25 +23,25 @@ import org.objectweb.asm.signature.SignatureVisitor; /** - * Computes the set of classes referenced by visited code. - * Inspired by org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor + * in the ASM dependencies example. */ public class DefaultSignatureVisitor extends SignatureVisitor { - private final ResultCollector resultCollector; + private final ResultCollector resultCollector; - public DefaultSignatureVisitor(ResultCollector resultCollector) { - super(Opcodes.ASM7); - this.resultCollector = resultCollector; - } + public DefaultSignatureVisitor(ResultCollector resultCollector) { + super(Opcodes.ASM7); + this.resultCollector = resultCollector; + } - @Override - public void visitClassType(final String name) { - resultCollector.addName(name); - } + @Override + public void visitClassType(final String name) { + resultCollector.addName(name); + } - @Override - public void visitInnerClassType(final String name) { - resultCollector.addName(name); - } + @Override + public void visitInnerClassType(final String name) { + resultCollector.addName(name); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java index 6bad5f01..2999d021 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java @@ -19,6 +19,9 @@ package se.kth.depclean.core.analysis.asm; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; @@ -28,10 +31,6 @@ import se.kth.depclean.core.analysis.ClassFileVisitor; import se.kth.depclean.core.analysis.graph.DefaultCallGraph; -import java.io.IOException; -import java.io.InputStream; -import java.util.Set; - /** * Computes the set of classes referenced by visited class files, using * DependencyVisitor. @@ -41,65 +40,65 @@ @Slf4j public class DependencyClassFileVisitor implements ClassFileVisitor { - private final ResultCollector resultCollector = new ResultCollector(); + private final ResultCollector resultCollector = new ResultCollector(); - /* - * @see org.apache.invoke.shared.dependency.analyzer.ClassFileVisitor#visitClass(java.lang.String, - * java.io.InputStream) - */ - public void visitClass(String className, InputStream in) { - try { - ClassReader reader = new ClassReader(in); + /* + * @see org.apache.invoke.shared.dependency.analyzer.ClassFileVisitor#visitClass(java.lang.String, + * java.io.InputStream) + */ + public void visitClass(String className, InputStream in) { + try { + ClassReader reader = new ClassReader(in); - final Set constantPoolClassRefs = ConstantPoolParser.getConstantPoolClassReferences(reader.b); - for (String string : constantPoolClassRefs) { - resultCollector.addName(string); - } + final Set constantPoolClassRefs = ConstantPoolParser.getConstantPoolClassReferences(reader.b); + for (String string : constantPoolClassRefs) { + resultCollector.addName(string); + } - /* visit class members */ - AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor( - resultCollector - ); - SignatureVisitor signatureVisitor = new DefaultSignatureVisitor( - resultCollector - ); - FieldVisitor fieldVisitor = new DefaultFieldVisitor( - annotationVisitor, - resultCollector - ); - MethodVisitor methodVisitor = new DefaultMethodVisitor( - annotationVisitor, - signatureVisitor, - resultCollector - ); + /* visit class members */ + AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor( + resultCollector + ); + SignatureVisitor signatureVisitor = new DefaultSignatureVisitor( + resultCollector + ); + FieldVisitor fieldVisitor = new DefaultFieldVisitor( + annotationVisitor, + resultCollector + ); + MethodVisitor methodVisitor = new DefaultMethodVisitor( + annotationVisitor, + signatureVisitor, + resultCollector + ); - DefaultClassVisitor defaultClassVisitor = new DefaultClassVisitor( - signatureVisitor, - annotationVisitor, - fieldVisitor, - methodVisitor, - resultCollector - ); + DefaultClassVisitor defaultClassVisitor = new DefaultClassVisitor( + signatureVisitor, + annotationVisitor, + fieldVisitor, + methodVisitor, + resultCollector + ); - reader.accept(defaultClassVisitor, 0); + reader.accept(defaultClassVisitor, 0); - // inset edge in the graph based on the bytecode analysis - DefaultCallGraph.addEdge(className, resultCollector.getDependencies()); + // inset edge in the graph based on the bytecode analysis + DefaultCallGraph.addEdge(className, resultCollector.getDependencies()); - } catch (IndexOutOfBoundsException | IOException e) { - // some bug inside ASM causes an IOB exception. Log it and move on? - // this happens when the class isn't valid. - log.warn("Unable to process: " + className); - } - resultCollector.clearClasses(); - } + } catch (IndexOutOfBoundsException | IOException e) { + // some bug inside ASM causes an IOB exception. Log it and move on? + // this happens when the class isn't valid. + log.warn("Unable to process: " + className); + } + resultCollector.clearClasses(); + } - // public methods --------------------------------------------------------- + // public methods --------------------------------------------------------- - /** - * @return the set of classes referenced by visited class files - */ - public Set getDependencies() { - return resultCollector.getDependencies(); - } + /** + * @return the set of classes referenced by visited class files + */ + public Set getDependencies() { + return resultCollector.getDependencies(); + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ResultCollector.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ResultCollector.java index 10497b73..d806acdb 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ResultCollector.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ResultCollector.java @@ -19,70 +19,69 @@ package se.kth.depclean.core.analysis.asm; -import org.objectweb.asm.Type; - import java.util.HashSet; import java.util.Set; +import org.objectweb.asm.Type; public class ResultCollector { - private final Set classes = new HashSet<>(); + private final Set classes = new HashSet<>(); - public Set getDependencies() { - return classes; - } + public Set getDependencies() { + return classes; + } - public void clearClasses() { - classes.clear(); - } + public void clearClasses() { + classes.clear(); + } - void addDesc(final String desc) { - addType(Type.getType(desc)); - } + void addDesc(final String desc) { + addType(Type.getType(desc)); + } - void addType(final Type t) { - switch (t.getSort()) { - case Type.ARRAY: - addType(t.getElementType()); - break; - case Type.OBJECT: - addName(t.getClassName().replace('.', '/')); - break; - default: - } + void addType(final Type t) { + switch (t.getSort()) { + case Type.ARRAY: + addType(t.getElementType()); + break; + case Type.OBJECT: + addName(t.getClassName().replace('.', '/')); + break; + default: } + } - public void add(String name) { - classes.add(name); - } + public void add(String name) { + classes.add(name); + } - void addNames(final String[] names) { - if (names == null) { - return; - } - for (String name : names) { - addName(name); - } + void addNames(final String[] names) { + if (names == null) { + return; } + for (String name : names) { + addName(name); + } + } - public void addName(String name) { - if (name == null) { - return; - } - // decode arrays - if (name.startsWith("[L") && name.endsWith(";")) { - name = name.substring(2, name.length() - 1); - } - // decode internal representation - name = name.replace('/', '.'); - classes.add(name); + public void addName(String name) { + if (name == null) { + return; + } + // decode arrays + if (name.startsWith("[L") && name.endsWith(";")) { + name = name.substring(2, name.length() - 1); } + // decode internal representation + name = name.replace('/', '.'); + classes.add(name); + } - void addMethodDesc(final String desc) { - addType(Type.getReturnType(desc)); - Type[] types = Type.getArgumentTypes(desc); - for (Type type : types) { - addType(type); - } + void addMethodDesc(final String desc) { + addType(Type.getReturnType(desc)); + Type[] types = Type.getArgumentTypes(desc); + for (Type type : types) { + addType(type); } + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/ClassMembersVisitorCounter.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/ClassMembersVisitorCounter.java index c51526c7..e12c19a3 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/ClassMembersVisitorCounter.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/ClassMembersVisitorCounter.java @@ -19,73 +19,73 @@ public final class ClassMembersVisitorCounter { - /** - * Number of types. - */ - private static long nbVisitedTypes; - - /** - * Number of fields. - */ - private static long nbVisitedFields; - - /** - * Number of methods. - */ - private static long nbVisitedMethods; - - /** - * Number of annotations. - */ - private static long nbVisitedAnnotations; - - private ClassMembersVisitorCounter() { - throw new IllegalStateException("Utility class"); - } - - public static void resetClassCounters() { - nbVisitedTypes = 0; - nbVisitedFields = 0; - nbVisitedMethods = 0; - nbVisitedAnnotations = 0; - } - - public static void markAsNotFoundClassCounters() { - nbVisitedTypes = -1; - nbVisitedFields = -1; - nbVisitedMethods = -1; - nbVisitedAnnotations = -1; - } - - public static void addVisitedClass() { - nbVisitedTypes++; - } - - public static void addVisitedField() { - nbVisitedFields++; - } - - public static void addVisitedMethod() { - nbVisitedMethods++; - } - - public static void addVisitedAnnotation() { - nbVisitedAnnotations++; - } - - public static long getNbVisitedTypes() { - return nbVisitedTypes; - } - - public static long getNbVisitedFields() { - return nbVisitedFields; - } - - public static long getNbVisitedMethods() { - return nbVisitedMethods; - } - - public static long getNbVisitedAnnotations() { - return nbVisitedAnnotations; - } + /** + * Number of types. + */ + private static long nbVisitedTypes; + + /** + * Number of fields. + */ + private static long nbVisitedFields; + + /** + * Number of methods. + */ + private static long nbVisitedMethods; + + /** + * Number of annotations. + */ + private static long nbVisitedAnnotations; + + private ClassMembersVisitorCounter() { + throw new IllegalStateException("Utility class"); + } + + public static void resetClassCounters() { + nbVisitedTypes = 0; + nbVisitedFields = 0; + nbVisitedMethods = 0; + nbVisitedAnnotations = 0; + } + + public static void markAsNotFoundClassCounters() { + nbVisitedTypes = -1; + nbVisitedFields = -1; + nbVisitedMethods = -1; + nbVisitedAnnotations = -1; + } + + public static void addVisitedClass() { + nbVisitedTypes++; + } + + public static void addVisitedField() { + nbVisitedFields++; + } + + public static void addVisitedMethod() { + nbVisitedMethods++; + } + + public static void addVisitedAnnotation() { + nbVisitedAnnotations++; + } + + public static long getNbVisitedTypes() { + return nbVisitedTypes; + } + + public static long getNbVisitedFields() { + return nbVisitedFields; + } + + public static long getNbVisitedMethods() { + return nbVisitedMethods; + } + + public static long getNbVisitedAnnotations() { + return nbVisitedAnnotations; + } } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java index 8db424bb..83ee958c 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java @@ -16,86 +16,87 @@ */ package se.kth.depclean.core.analysis.graph; -import org.jgrapht.graph.AbstractBaseGraph; -import org.jgrapht.graph.DefaultDirectedGraph; -import org.jgrapht.graph.DefaultEdge; -import org.jgrapht.traverse.DepthFirstIterator; - import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.traverse.DepthFirstIterator; public class DefaultCallGraph { - private static final AbstractBaseGraph directedGraph = new DefaultDirectedGraph<>(DefaultEdge.class); - private static final Set projectVertices = new HashSet<>(); - private static final Map> usagesPerClass = new HashMap<>(); + private static final AbstractBaseGraph directedGraph = new DefaultDirectedGraph<>( + DefaultEdge.class); + private static final Set projectVertices = new HashSet<>(); + private static final Map> usagesPerClass = new HashMap<>(); - public static void addEdge(String clazz, Set referencedClassMembers) { - directedGraph.addVertex(clazz); - for (String referencedClassMember : referencedClassMembers) { - if (!directedGraph.containsVertex(referencedClassMember)) { - directedGraph.addVertex(referencedClassMember); - } - directedGraph.addEdge(clazz, referencedClassMember); - projectVertices.add(clazz); + public static void addEdge(String clazz, Set referencedClassMembers) { + directedGraph.addVertex(clazz); + for (String referencedClassMember : referencedClassMembers) { + if (!directedGraph.containsVertex(referencedClassMember)) { + directedGraph.addVertex(referencedClassMember); + } + directedGraph.addEdge(clazz, referencedClassMember); + projectVertices.add(clazz); - // Save the pair [class -> referencedClassMember] for further analysis - addReferencedClassMember(clazz, referencedClassMember); - } + // Save the pair [class -> referencedClassMember] for further analysis + addReferencedClassMember(clazz, referencedClassMember); } + } - private static void addReferencedClassMember(String clazz, String referencedClassMember) { - // System.out.println("\t" + clazz + " -> " + referencedClassMember); - Set s = usagesPerClass.computeIfAbsent(clazz, k -> new HashSet<>()); - s.add(referencedClassMember); - } + private static void addReferencedClassMember(String clazz, String referencedClassMember) { + // System.out.println("\t" + clazz + " -> " + referencedClassMember); + Set s = usagesPerClass.computeIfAbsent(clazz, k -> new HashSet<>()); + s.add(referencedClassMember); + } - public static Set referencedClassMembers(Set projectClasses) { - // System.out.println("project classes: " + projectClasses); - Set allReferencedClassMembers = new HashSet<>(); - for (String projectClass : projectClasses) { - allReferencedClassMembers.addAll(traverse(projectClass)); - } - // System.out.println("All referenced class members: " + allReferencedClassMembers); - return allReferencedClassMembers; + public static Set referencedClassMembers(Set projectClasses) { + // System.out.println("project classes: " + projectClasses); + Set allReferencedClassMembers = new HashSet<>(); + for (String projectClass : projectClasses) { + allReferencedClassMembers.addAll(traverse(projectClass)); } + // System.out.println("All referenced class members: " + allReferencedClassMembers); + return allReferencedClassMembers; + } - /** - * Traverse the graph using DFS. - * @param start The starting vertex. - * @return The set of all visited vertices. - */ - private static Set traverse(String start) { - Set referencedClassMembers = new HashSet<>(); - Iterator iterator = new DepthFirstIterator<>(directedGraph, start); - while (iterator.hasNext()) { - referencedClassMembers.add(iterator.next()); - } - return referencedClassMembers; + /** + * Traverse the graph using DFS. + * + * @param start The starting vertex. + * @return The set of all visited vertices. + */ + private static Set traverse(String start) { + Set referencedClassMembers = new HashSet<>(); + Iterator iterator = new DepthFirstIterator<>(directedGraph, start); + while (iterator.hasNext()) { + referencedClassMembers.add(iterator.next()); } + return referencedClassMembers; + } - public AbstractBaseGraph getDirectedGraph() { - return directedGraph; - } + public AbstractBaseGraph getDirectedGraph() { + return directedGraph; + } - public static Set getProjectVertices() { - return projectVertices; - } + public static Set getProjectVertices() { + return projectVertices; + } - public static Set getVertices() { - return directedGraph.vertexSet(); - } + public static Set getVertices() { + return directedGraph.vertexSet(); + } - public static void cleanDirectedGraph() { - directedGraph.vertexSet().clear(); - directedGraph.edgeSet().clear(); - } + public static void cleanDirectedGraph() { + directedGraph.vertexSet().clear(); + directedGraph.edgeSet().clear(); + } - public Map> getUsagesPerClass() { - return usagesPerClass; - } + public Map> getUsagesPerClass() { + return usagesPerClass; + } } diff --git a/depclean-core/src/test/java/se/kth/depclean/core/analysis/ClassFileVisitorUtilsTest.java b/depclean-core/src/test/java/se/kth/depclean/core/analysis/ClassFileVisitorUtilsTest.java index 212edf5a..d4057237 100644 --- a/depclean-core/src/test/java/se/kth/depclean/core/analysis/ClassFileVisitorUtilsTest.java +++ b/depclean-core/src/test/java/se/kth/depclean/core/analysis/ClassFileVisitorUtilsTest.java @@ -7,16 +7,16 @@ public class ClassFileVisitorUtilsTest { - @Before - public void setUp() throws Exception { - } + @Before + public void setUp() throws Exception { + } - @After - public void tearDown() throws Exception { - } + @After + public void tearDown() throws Exception { + } - @Test - public void accept() { - Assert.assertTrue(true); - } + @Test + public void accept() { + Assert.assertTrue(true); + } } \ No newline at end of file diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java index b6607946..df46573e 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java @@ -18,6 +18,22 @@ package se.kth.depclean; import fr.dutra.tools.maven.deptree.core.ParseException; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.maven.artifact.Artifact; @@ -51,639 +67,656 @@ import se.kth.depclean.util.MavenInvoker; import se.kth.depclean.util.json.ParsedDependencies; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - /** - * This Maven mojo is the main class of DepClean. - * DepClean is built on top of the maven-dependency-analyzer component. - * It produces a clean copy of the project's pom file, without bloated dependencies. + * This Maven mojo is the main class of DepClean. DepClean is built on top of the + * maven-dependency-analyzer component. It produces a clean copy of the project's pom file, without + * bloated dependencies. * - * @see - * - * @see - * + * @see + * @see */ @Mojo(name = "depclean", defaultPhase = LifecyclePhase.PACKAGE, - requiresDependencyCollection = ResolutionScope.TEST, - requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) + requiresDependencyCollection = ResolutionScope.TEST, + requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) @Slf4j public class DepCleanMojo extends AbstractMojo { - private static final String SEPARATOR = "-------------------------------------------------------"; - public static final String DIRECTORY_TO_COPY_DEPENDENCIES = "dependency"; - - /** - * The Maven project to analyze. - */ - @Parameter(defaultValue = "${project}", readonly = true) - private MavenProject project; - - /** - * The Maven session to analyze. - */ - @Parameter(defaultValue = "${session}", readonly = true) - private MavenSession session; - - /** - * If this is true, DepClean creates a debloated version of the pom without unused dependencies, - * called "debloated-pom.xml", in root of the project. - */ - @Parameter(property = "creatPomDebloated", defaultValue = "false") - private boolean createPomDebloated; - - /** - * If this is true, DepClean creates a JSON file with the result of the analysis. The file is called - * "debloat-result.json" and it is located in the root of the project. - */ - @Parameter(property = "createResultJson", defaultValue = "false") - private boolean createResultJson; - - /** - * Add a list of dependencies, identified by their coordinates, to be ignored by DepClean during the analysis and - * considered as used dependencies. Useful to override incomplete result caused by bytecode-level analysis - * Dependency format is groupId:artifactId:version. - */ - @Parameter(property = "ignoreDependencies") - private Set ignoreDependencies; - - /** - * Ignore dependencies with specific scopes from the DepClean analysis. - */ - @Parameter(property = "ignoreScopes") - private Set ignoreScopes; - - /** - * If this is true, DepClean will not analyze the test sources in the project, - * and, therefore, the dependencies that are only used for testing will be considered - * unused. This property is useful to detect dependencies that have a compile scope but - * are only used during testing. Hence, these dependencies should have a test scope. - */ - @Parameter(property = "ignoreTests", defaultValue = "false") - private boolean ignoreTests; - - /** - * If this is true, and DepClean reported any unused direct dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. - */ - @Parameter(property = "failIfUnusedDirect", defaultValue = "false") - private boolean failIfUnusedDirect; - - /** - * If this is true, and DepClean reported any unused transitive dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. - */ - @Parameter(property = "failIfUnusedTransitive", defaultValue = "false") - private boolean failIfUnusedTransitive; - - /** - * If this is true, and DepClean reported any unused inherited dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. - */ - @Parameter(property = "failIfUnusedInherited", defaultValue = "false") - private boolean failIfUnusedInherited; - - /** - * Skip plugin execution completely. - */ - @Parameter(property = "skipDepClean", defaultValue = "false") - private boolean skipDepClean; - - @Component - private ProjectBuilder mavenProjectBuilder; - - @Component - private RepositorySystem repositorySystem; - - @Component(hint = "default") - private DependencyGraphBuilder dependencyGraphBuilder; - - /** - * Write pom file to the filesystem. - * - * @param pomFile The path to the pom. - * @param model The maven model to get the pom from. - * @throws IOException In case of any IO issue. - */ - private static void writePom(final Path pomFile, final Model model) throws IOException { - MavenXpp3Writer writer = new MavenXpp3Writer(); - writer.write(Files.newBufferedWriter(pomFile), model); - } - - /** - * Print the status of the depenencies to the standard output. - * The format is: "[coordinates][scope] [(size)]" - * - * @param sizeOfDependencies A map with the size of the dependencies. - * @param dependencies The set dependencies to print. - */ - private void printDependencies(Map sizeOfDependencies, Set dependencies) { - dependencies - .stream() - .sorted(Comparator.comparing(o -> getSizeOfDependency(sizeOfDependencies, o))) - .collect(Collectors.toCollection(LinkedList::new)) - .descendingIterator() - .forEachRemaining(s -> printString("\t" + s + " (" + getSize(s, sizeOfDependencies) + ")")); - } - - /** - * Utility method to obtain the size of a dependency from a map of dependency -> size. If the size of the dependency - * cannot be obtained form the map (no key with the name of the dependency exists), then it returns 0. - * - * @param sizeOfDependencies A map of dependency -> size. - * @param dependency The coordinates of a dependency. - * @return The size of the dependency if its name is a key in the map, otherwise it returns 0. - */ - private Long getSizeOfDependency(Map sizeOfDependencies, String dependency) { - Long size = sizeOfDependencies.get(dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar"); - if (size != null) { - return size; - } else { - // The name of the dependency does not match with the name of the download jar, so we keep assume the size - // cannot be obtained and return 0. - return Long.valueOf(0); - } - } - - /** - * Get the size of the dependency in human readable format. - * - * @param dependency The dependency. - * @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the downloaded jar file - * i.e., [artifactId]-[version].jar - * @return The human readable representation of the dependency size. - */ - private String getSize(String dependency, Map sizeOfDependencies) { - String dep = dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar"; - if (sizeOfDependencies.containsKey(dep)) { - return FileUtils.byteCountToDisplaySize(sizeOfDependencies.get(dep)); - } else { - // The size cannot be obtained. - return "size unknown"; + private static final String SEPARATOR = "-------------------------------------------------------"; + public static final String DIRECTORY_TO_COPY_DEPENDENCIES = "dependency"; + + /** + * The Maven project to analyze. + */ + @Parameter(defaultValue = "${project}", readonly = true) + private MavenProject project; + + /** + * The Maven session to analyze. + */ + @Parameter(defaultValue = "${session}", readonly = true) + private MavenSession session; + + /** + * If this is true, DepClean creates a debloated version of the pom without unused dependencies, + * called "debloated-pom.xml", in root of the project. + */ + @Parameter(property = "creatPomDebloated", defaultValue = "false") + private boolean createPomDebloated; + + /** + * If this is true, DepClean creates a JSON file with the result of the analysis. The file is + * called "debloat-result.json" and it is located in the root of the project. + */ + @Parameter(property = "createResultJson", defaultValue = "false") + private boolean createResultJson; + + /** + * Add a list of dependencies, identified by their coordinates, to be ignored by DepClean during + * the analysis and considered as used dependencies. Useful to override incomplete result caused + * by bytecode-level analysis Dependency format is groupId:artifactId:version. + */ + @Parameter(property = "ignoreDependencies") + private Set ignoreDependencies; + + /** + * Ignore dependencies with specific scopes from the DepClean analysis. + */ + @Parameter(property = "ignoreScopes") + private Set ignoreScopes; + + /** + * If this is true, DepClean will not analyze the test sources in the project, and, therefore, the + * dependencies that are only used for testing will be considered unused. This property is useful + * to detect dependencies that have a compile scope but are only used during testing. Hence, these + * dependencies should have a test scope. + */ + @Parameter(property = "ignoreTests", defaultValue = "false") + private boolean ignoreTests; + + /** + * If this is true, and DepClean reported any unused direct dependency in the dependency tree, + * then the project's build fails immediately after running DepClean. + */ + @Parameter(property = "failIfUnusedDirect", defaultValue = "false") + private boolean failIfUnusedDirect; + + /** + * If this is true, and DepClean reported any unused transitive dependency in the dependency tree, + * then the project's build fails immediately after running DepClean. + */ + @Parameter(property = "failIfUnusedTransitive", defaultValue = "false") + private boolean failIfUnusedTransitive; + + /** + * If this is true, and DepClean reported any unused inherited dependency in the dependency tree, + * then the project's build fails immediately after running DepClean. + */ + @Parameter(property = "failIfUnusedInherited", defaultValue = "false") + private boolean failIfUnusedInherited; + + /** + * Skip plugin execution completely. + */ + @Parameter(property = "skipDepClean", defaultValue = "false") + private boolean skipDepClean; + + @Component + private ProjectBuilder mavenProjectBuilder; + + @Component + private RepositorySystem repositorySystem; + + @Component(hint = "default") + private DependencyGraphBuilder dependencyGraphBuilder; + + /** + * Write pom file to the filesystem. + * + * @param pomFile The path to the pom. + * @param model The maven model to get the pom from. + * @throws IOException In case of any IO issue. + */ + private static void writePom(final Path pomFile, final Model model) throws IOException { + MavenXpp3Writer writer = new MavenXpp3Writer(); + writer.write(Files.newBufferedWriter(pomFile), model); + } + + /** + * Print the status of the depenencies to the standard output. The format is: + * "[coordinates][scope] [(size)]" + * + * @param sizeOfDependencies A map with the size of the dependencies. + * @param dependencies The set dependencies to print. + */ + private void printDependencies(Map sizeOfDependencies, Set dependencies) { + dependencies + .stream() + .sorted(Comparator.comparing(o -> getSizeOfDependency(sizeOfDependencies, o))) + .collect(Collectors.toCollection(LinkedList::new)) + .descendingIterator() + .forEachRemaining(s -> printString("\t" + s + " (" + getSize(s, sizeOfDependencies) + ")")); + } + + /** + * Utility method to obtain the size of a dependency from a map of dependency -> size. If the size + * of the dependency cannot be obtained form the map (no key with the name of the dependency + * exists), then it returns 0. + * + * @param sizeOfDependencies A map of dependency -> size. + * @param dependency The coordinates of a dependency. + * @return The size of the dependency if its name is a key in the map, otherwise it returns 0. + */ + private Long getSizeOfDependency(Map sizeOfDependencies, String dependency) { + Long size = sizeOfDependencies + .get(dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar"); + if (size != null) { + return size; + } else { + // The name of the dependency does not match with the name of the download jar, so we keep assume the size + // cannot be obtained and return 0. + return Long.valueOf(0); + } + } + + /** + * Get the size of the dependency in human readable format. + * + * @param dependency The dependency. + * @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the + * downloaded jar file i.e., [artifactId]-[version].jar + * @return The human readable representation of the dependency size. + */ + private String getSize(String dependency, Map sizeOfDependencies) { + String dep = dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar"; + if (sizeOfDependencies.containsKey(dep)) { + return FileUtils.byteCountToDisplaySize(sizeOfDependencies.get(dep)); + } else { + // The size cannot be obtained. + return "size unknown"; + } + } + + /** + * Exclude artifacts with specific scopes from the analysis. + * + * @param artifacts The set of artifacts to analyze. + * @return The set of artifacts for which the scope has not been excluded. + */ + private Set excludeScope(Set artifacts) { + Set nonExcludedArtifacts = new HashSet<>(); + for (Artifact artifact : artifacts) { + if (!ignoreScopes.contains(artifact.getScope())) { + nonExcludedArtifacts.add(artifact); } - } - - /** - * Exclude artifacts with specific scopes from the analysis. - * - * @param artifacts The set of artifacts to analyze. - * @return The set of artifacts for which the scope has not been excluded. - */ - private Set excludeScope(Set artifacts) { - Set nonExcludedArtifacts = new HashSet<>(); - for (Artifact artifact : artifacts) { - if (!ignoreScopes.contains(artifact.getScope())) { - nonExcludedArtifacts.add(artifact); - } - } - return nonExcludedArtifacts; - } - - /** - * Determine if an artifact is a direct or transitive child of a dependency. - * - * @param artifact The artifact. - * @param dependency The dependency - * @return true if the artifact is a child of a dependency in the dependency tree. - * @throws DependencyGraphBuilderException If the graph cannot be constructed. - */ - private boolean isChildren(Artifact artifact, Dependency dependency) throws DependencyGraphBuilderException { - List dependencyNodes = getDependencyNodes(); - for (DependencyNode node : dependencyNodes) { - Dependency dependencyNode = createDependency(node.getArtifact()); - if (dependency.getGroupId().equals(dependencyNode.getGroupId()) && - dependency.getArtifactId().equals(dependencyNode.getArtifactId())) { - // now we are in the target dependency - for (DependencyNode child : node.getChildren()) { - if (child.getArtifact().equals(artifact)) { - // the dependency contains the artifact as a child node - return true; - } - - } - } + } + return nonExcludedArtifacts; + } + + /** + * Determine if an artifact is a direct or transitive child of a dependency. + * + * @param artifact The artifact. + * @param dependency The dependency + * @return true if the artifact is a child of a dependency in the dependency tree. + * @throws DependencyGraphBuilderException If the graph cannot be constructed. + */ + private boolean isChildren(Artifact artifact, Dependency dependency) + throws DependencyGraphBuilderException { + List dependencyNodes = getDependencyNodes(); + for (DependencyNode node : dependencyNodes) { + Dependency dependencyNode = createDependency(node.getArtifact()); + if (dependency.getGroupId().equals(dependencyNode.getGroupId()) && + dependency.getArtifactId().equals(dependencyNode.getArtifactId())) { + // now we are in the target dependency + for (DependencyNode child : node.getChildren()) { + if (child.getArtifact().equals(artifact)) { + // the dependency contains the artifact as a child node + return true; + } + + } } - return false; - } - - /** - * This method returns a list of dependency nodes from a graph of dependency tree. - * - * @return The nodes in the dependency graph. - * @throws DependencyGraphBuilderException If the graph cannot be built. - */ - private List getDependencyNodes() throws DependencyGraphBuilderException { - ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); - buildingRequest.setProject(project); - DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null); - CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor(); - rootNode.accept(visitor); - return visitor.getNodes(); - } - - /** - * This method creates a {@link org.apache.maven.model.Dependency} object from a - * Maven {@link org.apache.maven.artifact.Artifact}. - * - * @param artifact The artifact to create the dependency. - * @return The Dependency object. - */ - private Dependency createDependency(final Artifact artifact) { - Dependency dependency = new Dependency(); - dependency.setGroupId(artifact.getGroupId()); - dependency.setArtifactId(artifact.getArtifactId()); - dependency.setVersion(artifact.getVersion()); - if (artifact.hasClassifier()) { - dependency.setClassifier(artifact.getClassifier()); - } - dependency.setOptional(artifact.isOptional()); - dependency.setScope(artifact.getScope()); - dependency.setType(artifact.getType()); - return dependency; - } - - private void printString(String string) { - System.out.println(string); //NOSONAR avoid a warning of non-used logger - } - - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - if (skipDepClean) { - getLog().info("Skipping DepClean plugin execution"); - return; - } - - printString(SEPARATOR); - getLog().info("Starting DepClean dependency analysis"); - - File pomFile = new File(project.getBasedir().getAbsolutePath() + File.separator + "pom.xml"); - - String packaging = project.getPackaging(); - if (packaging.equals("pom")) { - getLog().info("Skipping because packaging type " + packaging + "."); - return; - } - - /* Build Maven model to manipulate the pom */ - Model model; - FileReader reader; - MavenXpp3Reader mavenReader = new MavenXpp3Reader(); + } + return false; + } + + /** + * This method returns a list of dependency nodes from a graph of dependency tree. + * + * @return The nodes in the dependency graph. + * @throws DependencyGraphBuilderException If the graph cannot be built. + */ + private List getDependencyNodes() throws DependencyGraphBuilderException { + ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest( + session.getProjectBuildingRequest()); + buildingRequest.setProject(project); + DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null); + CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor(); + rootNode.accept(visitor); + return visitor.getNodes(); + } + + /** + * This method creates a {@link org.apache.maven.model.Dependency} object from a Maven {@link + * org.apache.maven.artifact.Artifact}. + * + * @param artifact The artifact to create the dependency. + * @return The Dependency object. + */ + private Dependency createDependency(final Artifact artifact) { + Dependency dependency = new Dependency(); + dependency.setGroupId(artifact.getGroupId()); + dependency.setArtifactId(artifact.getArtifactId()); + dependency.setVersion(artifact.getVersion()); + if (artifact.hasClassifier()) { + dependency.setClassifier(artifact.getClassifier()); + } + dependency.setOptional(artifact.isOptional()); + dependency.setScope(artifact.getScope()); + dependency.setType(artifact.getType()); + return dependency; + } + + private void printString(String string) { + System.out.println(string); //NOSONAR avoid a warning of non-used logger + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (skipDepClean) { + getLog().info("Skipping DepClean plugin execution"); + return; + } + + printString(SEPARATOR); + getLog().info("Starting DepClean dependency analysis"); + + File pomFile = new File(project.getBasedir().getAbsolutePath() + File.separator + "pom.xml"); + + String packaging = project.getPackaging(); + if (packaging.equals("pom")) { + getLog().info("Skipping because packaging type " + packaging + "."); + return; + } + + /* Build Maven model to manipulate the pom */ + Model model; + FileReader reader; + MavenXpp3Reader mavenReader = new MavenXpp3Reader(); + try { + reader = new FileReader(pomFile); + model = mavenReader.read(reader); + model.setPomFile(pomFile); + } catch (Exception ex) { + getLog().error("Unable to build the maven project."); + return; + } + + /* Copy direct dependencies locally */ + try { + MavenInvoker.runCommand("mvn dependency:copy-dependencies -DoutputDirectory=" + + project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES); + } catch (IOException | InterruptedException e) { + getLog().error("Unable to resolve all the dependencies."); + Thread.currentThread().interrupt(); + return; + } + + // TODO remove this workaround later + if (new File(project.getBuild().getDirectory() + File.separator + "libs").exists()) { try { - reader = new FileReader(pomFile); - model = mavenReader.read(reader); - model.setPomFile(pomFile); - } catch (Exception ex) { - getLog().error("Unable to build the maven project."); - return; + FileUtils + .copyDirectory(new File(project.getBuild().getDirectory() + File.separator + "libs"), + new File(project.getBuild().getDirectory() + File.separator + + DIRECTORY_TO_COPY_DEPENDENCIES) + ); + } catch (IOException e) { + getLog().error("Error copying directory libs to dependency"); } - - /* Copy direct dependencies locally */ - try { - MavenInvoker.runCommand("mvn dependency:copy-dependencies -DoutputDirectory=" + - project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES); - } catch (IOException | InterruptedException e) { - getLog().error("Unable to resolve all the dependencies."); - Thread.currentThread().interrupt(); - return; + } + + /* Get the size of all the dependencies */ + Map sizeOfDependencies = new HashMap<>(); + // First, add the size of the project, as the sum of all the files in target/classes + String projectJar = project.getArtifactId() + "-" + project.getVersion() + ".jar"; + long projectSize = FileUtils.sizeOf(new File(project.getBuild().getOutputDirectory())); + sizeOfDependencies.put(projectJar, projectSize); + if (Files.exists(Path.of( + project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES))) { + Iterator iterator = FileUtils.iterateFiles( + new File( + project.getBuild().getDirectory() + File.separator + + DIRECTORY_TO_COPY_DEPENDENCIES), new String[]{"jar"}, true); + while (iterator.hasNext()) { + File file = iterator.next(); + sizeOfDependencies.put(file.getName(), FileUtils.sizeOf(file)); } - - // TODO remove this workaround later - if (new File(project.getBuild().getDirectory() + File.separator + "libs").exists()) { - try { - FileUtils.copyDirectory(new File(project.getBuild().getDirectory() + File.separator + "libs"), - new File(project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES) - ); - } catch (IOException e) { - getLog().error("Error copying directory libs to dependency"); - } - } - - /* Get the size of all the dependencies */ - Map sizeOfDependencies = new HashMap<>(); - // First, add the size of the project, as the sum of all the files in target/classes - String projectJar = project.getArtifactId() + "-" + project.getVersion() + ".jar"; - long projectSize = FileUtils.sizeOf(new File(project.getBuild().getOutputDirectory())); - sizeOfDependencies.put(projectJar, projectSize); - if (Files.exists(Path.of(project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES))) { - Iterator iterator = FileUtils.iterateFiles( - new File( - project.getBuild().getDirectory() + File.separator - + DIRECTORY_TO_COPY_DEPENDENCIES), new String[]{"jar"}, true); - while (iterator.hasNext()) { - File file = iterator.next(); - sizeOfDependencies.put(file.getName(), FileUtils.sizeOf(file)); - } - } else { - log.warn("Dependencies where not copied locally"); - } - - /* Decompress dependencies */ - String dependencyDirectoryName = project.getBuild().getDirectory() + "/" + DIRECTORY_TO_COPY_DEPENDENCIES; - File dependencyDirectory = new File(dependencyDirectoryName); - if (dependencyDirectory.exists()) { - JarUtils.decompressJars(dependencyDirectoryName); - } - - /* Analyze dependencies usage status */ - ProjectDependencyAnalysis projectDependencyAnalysis; - DefaultProjectDependencyAnalyzer dependencyAnalyzer = new DefaultProjectDependencyAnalyzer(ignoreTests); - try { - projectDependencyAnalysis = dependencyAnalyzer.analyze(project); - } catch (ProjectDependencyAnalyzerException e) { - getLog().error("Unable to analyze dependencies."); - return; - } - - Set usedTransitiveArtifacts = projectDependencyAnalysis.getUsedUndeclaredArtifacts(); - Set usedDirectArtifacts = projectDependencyAnalysis.getUsedDeclaredArtifacts(); - Set unusedDirectArtifacts = projectDependencyAnalysis.getUnusedDeclaredArtifacts(); - Set unusedTransitiveArtifacts = project.getArtifacts(); - - unusedTransitiveArtifacts.removeAll(usedDirectArtifacts); - unusedTransitiveArtifacts.removeAll(usedTransitiveArtifacts); - unusedTransitiveArtifacts.removeAll(unusedDirectArtifacts); - - /* Exclude dependencies with specific scopes from the DepClean analysis */ + } else { + log.warn("Dependencies where not copied locally"); + } + + /* Decompress dependencies */ + String dependencyDirectoryName = + project.getBuild().getDirectory() + "/" + DIRECTORY_TO_COPY_DEPENDENCIES; + File dependencyDirectory = new File(dependencyDirectoryName); + if (dependencyDirectory.exists()) { + JarUtils.decompressJars(dependencyDirectoryName); + } + + /* Analyze dependencies usage status */ + ProjectDependencyAnalysis projectDependencyAnalysis; + DefaultProjectDependencyAnalyzer dependencyAnalyzer = new DefaultProjectDependencyAnalyzer( + ignoreTests); + try { + projectDependencyAnalysis = dependencyAnalyzer.analyze(project); + } catch (ProjectDependencyAnalyzerException e) { + getLog().error("Unable to analyze dependencies."); + return; + } + + Set usedTransitiveArtifacts = projectDependencyAnalysis.getUsedUndeclaredArtifacts(); + Set usedDirectArtifacts = projectDependencyAnalysis.getUsedDeclaredArtifacts(); + Set unusedDirectArtifacts = projectDependencyAnalysis.getUnusedDeclaredArtifacts(); + Set unusedTransitiveArtifacts = project.getArtifacts(); + + unusedTransitiveArtifacts.removeAll(usedDirectArtifacts); + unusedTransitiveArtifacts.removeAll(usedTransitiveArtifacts); + unusedTransitiveArtifacts.removeAll(unusedDirectArtifacts); + + /* Exclude dependencies with specific scopes from the DepClean analysis */ + if (!ignoreScopes.isEmpty()) { + printString("Ignoring dependencies with scope(s): " + ignoreScopes.toString()); if (!ignoreScopes.isEmpty()) { - printString("Ignoring dependencies with scope(s): " + ignoreScopes.toString()); - if (!ignoreScopes.isEmpty()) { - usedTransitiveArtifacts = excludeScope(usedTransitiveArtifacts); - usedDirectArtifacts = excludeScope(usedDirectArtifacts); - unusedDirectArtifacts = excludeScope(unusedDirectArtifacts); - unusedTransitiveArtifacts = excludeScope(unusedTransitiveArtifacts); - } + usedTransitiveArtifacts = excludeScope(usedTransitiveArtifacts); + usedDirectArtifacts = excludeScope(usedDirectArtifacts); + unusedDirectArtifacts = excludeScope(unusedDirectArtifacts); + unusedTransitiveArtifacts = excludeScope(unusedTransitiveArtifacts); } - - /* Use artifacts coordinates for the report instead of the Artifact object */ - - // List of dependencies declared in the POM - List dependencies = model.getDependencies(); - Set declaredArtifactsGAs = new HashSet<>(); - for (Dependency dep : dependencies) { - declaredArtifactsGAs.add(dep.getGroupId() + ":" + dep.getArtifactId()); + } + + /* Use artifacts coordinates for the report instead of the Artifact object */ + + // List of dependencies declared in the POM + List dependencies = model.getDependencies(); + Set declaredArtifactsGAs = new HashSet<>(); + for (Dependency dep : dependencies) { + declaredArtifactsGAs.add(dep.getGroupId() + ":" + dep.getArtifactId()); + } + + // --- used dependencies + Set usedDirectArtifactsCoordinates = new HashSet<>(); + Set usedInheritedArtifactsCoordinates = new HashSet<>(); + Set usedTransitiveArtifactsCoordinates = new HashSet<>(); + + for (Artifact artifact : usedDirectArtifacts) { + String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); + String artifactGAVS = + artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString() + .split(":")[4]; + if (declaredArtifactsGAs.contains(artifactGA)) { + // the artifact is declared in the pom + usedDirectArtifactsCoordinates.add(artifactGAVS); + } else { + // the artifact is inherited + usedInheritedArtifactsCoordinates.add(artifactGAVS); } - - // --- used dependencies - Set usedDirectArtifactsCoordinates = new HashSet<>(); - Set usedInheritedArtifactsCoordinates = new HashSet<>(); - Set usedTransitiveArtifactsCoordinates = new HashSet<>(); - - for (Artifact artifact : usedDirectArtifacts) { - String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); - String artifactGAVS = - artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString().split(":")[4]; - if (declaredArtifactsGAs.contains(artifactGA)) { - // the artifact is declared in the pom - usedDirectArtifactsCoordinates.add(artifactGAVS); - } else { - // the artifact is inherited - usedInheritedArtifactsCoordinates.add(artifactGAVS); - } + } + + // TODO Fix: The used transitive dependencies induced by inherited dependencies should be considered as used + // inherited + for (Artifact artifact : usedTransitiveArtifacts) { + String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); + String artifactGAVS = + artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString() + .split(":")[4]; + usedTransitiveArtifactsCoordinates.add(artifactGAVS); + } + + // --- unused dependencies + Set unusedDirectArtifactsCoordinates = new HashSet<>(); + Set unusedInheritedArtifactsCoordinates = new HashSet<>(); + Set unusedTransitiveArtifactsCoordinates = new HashSet<>(); + + for (Artifact artifact : unusedDirectArtifacts) { + String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); + String artifactGAVS = + artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString() + .split(":")[4]; + if (declaredArtifactsGAs.contains(artifactGA)) { + // the artifact is declared in the pom + unusedDirectArtifactsCoordinates.add(artifactGAVS); + } else { + // the artifact is inherited + unusedInheritedArtifactsCoordinates.add(artifactGAVS); } - - // TODO Fix: The used transitive dependencies induced by inherited dependencies should be considered as used - // inherited - for (Artifact artifact : usedTransitiveArtifacts) { - String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); - String artifactGAVS = - artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString().split(":")[4]; - usedTransitiveArtifactsCoordinates.add(artifactGAVS); + } + + // TODO Fix: The unused transitive dependencies induced by inherited dependencies should be considered as + // unused inherited + for (Artifact artifact : unusedTransitiveArtifacts) { + String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); + String artifactGAVS = + artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString() + .split(":")[4]; + unusedTransitiveArtifactsCoordinates.add(artifactGAVS); + } + + /* Ignoring dependencies from the analysis */ + if (ignoreDependencies != null) { + for (String ignoredDependency : ignoreDependencies) { + // if the ignored dependency is an unused direct dependency then add it to the set of used direct + // and remove it from the set of unused direct + for (Iterator i = unusedDirectArtifactsCoordinates.iterator(); i.hasNext(); ) { + String unusedDirectArtifact = i.next(); + if (ignoredDependency.equals(unusedDirectArtifact)) { + usedDirectArtifactsCoordinates.add(unusedDirectArtifact); + i.remove(); + break; + } + } + // if the ignored dependency is an unused inherited dependency then add it to the set of used inherited + // and remove it from the set of unused inherited + for (Iterator j = unusedInheritedArtifactsCoordinates.iterator(); j.hasNext(); ) { + String unusedInheritedArtifact = j.next(); + if (ignoredDependency.equals(unusedInheritedArtifact)) { + usedInheritedArtifactsCoordinates.add(unusedInheritedArtifact); + j.remove(); + break; + } + } + // if the ignored dependency is an unused transitive dependency then add it to the set of used transitive + // and remove it from the set of unused transitive + for (Iterator j = unusedTransitiveArtifactsCoordinates.iterator(); j.hasNext(); ) { + String unusedTransitiveArtifact = j.next(); + if (ignoredDependency.equals(unusedTransitiveArtifact)) { + usedTransitiveArtifactsCoordinates.add(unusedTransitiveArtifact); + j.remove(); + break; + } + } } - - // --- unused dependencies - Set unusedDirectArtifactsCoordinates = new HashSet<>(); - Set unusedInheritedArtifactsCoordinates = new HashSet<>(); - Set unusedTransitiveArtifactsCoordinates = new HashSet<>(); - - for (Artifact artifact : unusedDirectArtifacts) { - String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); - String artifactGAVS = - artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString().split(":")[4]; - if (declaredArtifactsGAs.contains(artifactGA)) { - // the artifact is declared in the pom - unusedDirectArtifactsCoordinates.add(artifactGAVS); - } else { - // the artifact is inherited - unusedInheritedArtifactsCoordinates.add(artifactGAVS); - } + } + + /* Printing the results to the console */ + printString(SEPARATOR); + printString(" D E P C L E A N A N A L Y S I S R E S U L T S"); + printString(SEPARATOR); + + printString( + "Used direct dependencies".toUpperCase() + " [" + usedDirectArtifactsCoordinates.size() + + "]" + ": "); + printDependencies(sizeOfDependencies, usedDirectArtifactsCoordinates); + + printString( + "Used inherited dependencies".toUpperCase() + " [" + usedInheritedArtifactsCoordinates + .size() + "]" + ": "); + printDependencies(sizeOfDependencies, usedInheritedArtifactsCoordinates); + + printString( + "Used transitive dependencies".toUpperCase() + " [" + usedTransitiveArtifactsCoordinates + .size() + + "]" + ": "); + printDependencies(sizeOfDependencies, usedTransitiveArtifactsCoordinates); + + printString("Potentially unused direct dependencies".toUpperCase() + " [" + + unusedDirectArtifactsCoordinates.size() + "]" + ": "); + printDependencies(sizeOfDependencies, unusedDirectArtifactsCoordinates); + + printString("Potentially unused inherited dependencies".toUpperCase() + " [" + + unusedInheritedArtifactsCoordinates.size() + "]" + ": "); + printDependencies(sizeOfDependencies, unusedInheritedArtifactsCoordinates); + + printString("Potentially unused transitive dependencies".toUpperCase() + " [" + + unusedTransitiveArtifactsCoordinates.size() + "]" + ": "); + printDependencies(sizeOfDependencies, unusedTransitiveArtifactsCoordinates); + + if (!ignoreDependencies.isEmpty()) { + printString(SEPARATOR); + printString( + "Dependencies ignored in the analysis by the user" + " [" + ignoreDependencies.size() + + "]" + ":" + + " "); + ignoreDependencies.stream().forEach(s -> printString("\t" + s)); + } + + /* Fail the build if there are unused direct dependencies */ + if (failIfUnusedDirect && !unusedDirectArtifactsCoordinates.isEmpty()) { + throw new MojoExecutionException( + "Build failed due to unused direct dependencies in the dependency tree " + + "of the project."); + } + + /* Fail the build if there are unused direct dependencies */ + if (failIfUnusedTransitive && !unusedTransitiveArtifactsCoordinates.isEmpty()) { + throw new MojoExecutionException( + "Build failed due to unused transitive dependencies in the dependency " + + "tree of the project."); + } + + /* Fail the build if there are unused direct dependencies */ + if (failIfUnusedInherited && !unusedInheritedArtifactsCoordinates.isEmpty()) { + throw new MojoExecutionException( + "Build failed due to unused inherited dependencies in the dependency " + + "tree of the project."); + } + + /* Writing the debloated version of the pom */ + if (createPomDebloated) { + getLog().info("Starting debloating POM"); + + /* Add used transitive as direct dependencies */ + try { + if (!usedTransitiveArtifacts.isEmpty()) { + getLog() + .info("Adding " + unusedTransitiveArtifactsCoordinates.size() + " used transitive " + + "dependencies as direct dependencies."); + for (Artifact usedUndeclaredArtifact : usedTransitiveArtifacts) { + model.addDependency(createDependency(usedUndeclaredArtifact)); + } + } + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); } - // TODO Fix: The unused transitive dependencies induced by inherited dependencies should be considered as - // unused inherited - for (Artifact artifact : unusedTransitiveArtifacts) { - String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); - String artifactGAVS = - artifactGA + ":" + artifact.toString().split(":")[3] + ":" + artifact.toString().split(":")[4]; - unusedTransitiveArtifactsCoordinates.add(artifactGAVS); + /* Remove unused direct dependencies */ + try { + if (!unusedDirectArtifacts.isEmpty()) { + getLog().info("Removing " + unusedDirectArtifactsCoordinates.size() + + " unused direct dependencies."); + for (Artifact unusedDeclaredArtifact : unusedDirectArtifacts) { + for (Dependency dependency : model.getDependencies()) { + if (dependency.getGroupId().equals(unusedDeclaredArtifact.getGroupId()) && + dependency.getArtifactId().equals(unusedDeclaredArtifact.getArtifactId())) { + model.removeDependency(dependency); + break; + } + } + } + } + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); } - /* Ignoring dependencies from the analysis */ - if (ignoreDependencies != null) { - for (String ignoredDependency : ignoreDependencies) { - // if the ignored dependency is an unused direct dependency then add it to the set of used direct - // and remove it from the set of unused direct - for (Iterator i = unusedDirectArtifactsCoordinates.iterator(); i.hasNext(); ) { - String unusedDirectArtifact = i.next(); - if (ignoredDependency.equals(unusedDirectArtifact)) { - usedDirectArtifactsCoordinates.add(unusedDirectArtifact); - i.remove(); - break; - } - } - // if the ignored dependency is an unused inherited dependency then add it to the set of used inherited - // and remove it from the set of unused inherited - for (Iterator j = unusedInheritedArtifactsCoordinates.iterator(); j.hasNext(); ) { - String unusedInheritedArtifact = j.next(); - if (ignoredDependency.equals(unusedInheritedArtifact)) { - usedInheritedArtifactsCoordinates.add(unusedInheritedArtifact); - j.remove(); - break; - } - } - // if the ignored dependency is an unused transitive dependency then add it to the set of used transitive - // and remove it from the set of unused transitive - for (Iterator j = unusedTransitiveArtifactsCoordinates.iterator(); j.hasNext(); ) { - String unusedTransitiveArtifact = j.next(); - if (ignoredDependency.equals(unusedTransitiveArtifact)) { - usedTransitiveArtifactsCoordinates.add(unusedTransitiveArtifact); - j.remove(); - break; - } + /* Exclude unused transitive dependencies */ + try { + if (!unusedTransitiveArtifacts.isEmpty()) { + getLog().info( + "Excluding " + unusedTransitiveArtifacts.size() + " unused transitive dependencies " + + "one-by-one."); + for (Dependency dependency : model.getDependencies()) { + for (Artifact artifact : unusedTransitiveArtifacts) { + if (isChildren(artifact, dependency)) { + getLog().info("Excluding " + artifact.toString() + " from dependency " + dependency + .toString()); + Exclusion exclusion = new Exclusion(); + exclusion.setGroupId(artifact.getGroupId()); + exclusion.setArtifactId(artifact.getArtifactId()); + dependency.addExclusion(exclusion); + } } - } + } + } + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage(), e); } - /* Printing the results to the console */ - printString(SEPARATOR); - printString(" D E P C L E A N A N A L Y S I S R E S U L T S"); - printString(SEPARATOR); - - printString("Used direct dependencies".toUpperCase() + " [" + usedDirectArtifactsCoordinates.size() + "]" + ": "); - printDependencies(sizeOfDependencies, usedDirectArtifactsCoordinates); - - printString("Used inherited dependencies".toUpperCase() + " [" + usedInheritedArtifactsCoordinates.size() + "]" + ": "); - printDependencies(sizeOfDependencies, usedInheritedArtifactsCoordinates); - - printString("Used transitive dependencies".toUpperCase() + " [" + usedTransitiveArtifactsCoordinates.size() + - "]" + ": "); - printDependencies(sizeOfDependencies, usedTransitiveArtifactsCoordinates); - - printString("Potentially unused direct dependencies".toUpperCase() + " [" + unusedDirectArtifactsCoordinates.size() + "]" + ": "); - printDependencies(sizeOfDependencies, unusedDirectArtifactsCoordinates); - - printString("Potentially unused inherited dependencies".toUpperCase() + " [" + unusedInheritedArtifactsCoordinates.size() + "]" + ": "); - printDependencies(sizeOfDependencies, unusedInheritedArtifactsCoordinates); - - printString("Potentially unused transitive dependencies".toUpperCase() + " [" + unusedTransitiveArtifactsCoordinates.size() + "]" + ": "); - printDependencies(sizeOfDependencies, unusedTransitiveArtifactsCoordinates); - - if (!ignoreDependencies.isEmpty()) { - printString(SEPARATOR); - printString("Dependencies ignored in the analysis by the user" + " [" + ignoreDependencies.size() + "]" + ":" + - " "); - ignoreDependencies.stream().forEach(s -> printString("\t" + s)); + /* Write the debloated pom file */ + String pathToDebloatedPom = + project.getBasedir().getAbsolutePath() + File.separator + "pom-debloated.xml"; + try { + Path path = Paths.get(pathToDebloatedPom); + writePom(path, model); + } catch (IOException e) { + throw new MojoExecutionException(e.getMessage(), e); } - /* Fail the build if there are unused direct dependencies */ - if (failIfUnusedDirect && !unusedDirectArtifactsCoordinates.isEmpty()) { - throw new MojoExecutionException("Build failed due to unused direct dependencies in the dependency tree " + - "of the project."); - } + getLog().info("POM debloated successfully"); + getLog().info("pom-debloated.xml file created in: " + pathToDebloatedPom); + } - /* Fail the build if there are unused direct dependencies */ - if (failIfUnusedTransitive && !unusedTransitiveArtifactsCoordinates.isEmpty()) { - throw new MojoExecutionException("Build failed due to unused transitive dependencies in the dependency " + - "tree of the project."); - } - /* Fail the build if there are unused direct dependencies */ - if (failIfUnusedInherited && !unusedInheritedArtifactsCoordinates.isEmpty()) { - throw new MojoExecutionException("Build failed due to unused inherited dependencies in the dependency " + - "tree of the project."); + /* Writing the JSON file with the debloat results */ + if (createResultJson) { + printString("Creating depclean-results.json, please wait..."); + String pathToJsonFile = + project.getBasedir().getAbsolutePath() + File.separator + "depclean-results.json"; + String treeFile = project.getBuild().getDirectory() + File.separator + "tree.txt"; + /* Copy direct dependencies locally */ + try { + MavenInvoker.runCommand("mvn dependency:tree -DoutputFile=" + treeFile + " -Dverbose=true"); + } catch (IOException | InterruptedException e) { + getLog().error("Unable to generate dependency tree."); + // Restore interrupted state... + Thread.currentThread().interrupt(); + return; } - - /* Writing the debloated version of the pom */ - if (createPomDebloated) { - getLog().info("Starting debloating POM"); - - /* Add used transitive as direct dependencies */ - try { - if (!usedTransitiveArtifacts.isEmpty()) { - getLog().info("Adding " + unusedTransitiveArtifactsCoordinates.size() + " used transitive " + - "dependencies as direct dependencies."); - for (Artifact usedUndeclaredArtifact : usedTransitiveArtifacts) { - model.addDependency(createDependency(usedUndeclaredArtifact)); - } - } - } catch (Exception e) { - throw new MojoExecutionException(e.getMessage(), e); - } - - /* Remove unused direct dependencies */ - try { - if (!unusedDirectArtifacts.isEmpty()) { - getLog().info("Removing " + unusedDirectArtifactsCoordinates.size() + " unused direct dependencies."); - for (Artifact unusedDeclaredArtifact : unusedDirectArtifacts) { - for (Dependency dependency : model.getDependencies()) { - if (dependency.getGroupId().equals(unusedDeclaredArtifact.getGroupId()) && - dependency.getArtifactId().equals(unusedDeclaredArtifact.getArtifactId())) { - model.removeDependency(dependency); - break; - } - } - } - } - } catch (Exception e) { - throw new MojoExecutionException(e.getMessage(), e); - } - - /* Exclude unused transitive dependencies */ - try { - if (!unusedTransitiveArtifacts.isEmpty()) { - getLog().info("Excluding " + unusedTransitiveArtifacts.size() + " unused transitive dependencies " + - "one-by-one."); - for (Dependency dependency : model.getDependencies()) { - for (Artifact artifact : unusedTransitiveArtifacts) { - if (isChildren(artifact, dependency)) { - getLog().info("Excluding " + artifact.toString() + " from dependency " + dependency.toString()); - Exclusion exclusion = new Exclusion(); - exclusion.setGroupId(artifact.getGroupId()); - exclusion.setArtifactId(artifact.getArtifactId()); - dependency.addExclusion(exclusion); - } - } - } - } - } catch (Exception e) { - throw new MojoExecutionException(e.getMessage(), e); - } - - /* Write the debloated pom file */ - String pathToDebloatedPom = project.getBasedir().getAbsolutePath() + File.separator + "pom-debloated.xml"; - try { - Path path = Paths.get(pathToDebloatedPom); - writePom(path, model); - } catch (IOException e) { - throw new MojoExecutionException(e.getMessage(), e); - } - - getLog().info("POM debloated successfully"); - getLog().info("pom-debloated.xml file created in: " + pathToDebloatedPom); + File classUsageFile = new File( + project.getBasedir().getAbsolutePath() + File.separator + "class-usage.csv"); + try { + FileUtils.write(classUsageFile, "OriginClass,TargetClass,Dependency\n", + Charset.defaultCharset()); + } catch (IOException e) { + getLog().error("Error writing the CSV header."); } - - - /* Writing the JSON file with the debloat results */ - if (createResultJson) { - printString("Creating depclean-results.json, please wait..."); - String pathToJsonFile = project.getBasedir().getAbsolutePath() + File.separator + "depclean-results.json"; - String treeFile = project.getBuild().getDirectory() + File.separator + "tree.txt"; - /* Copy direct dependencies locally */ - try { - MavenInvoker.runCommand("mvn dependency:tree -DoutputFile=" + treeFile + " -Dverbose=true"); - } catch (IOException | InterruptedException e) { - getLog().error("Unable to generate dependency tree."); - // Restore interrupted state... - Thread.currentThread().interrupt(); - return; - } - File classUsageFile = new File(project.getBasedir().getAbsolutePath() + File.separator + "class-usage.csv"); - try { - FileUtils.write(classUsageFile, "OriginClass,TargetClass,Dependency\n", Charset.defaultCharset()); - } catch (IOException e) { - getLog().error("Error writing the CSV header."); - } - ParsedDependencies parsedDependencies = new ParsedDependencies( - treeFile, - sizeOfDependencies, - dependencyAnalyzer, - usedDirectArtifactsCoordinates, - usedInheritedArtifactsCoordinates, - usedTransitiveArtifactsCoordinates, - unusedDirectArtifactsCoordinates, - unusedInheritedArtifactsCoordinates, - unusedTransitiveArtifactsCoordinates, - classUsageFile - ); - try { - FileUtils.write(new File(pathToJsonFile), parsedDependencies.parseTreeToJSON(), Charset.defaultCharset()); - getLog().info("depclean-results.json file created in: " + pathToJsonFile); - } catch (ParseException | IOException e) { - getLog().error("Unable to generate JSON file."); - } + ParsedDependencies parsedDependencies = new ParsedDependencies( + treeFile, + sizeOfDependencies, + dependencyAnalyzer, + usedDirectArtifactsCoordinates, + usedInheritedArtifactsCoordinates, + usedTransitiveArtifactsCoordinates, + unusedDirectArtifactsCoordinates, + unusedInheritedArtifactsCoordinates, + unusedTransitiveArtifactsCoordinates, + classUsageFile + ); + try { + FileUtils.write(new File(pathToJsonFile), parsedDependencies.parseTreeToJSON(), + Charset.defaultCharset()); + getLog().info("depclean-results.json file created in: " + pathToJsonFile); + } catch (ParseException | IOException e) { + getLog().error("Unable to generate JSON file."); } - } + } + } } diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/FileUtils.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/FileUtils.java index b0d2dd56..d43e4a91 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/FileUtils.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/FileUtils.java @@ -17,77 +17,76 @@ package se.kth.depclean.util; -import lombok.extern.slf4j.Slf4j; - import java.io.File; import java.io.IOException; import java.nio.file.Files; +import lombok.extern.slf4j.Slf4j; @Slf4j public final class FileUtils { - private FileUtils() { - } + private FileUtils() { + } - /** - * Deletes a directory recursively. - * - * @param directory The directory to be deleted. - * @throws IOException In case of IO issues. - */ - public static void deleteDirectory(final File directory) throws IOException { - if (!directory.exists()) { - return; - } - if (!isSymlink(directory)) { - cleanDirectory(directory); - } - Files.delete(directory.toPath()); + /** + * Deletes a directory recursively. + * + * @param directory The directory to be deleted. + * @throws IOException In case of IO issues. + */ + public static void deleteDirectory(final File directory) throws IOException { + if (!directory.exists()) { + return; } + if (!isSymlink(directory)) { + cleanDirectory(directory); + } + Files.delete(directory.toPath()); + } - private static boolean isSymlink(final File file) { - if (file == null) { - throw new NullPointerException("File must be not null"); - } - return Files.isSymbolicLink(file.toPath()); + private static boolean isSymlink(final File file) { + if (file == null) { + throw new NullPointerException("File must be not null"); } + return Files.isSymbolicLink(file.toPath()); + } - private static void cleanDirectory(final File directory) throws IOException { - final File[] files = verifiedListFiles(directory); - IOException exception = null; - for (final File file : files) { - try { - forceDelete(file); - } catch (final IOException ioe) { - exception = ioe; - } - } - if (null != exception) { - throw exception; - } + private static void cleanDirectory(final File directory) throws IOException { + final File[] files = verifiedListFiles(directory); + IOException exception = null; + for (final File file : files) { + try { + forceDelete(file); + } catch (final IOException ioe) { + exception = ioe; + } + } + if (null != exception) { + throw exception; } + } - private static File[] verifiedListFiles(final File directory) throws IOException { - if (!directory.exists()) { - final String message = directory + " does not exist"; - throw new IllegalArgumentException(message); - } - if (!directory.isDirectory()) { - final String message = directory + " is not a directory"; - throw new IllegalArgumentException(message); - } - final File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - throw new IOException("Failed to list contents of " + directory); - } - return files; + private static File[] verifiedListFiles(final File directory) throws IOException { + if (!directory.exists()) { + final String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + if (!directory.isDirectory()) { + final String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + final File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + throw new IOException("Failed to list contents of " + directory); } + return files; + } - private static void forceDelete(final File file) throws IOException { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - Files.delete(file.toPath()); - } + private static void forceDelete(final File file) throws IOException { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + Files.delete(file.toPath()); } + } } diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/JarUtils.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/JarUtils.java index a61f56d8..8e36a149 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/JarUtils.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/JarUtils.java @@ -17,8 +17,6 @@ package se.kth.depclean.util; -import lombok.extern.slf4j.Slf4j; - import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; @@ -27,80 +25,82 @@ import java.util.Objects; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; +import lombok.extern.slf4j.Slf4j; @Slf4j public final class JarUtils { - /** - * Size of the buffer to read/write data. - */ - private static final int BUFFER_SIZE = 16384; + /** + * Size of the buffer to read/write data. + */ + private static final int BUFFER_SIZE = 16384; - private JarUtils() { - } + private JarUtils() { + } - /** - * Decompress all jar files located in a given directory. - * - * @param outputDirectory The directory path to put the decompressed files. - */ - public static void decompressJars(final String outputDirectory) { - File files = new File(outputDirectory); - for (File f : Objects.requireNonNull(files.listFiles())) { - if (f.getName().endsWith(".jar")) { - try { - JarUtils.decompressJarFile(outputDirectory, f.getAbsolutePath()); - // delete the original dependency jar file - org.apache.commons.io.FileUtils.forceDelete(f); - } catch (IOException e) { - log.warn("Problem decompressing jar file: " + f.getAbsolutePath()); - } - } + /** + * Decompress all jar files located in a given directory. + * + * @param outputDirectory The directory path to put the decompressed files. + */ + public static void decompressJars(final String outputDirectory) { + File files = new File(outputDirectory); + for (File f : Objects.requireNonNull(files.listFiles())) { + if (f.getName().endsWith(".jar")) { + try { + JarUtils.decompressJarFile(outputDirectory, f.getAbsolutePath()); + // delete the original dependency jar file + org.apache.commons.io.FileUtils.forceDelete(f); + } catch (IOException e) { + log.warn("Problem decompressing jar file: " + f.getAbsolutePath()); } + } } + } - /** - * Decompress a jar file in a path to a directory (will be created if it doesn't exists). - * - * @param destDirectory The destine directory. - * @param jarFilePath The path to the Jar file to be decompressed. - * @throws IOException In case of IO issues. - */ - private static void decompressJarFile(String destDirectory, String jarFilePath) throws IOException { - File destDir = new File(destDirectory); - if (!destDir.exists()) { - destDir.mkdir(); - } - try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath))) { - JarEntry entry = jarIn.getNextJarEntry(); - // iterates over all the entries in the jar file - while (entry != null) { - String filePath = destDirectory + File.separator + entry.getName(); - if (!entry.isDirectory()) { - new File(filePath).getParentFile().mkdirs(); //NOSONAR Triggers a false warning of path traversal attack - // if the entry is a file, extracts it - extractFile(jarIn, filePath); - } - jarIn.closeEntry(); - entry = jarIn.getNextJarEntry(); - } + /** + * Decompress a jar file in a path to a directory (will be created if it doesn't exists). + * + * @param destDirectory The destine directory. + * @param jarFilePath The path to the Jar file to be decompressed. + * @throws IOException In case of IO issues. + */ + private static void decompressJarFile(String destDirectory, String jarFilePath) throws IOException { + File destDir = new File(destDirectory); + if (!destDir.exists()) { + destDir.mkdir(); + } + try (JarInputStream jarIn = new JarInputStream(new FileInputStream(jarFilePath))) { + JarEntry entry = jarIn.getNextJarEntry(); + // iterates over all the entries in the jar file + while (entry != null) { + String filePath = destDirectory + File.separator + entry.getName(); + if (!entry.isDirectory()) { + new File(filePath).getParentFile().mkdirs(); //NOSONAR Triggers a false warning of path traversal attack + // if the entry is a file, extracts it + extractFile(jarIn, filePath); } + jarIn.closeEntry(); + entry = jarIn.getNextJarEntry(); + } } + } - /** - * Extracts an entry file. - * - * @param jarIn The jar file to be extracted. - * @param filePath Path to the file. - * @throws IOException In case of IO issues. - */ - private static void extractFile(final JarInputStream jarIn, final String filePath) throws IOException { - try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) { //NOSONAR Triggers a false warning of path traversal attack - byte[] bytesIn = new byte[BUFFER_SIZE]; - int read; - while ((read = jarIn.read(bytesIn)) != -1) { - bos.write(bytesIn, 0, read); - } - } + /** + * Extracts an entry file. + * + * @param jarIn The jar file to be extracted. + * @param filePath Path to the file. + * @throws IOException In case of IO issues. + */ + private static void extractFile(final JarInputStream jarIn, final String filePath) throws IOException { + try (BufferedOutputStream bos = new BufferedOutputStream( + new FileOutputStream(filePath))) { //NOSONAR Triggers a false warning of path traversal attack + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read; + while ((read = jarIn.read(bytesIn)) != -1) { + bos.write(bytesIn, 0, read); + } } + } } diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/MavenInvoker.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/MavenInvoker.java index 22508bf8..81536635 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/MavenInvoker.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/MavenInvoker.java @@ -17,14 +17,6 @@ package se.kth.depclean.util; -import lombok.extern.slf4j.Slf4j; -import org.apache.maven.shared.invoker.DefaultInvocationRequest; -import org.apache.maven.shared.invoker.DefaultInvoker; -import org.apache.maven.shared.invoker.InvocationRequest; -import org.apache.maven.shared.invoker.InvocationResult; -import org.apache.maven.shared.invoker.Invoker; -import org.apache.maven.shared.invoker.MavenInvocationException; - import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -32,78 +24,86 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; +import lombok.extern.slf4j.Slf4j; +import org.apache.maven.shared.invoker.DefaultInvocationRequest; +import org.apache.maven.shared.invoker.DefaultInvoker; +import org.apache.maven.shared.invoker.InvocationRequest; +import org.apache.maven.shared.invoker.InvocationResult; +import org.apache.maven.shared.invoker.Invoker; +import org.apache.maven.shared.invoker.MavenInvocationException; @Slf4j public final class MavenInvoker { - private MavenInvoker() { - } + private MavenInvoker() { + } - /** - * Creates a native process to execute a custom command. This method is used to invoke maven plugins directly. - * - * @param cmd The command to be executed. - * @return The console output. - * @throws IOException In case of IO issues. - * @throws InterruptedException In case of IO issues. - */ - public static String[] runCommand(String cmd) throws IOException, InterruptedException { - String os = System.getProperty("os.name").toLowerCase(); - Process process; - ArrayList list; - if (isUnix(os)) { - list = new ArrayList<>(); - process = Runtime.getRuntime().exec(cmd); - InputStream inputStream = process.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); - return outputToConsole(process, list, br); - } else if (isWindows(os)) { - list = new ArrayList<>(); - process = Runtime.getRuntime().exec("cmd /C " + cmd); - BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); - return outputToConsole(process, list, br); - } - return new String[0]; // return an empty array otherwise - } + /** + * Creates a native process to execute a custom command. This method is used to invoke maven plugins directly. + * + * @param cmd The command to be executed. + * @return The console output. + * @throws IOException In case of IO issues. + * @throws InterruptedException In case of IO issues. + */ + public static String[] runCommand(String cmd) throws IOException, InterruptedException { + String os = System.getProperty("os.name").toLowerCase(); + Process process; + ArrayList list; + if (isUnix(os)) { + list = new ArrayList<>(); + process = Runtime.getRuntime().exec(cmd); + InputStream inputStream = process.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); + return outputToConsole(process, list, br); + } else if (isWindows(os)) { + list = new ArrayList<>(); + process = Runtime.getRuntime().exec("cmd /C " + cmd); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + return outputToConsole(process, list, br); + } + return new String[0]; // return an empty array otherwise + } - /** - * Print the output of the command to the standard output. - */ - private static String[] outputToConsole(Process process, ArrayList list, BufferedReader br) throws IOException, InterruptedException { - String s; - while ((s = br.readLine()) != null) { - list.add(s); - } - process.waitFor(); - br.close(); - return list.toArray(new String[0]); - } + /** + * Print the output of the command to the standard output. + */ + private static String[] outputToConsole(Process process, ArrayList list, BufferedReader br) + throws IOException, InterruptedException { + String s; + while ((s = br.readLine()) != null) { + list.add(s); + } + process.waitFor(); + br.close(); + return list.toArray(new String[0]); + } - /** - * This method invokes Maven to execute a given goal programmatically instead of running a command directly as - * in {@link #runCommand(String)}. - * - * @param mvnHome Location of maven installation. - * @param pomPath Path to the pom of the project. - * @param mvnGoal The maven goal to execute. - * @return The exit code from the Maven invocation. - * @throws MavenInvocationException In case of any issue invoking maven. - */ - public static int invokeMaven(String mvnHome, String pomPath, String mvnGoal) throws MavenInvocationException { - InvocationRequest request = new DefaultInvocationRequest(); - request.setPomFile(new File(pomPath)); - request.setGoals(Collections.singletonList(mvnGoal)); - Invoker invoker = new DefaultInvoker(); - invoker.setMavenHome(new File(mvnHome)); - InvocationResult result = invoker.execute(request); - return result.getExitCode(); - } + /** + * This method invokes Maven to execute a given goal programmatically instead of running a command directly as in + * {@link #runCommand(String)}. + * + * @param mvnHome Location of maven installation. + * @param pomPath Path to the pom of the project. + * @param mvnGoal The maven goal to execute. + * @return The exit code from the Maven invocation. + * @throws MavenInvocationException In case of any issue invoking maven. + */ + public static int invokeMaven(String mvnHome, String pomPath, String mvnGoal) throws MavenInvocationException { + InvocationRequest request = new DefaultInvocationRequest(); + request.setPomFile(new File(pomPath)); + request.setGoals(Collections.singletonList(mvnGoal)); + Invoker invoker = new DefaultInvoker(); + invoker.setMavenHome(new File(mvnHome)); + InvocationResult result = invoker.execute(request); + return result.getExitCode(); + } - private static boolean isUnix(String os) { - return (os.contains("nix") || os.contains("nux") || os.contains("mac os")); - } + private static boolean isUnix(String os) { + return (os.contains("nix") || os.contains("nux") || os.contains("mac os")); + } - private static boolean isWindows(String os) { - return (os.contains("win")); - } + private static boolean isWindows(String os) { + return (os.contains("win")); + } } diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java index 0065a4b8..95d33803 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java @@ -4,155 +4,160 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import fr.dutra.tools.maven.deptree.core.Node; -import org.apache.commons.io.FileUtils; -import se.kth.depclean.core.analysis.DefaultProjectDependencyAnalyzer; -import se.kth.depclean.core.analysis.graph.DefaultCallGraph; - import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Map; import java.util.Set; +import org.apache.commons.io.FileUtils; +import se.kth.depclean.core.analysis.DefaultProjectDependencyAnalyzer; +import se.kth.depclean.core.analysis.graph.DefaultCallGraph; public class NodeAdapter extends TypeAdapter { - private final Set usedDirectArtifactsCoordinates; - private final Set usedInheritedArtifactsCoordinates; - private final Set usedTransitiveArtifactsCoordinates; - private final Set unusedDirectArtifactsCoordinates; - private final Set unusedInheritedArtifactsCoordinates; - private final Set unusedTransitiveArtifactsCoordinates; - private final DefaultProjectDependencyAnalyzer dependencyAnalyzer; - private final File classUsageFile; - - private final Map sizeOfDependencies; - - public NodeAdapter(Set usedDirectArtifactsCoordinates, - Set usedInheritedArtifactsCoordinates, - Set usedTransitiveArtifactsCoordinates, - Set unusedDirectArtifactsCoordinates, - Set unusedInheritedArtifactsCoordinates, - Set unusedTransitiveArtifactsCoordinates, - Map sizeOfDependencies, - DefaultProjectDependencyAnalyzer dependencyAnalyzer, - File classUsageFile) { - this.usedDirectArtifactsCoordinates = usedDirectArtifactsCoordinates; - this.usedInheritedArtifactsCoordinates = usedInheritedArtifactsCoordinates; - this.usedTransitiveArtifactsCoordinates = usedTransitiveArtifactsCoordinates; - this.unusedDirectArtifactsCoordinates = unusedDirectArtifactsCoordinates; - this.unusedInheritedArtifactsCoordinates = unusedInheritedArtifactsCoordinates; - this.unusedTransitiveArtifactsCoordinates = unusedTransitiveArtifactsCoordinates; - this.sizeOfDependencies = sizeOfDependencies; - this.dependencyAnalyzer = dependencyAnalyzer; - this.classUsageFile = classUsageFile; - } - - @Override - public void write(JsonWriter jsonWriter, Node node) throws IOException { - String coordinates = - node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getVersion() + ":" + node.getScope(); - String canonical = - node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getPackaging() + ":" + node.getVersion() + - ":" + node.getScope(); - String dependencyJar = node.getArtifactId() + "-" + node.getVersion() + ".jar"; - - // Write to the class-usage.csv file - DefaultCallGraph defaultCallGraph = new DefaultCallGraph(); - for (Map.Entry> usagePerClassMap : defaultCallGraph.getUsagesPerClass().entrySet()) { - String key = usagePerClassMap.getKey(); - Set value = usagePerClassMap.getValue(); - for (String s : value) { - if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) && dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().contains(s)) { - // System.out.println(key + " uses " + s + " from " + canonical); - String triplet = key + "," + s + "," + canonical + "\n"; - FileUtils.write(classUsageFile, triplet, Charset.defaultCharset(), true); - } - } + private final Set usedDirectArtifactsCoordinates; + private final Set usedInheritedArtifactsCoordinates; + private final Set usedTransitiveArtifactsCoordinates; + private final Set unusedDirectArtifactsCoordinates; + private final Set unusedInheritedArtifactsCoordinates; + private final Set unusedTransitiveArtifactsCoordinates; + private final DefaultProjectDependencyAnalyzer dependencyAnalyzer; + private final File classUsageFile; + + private final Map sizeOfDependencies; + + public NodeAdapter(Set usedDirectArtifactsCoordinates, + Set usedInheritedArtifactsCoordinates, + Set usedTransitiveArtifactsCoordinates, + Set unusedDirectArtifactsCoordinates, + Set unusedInheritedArtifactsCoordinates, + Set unusedTransitiveArtifactsCoordinates, + Map sizeOfDependencies, + DefaultProjectDependencyAnalyzer dependencyAnalyzer, + File classUsageFile) { + this.usedDirectArtifactsCoordinates = usedDirectArtifactsCoordinates; + this.usedInheritedArtifactsCoordinates = usedInheritedArtifactsCoordinates; + this.usedTransitiveArtifactsCoordinates = usedTransitiveArtifactsCoordinates; + this.unusedDirectArtifactsCoordinates = unusedDirectArtifactsCoordinates; + this.unusedInheritedArtifactsCoordinates = unusedInheritedArtifactsCoordinates; + this.unusedTransitiveArtifactsCoordinates = unusedTransitiveArtifactsCoordinates; + this.sizeOfDependencies = sizeOfDependencies; + this.dependencyAnalyzer = dependencyAnalyzer; + this.classUsageFile = classUsageFile; + } + + @Override + public void write(JsonWriter jsonWriter, Node node) throws IOException { + String coordinates = + node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getVersion() + ":" + node.getScope(); + String canonical = + node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getPackaging() + ":" + node.getVersion() + + ":" + node.getScope(); + String dependencyJar = node.getArtifactId() + "-" + node.getVersion() + ".jar"; + + // Write to the class-usage.csv file + DefaultCallGraph defaultCallGraph = new DefaultCallGraph(); + for (Map.Entry> usagePerClassMap : defaultCallGraph.getUsagesPerClass().entrySet()) { + String key = usagePerClassMap.getKey(); + Set value = usagePerClassMap.getValue(); + for (String s : value) { + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) && dependencyAnalyzer + .getArtifactClassesMap().get(canonical).getAllTypes().contains(s)) { + // System.out.println(key + " uses " + s + " from " + canonical); + String triplet = key + "," + s + "," + canonical + "\n"; + FileUtils.write(classUsageFile, triplet, Charset.defaultCharset(), true); + } } - - JsonWriter localWriter = jsonWriter.beginObject() - .name("id") - .value(canonical) - - .name("coordinates") - .value(node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getVersion()) - - .name("groupId") - .value(node.getGroupId()) - - .name("artifactId") - .value(node.getArtifactId()) - - .name("version") - .value(node.getVersion()) - - .name("scope") - .value(node.getScope()) - - .name("packaging") - .value(node.getPackaging()) - - .name("omitted") - .value(node.isOmitted()) - - .name("classifier") - .value(node.getClassifier()) - - .name("size") - .value(sizeOfDependencies.get(dependencyJar)) - - .name("type") - .value((usedDirectArtifactsCoordinates.contains(coordinates) || unusedDirectArtifactsCoordinates.contains(coordinates)) ? "direct" : - (usedInheritedArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates.contains(coordinates)) ? "inherited" : - (usedTransitiveArtifactsCoordinates.contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) ? "transitive" : - "unknown") - .name("status") - .value((usedDirectArtifactsCoordinates.contains(coordinates) || usedInheritedArtifactsCoordinates.contains(coordinates) || usedTransitiveArtifactsCoordinates.contains(coordinates)) ? - "used" : - (unusedDirectArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates.contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) ? - "bloated" : - "unknown") - - .name("parent") - .value(node.getParent() != null ? - node.getParent().getArtifactCanonicalForm() : - "unknown"); - - JsonWriter allTypes = localWriter.name("allTypes").beginArray(); - if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical)) { - for (String allType : dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes()) { - allTypes.value(allType); - } + } + + JsonWriter localWriter = jsonWriter.beginObject() + .name("id") + .value(canonical) + + .name("coordinates") + .value(node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getVersion()) + + .name("groupId") + .value(node.getGroupId()) + + .name("artifactId") + .value(node.getArtifactId()) + + .name("version") + .value(node.getVersion()) + + .name("scope") + .value(node.getScope()) + + .name("packaging") + .value(node.getPackaging()) + + .name("omitted") + .value(node.isOmitted()) + + .name("classifier") + .value(node.getClassifier()) + + .name("size") + .value(sizeOfDependencies.get(dependencyJar)) + + .name("type") + .value((usedDirectArtifactsCoordinates.contains(coordinates) || unusedDirectArtifactsCoordinates + .contains(coordinates)) ? "direct" : + (usedInheritedArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates + .contains(coordinates)) ? "inherited" : + (usedTransitiveArtifactsCoordinates.contains(coordinates) || unusedTransitiveArtifactsCoordinates + .contains(coordinates)) ? "transitive" : + "unknown") + .name("status") + .value((usedDirectArtifactsCoordinates.contains(coordinates) || usedInheritedArtifactsCoordinates + .contains(coordinates) || usedTransitiveArtifactsCoordinates.contains(coordinates)) ? + "used" : + (unusedDirectArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates + .contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) ? + "bloated" : + "unknown") + + .name("parent") + .value(node.getParent() != null ? + node.getParent().getArtifactCanonicalForm() : + "unknown"); + + JsonWriter allTypes = localWriter.name("allTypes").beginArray(); + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical)) { + for (String allType : dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes()) { + allTypes.value(allType); } - allTypes.endArray(); + } + allTypes.endArray(); - JsonWriter usedTypes = localWriter.name("usedTypes").beginArray(); - if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical)) { - for (String usedType : dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes()) { - usedTypes.value(usedType); - } - } - usedTypes.endArray(); - - localWriter.name("usageRatio") - .value(dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) ? - dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().isEmpty() ? - 0 : // handle division by zero - ((double) dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes().size() / - dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().size()) : - -1) - .name("children") - .beginArray(); - - for (Node c : node.getChildNodes()) { - this.write(jsonWriter, c); + JsonWriter usedTypes = localWriter.name("usedTypes").beginArray(); + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical)) { + for (String usedType : dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes()) { + usedTypes.value(usedType); } - jsonWriter.endArray() - .endObject(); - } - - @Override - public Node read(JsonReader jsonReader) { - throw new UnsupportedOperationException(); - } + } + usedTypes.endArray(); + + localWriter.name("usageRatio") + .value(dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) ? + dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().isEmpty() ? + 0 : // handle division by zero + ((double) dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes().size() / + dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().size()) : + -1) + .name("children") + .beginArray(); + + for (Node c : node.getChildNodes()) { + this.write(jsonWriter, c); + } + jsonWriter.endArray() + .endObject(); + } + + @Override + public Node read(JsonReader jsonReader) { + throw new UnsupportedOperationException(); + } } diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/ParsedDependencies.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/ParsedDependencies.java index 146b0364..cd88aa68 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/ParsedDependencies.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/ParsedDependencies.java @@ -6,9 +6,6 @@ import fr.dutra.tools.maven.deptree.core.Node; import fr.dutra.tools.maven.deptree.core.ParseException; import fr.dutra.tools.maven.deptree.core.Parser; -import lombok.extern.slf4j.Slf4j; -import se.kth.depclean.core.analysis.DefaultProjectDependencyAnalyzer; - import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -18,67 +15,69 @@ import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Set; +import lombok.extern.slf4j.Slf4j; +import se.kth.depclean.core.analysis.DefaultProjectDependencyAnalyzer; @Slf4j public class ParsedDependencies { - private final String treeTextFilePath; - private final DefaultProjectDependencyAnalyzer dependencyAnalyzer; - private final Set usedDirectArtifactsCoordinates; - private final Set usedInheritedArtifactsCoordinates; - private final Set usedTransitiveArtifactsCoordinates; - private final Set unusedDirectArtifactsCoordinates; - private final Set unusedInheritedArtifactsCoordinates; - private final Set unusedTransitiveArtifactsCoordinates; + private final String treeTextFilePath; + private final DefaultProjectDependencyAnalyzer dependencyAnalyzer; + private final Set usedDirectArtifactsCoordinates; + private final Set usedInheritedArtifactsCoordinates; + private final Set usedTransitiveArtifactsCoordinates; + private final Set unusedDirectArtifactsCoordinates; + private final Set unusedInheritedArtifactsCoordinates; + private final Set unusedTransitiveArtifactsCoordinates; - private final Map sizeOfDependencies; - private final File classUsageFile; + private final Map sizeOfDependencies; + private final File classUsageFile; - public ParsedDependencies(String treeTextFilePath, - Map sizeOfDependencies, - DefaultProjectDependencyAnalyzer dependencyAnalyzer, - Set usedDirectArtifactsCoordinates, - Set usedInheritedArtifactsCoordinates, - Set usedUndeclaredArtifactsCoordinates, - Set unusedDirectArtifactsCoordinates, - Set unusedInheritedArtifactsCoordinates, - Set unusedUndeclaredArtifactsCoordinates, - File classUsageFile) { - this.treeTextFilePath = treeTextFilePath; - this.sizeOfDependencies = sizeOfDependencies; - this.dependencyAnalyzer = dependencyAnalyzer; - this.usedDirectArtifactsCoordinates = usedDirectArtifactsCoordinates; - this.usedInheritedArtifactsCoordinates = usedInheritedArtifactsCoordinates; - this.usedTransitiveArtifactsCoordinates = usedUndeclaredArtifactsCoordinates; - this.unusedDirectArtifactsCoordinates = unusedDirectArtifactsCoordinates; - this.unusedInheritedArtifactsCoordinates = unusedInheritedArtifactsCoordinates; - this.unusedTransitiveArtifactsCoordinates = unusedUndeclaredArtifactsCoordinates; - this.classUsageFile = classUsageFile; - } + public ParsedDependencies(String treeTextFilePath, + Map sizeOfDependencies, + DefaultProjectDependencyAnalyzer dependencyAnalyzer, + Set usedDirectArtifactsCoordinates, + Set usedInheritedArtifactsCoordinates, + Set usedUndeclaredArtifactsCoordinates, + Set unusedDirectArtifactsCoordinates, + Set unusedInheritedArtifactsCoordinates, + Set unusedUndeclaredArtifactsCoordinates, + File classUsageFile) { + this.treeTextFilePath = treeTextFilePath; + this.sizeOfDependencies = sizeOfDependencies; + this.dependencyAnalyzer = dependencyAnalyzer; + this.usedDirectArtifactsCoordinates = usedDirectArtifactsCoordinates; + this.usedInheritedArtifactsCoordinates = usedInheritedArtifactsCoordinates; + this.usedTransitiveArtifactsCoordinates = usedUndeclaredArtifactsCoordinates; + this.unusedDirectArtifactsCoordinates = unusedDirectArtifactsCoordinates; + this.unusedInheritedArtifactsCoordinates = unusedInheritedArtifactsCoordinates; + this.unusedTransitiveArtifactsCoordinates = unusedUndeclaredArtifactsCoordinates; + this.classUsageFile = classUsageFile; + } - public String parseTreeToJSON() throws ParseException, IOException { - InputType type = InputType.TEXT; - Reader r = new BufferedReader(new InputStreamReader( - new FileInputStream(treeTextFilePath), StandardCharsets.UTF_8 - )); - Parser parser = type.newParser(); - Node tree = parser.parse(r); - NodeAdapter nodeAdapter = new NodeAdapter( - usedDirectArtifactsCoordinates, - usedInheritedArtifactsCoordinates, - usedTransitiveArtifactsCoordinates, - unusedDirectArtifactsCoordinates, - unusedInheritedArtifactsCoordinates, - unusedTransitiveArtifactsCoordinates, - sizeOfDependencies, - dependencyAnalyzer, - classUsageFile - ); - GsonBuilder gsonBuilder = new GsonBuilder() - .setPrettyPrinting() - .registerTypeAdapter(Node.class, nodeAdapter); - Gson gson = gsonBuilder.create(); + public String parseTreeToJSON() throws ParseException, IOException { + InputType type = InputType.TEXT; + Reader r = new BufferedReader(new InputStreamReader( + new FileInputStream(treeTextFilePath), StandardCharsets.UTF_8 + )); + Parser parser = type.newParser(); + Node tree = parser.parse(r); + NodeAdapter nodeAdapter = new NodeAdapter( + usedDirectArtifactsCoordinates, + usedInheritedArtifactsCoordinates, + usedTransitiveArtifactsCoordinates, + unusedDirectArtifactsCoordinates, + unusedInheritedArtifactsCoordinates, + unusedTransitiveArtifactsCoordinates, + sizeOfDependencies, + dependencyAnalyzer, + classUsageFile + ); + GsonBuilder gsonBuilder = new GsonBuilder() + .setPrettyPrinting() + .registerTypeAdapter(Node.class, nodeAdapter); + Gson gson = gsonBuilder.create(); - return gson.toJson(tree); - } + return gson.toJson(tree); + } } diff --git a/depclean-maven-plugin/src/test/java/se/kth/depclean/core/maven/plugin/FileUtilsTest.java b/depclean-maven-plugin/src/test/java/se/kth/depclean/core/maven/plugin/FileUtilsTest.java index 2e644137..40ce3f0f 100644 --- a/depclean-maven-plugin/src/test/java/se/kth/depclean/core/maven/plugin/FileUtilsTest.java +++ b/depclean-maven-plugin/src/test/java/se/kth/depclean/core/maven/plugin/FileUtilsTest.java @@ -1,20 +1,19 @@ package se.kth.depclean.core.maven.plugin; +import java.io.File; +import java.io.IOException; import org.junit.Assert; import org.junit.Test; import se.kth.depclean.util.FileUtils; -import java.io.File; -import java.io.IOException; - public class FileUtilsTest { - @Test - public void deleteDirectory() throws IOException { - File file = new File("./target/dependency"); - if (file.exists()) { - FileUtils.deleteDirectory(new File("./target/dependency")); - } - Assert.assertFalse(file.exists()); + @Test + public void deleteDirectory() throws IOException { + File file = new File("./target/dependency"); + if (file.exists()) { + FileUtils.deleteDirectory(new File("./target/dependency")); } + Assert.assertFalse(file.exists()); + } } \ No newline at end of file From 6b44a002b5ee350d83e8c35cc3509af47013d224 Mon Sep 17 00:00:00 2001 From: cesarsotovalero Date: Sun, 14 Mar 2021 12:38:02 +0100 Subject: [PATCH 5/5] Fix non javadoc checkstyle warnings --- checkstyle.xml | 6 +- .../core/analysis/ClassFileVisitorUtils.java | 2 +- .../DefaultProjectDependencyAnalyzer.java | 9 +- .../analysis/ProjectDependencyAnalyzer.java | 2 +- .../core/analysis/asm/ConstantPoolParser.java | 12 +- .../asm/DefaultAnnotationVisitor.java | 4 +- .../analysis/asm/DefaultClassVisitor.java | 4 +- .../analysis/asm/DefaultFieldVisitor.java | 4 +- .../analysis/asm/DefaultMethodVisitor.java | 4 +- .../analysis/asm/DefaultSignatureVisitor.java | 4 +- .../asm/DependencyClassFileVisitor.java | 2 + .../core/analysis/graph/DefaultCallGraph.java | 3 +- .../java/se/kth/depclean/DepCleanMojo.java | 127 ++++++++---------- .../kth/depclean/util/json/NodeAdapter.java | 32 ++--- 14 files changed, 98 insertions(+), 117 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index b8d1ed10..5b4d3c5e 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -8,7 +8,7 @@ that can be found at https://google.github.io/styleguide/javaguide.html --> - + @@ -22,7 +22,7 @@ + default="checkstyle-suppressions.xml"/> @@ -347,7 +347,7 @@ + default="checkstyle-xpath-suppressions.xml"/> diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java index 80990ca4..53d78e4a 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ClassFileVisitorUtils.java @@ -77,7 +77,7 @@ private static void acceptJar(URL url, ClassFileVisitor visitor) throws IOException { try (JarInputStream in = new JarInputStream(url.openStream())) { JarEntry entry = null; - while ((entry = in.getNextJarEntry()) != null) {//NOSONAR + while ((entry = in.getNextJarEntry()) != null) { //NOSONAR String name = entry.getName(); // ignore files like package-info.class and module-info.class if (name.endsWith(CLASS) && name.indexOf('-') == -1) { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java index 18504255..7d8d2a42 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/DefaultProjectDependencyAnalyzer.java @@ -1,3 +1,5 @@ +package se.kth.depclean.core.analysis; + /* * Copyright (c) 2020, CASTOR Software Research Centre (www.castor.kth.se) * @@ -14,7 +16,6 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ -package se.kth.depclean.core.analysis; import java.io.File; import java.io.IOException; @@ -43,15 +44,9 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz */ private boolean isIgnoredTest; - /** - * ClassAnalyzer - */ @Requirement private final ClassAnalyzer classAnalyzer = new DefaultClassAnalyzer(); - /** - * DependencyAnalyzer - */ @Requirement private final DependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer(); diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java index 9dccb616..5e33d626 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/ProjectDependencyAnalyzer.java @@ -22,7 +22,7 @@ import org.apache.maven.project.MavenProject; /** - * Analyze a project's declared dependencies and effective classes + * Analyze a project's declared dependencies and effective classes. */ public interface ProjectDependencyAnalyzer { // fields ----------------------------------------------------------------- diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java index be9ed485..ce505784 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/ConstantPoolParser.java @@ -30,10 +30,10 @@ /** * A small parser to read the constant pool directly, in case it contains references ASM does not support. - *

- * Adapted from http://stackoverflow.com/a/32278587/23691 - *

- * Constant pool types: + * + *

Adapted from http://stackoverflow.com/a/32278587/23691 + * + *

Constant pool types: * * @see JVM 9 Sepc * @see JVM 10 Sepc @@ -149,8 +149,8 @@ static Set parseConstantPoolClassReferences(ByteBuffer buf) { } } Set result = new HashSet<>(); - for (Integer aClass : classes) { - result.add(stringConstants.get(aClass)); + for (Integer clazz : classes) { + result.add(stringConstants.get(clazz)); } return result; } diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java index a584e403..ea818abf 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultAnnotationVisitor.java @@ -24,8 +24,8 @@ import org.objectweb.asm.Type; /** - * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor - * in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by + * org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. */ public class DefaultAnnotationVisitor extends AnnotationVisitor { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java index 3872645f..b4c7f158 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultClassVisitor.java @@ -30,8 +30,8 @@ import se.kth.depclean.core.analysis.graph.ClassMembersVisitorCounter; /** - * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor - * in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by + * org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. */ public class DefaultClassVisitor extends ClassVisitor { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java index 7d5c65c1..62742e38 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultFieldVisitor.java @@ -24,8 +24,8 @@ import org.objectweb.asm.Opcodes; /** - * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor - * in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by + * org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. */ public class DefaultFieldVisitor extends FieldVisitor { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java index a5672ad1..52801835 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultMethodVisitor.java @@ -29,8 +29,8 @@ import org.objectweb.asm.signature.SignatureVisitor; /** - * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor - * in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by + * org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. */ public class DefaultMethodVisitor extends MethodVisitor { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java index b0c586ce..1a160f4c 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DefaultSignatureVisitor.java @@ -23,8 +23,8 @@ import org.objectweb.asm.signature.SignatureVisitor; /** - * Computes the set of classes referenced by visited code. Inspired by org.objectweb.asm.depend.DependencyVisitor - * in the ASM dependencies example. + * Computes the set of classes referenced by visited code. Inspired by + * org.objectweb.asm.depend.DependencyVisitor in the ASM dependencies example. */ public class DefaultSignatureVisitor extends SignatureVisitor { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java index 2999d021..4a1c7676 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/asm/DependencyClassFileVisitor.java @@ -96,6 +96,8 @@ public void visitClass(String className, InputStream in) { // public methods --------------------------------------------------------- /** + * Getter. + * * @return the set of classes referenced by visited class files */ public Set getDependencies() { diff --git a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java index 83ee958c..e4f74f83 100644 --- a/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java +++ b/depclean-core/src/main/java/se/kth/depclean/core/analysis/graph/DefaultCallGraph.java @@ -1,3 +1,5 @@ +package se.kth.depclean.core.analysis.graph; + /* * Copyright (c) 2020, CASTOR Software Research Centre (www.castor.kth.se) * @@ -14,7 +16,6 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ -package se.kth.depclean.core.analysis.graph; import java.util.HashMap; import java.util.HashSet; diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java index df46573e..f7bc0b81 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java @@ -68,9 +68,8 @@ import se.kth.depclean.util.json.ParsedDependencies; /** - * This Maven mojo is the main class of DepClean. DepClean is built on top of the - * maven-dependency-analyzer component. It produces a clean copy of the project's pom file, without - * bloated dependencies. + * This Maven mojo is the main class of DepClean. DepClean is built on top of the maven-dependency-analyzer component. + * It produces a clean copy of the project's pom file, without bloated dependencies. * * @see * @see @@ -97,23 +96,23 @@ public class DepCleanMojo extends AbstractMojo { private MavenSession session; /** - * If this is true, DepClean creates a debloated version of the pom without unused dependencies, - * called "debloated-pom.xml", in root of the project. + * If this is true, DepClean creates a debloated version of the pom without unused dependencies, called + * "debloated-pom.xml", in root of the project. */ @Parameter(property = "creatPomDebloated", defaultValue = "false") private boolean createPomDebloated; /** - * If this is true, DepClean creates a JSON file with the result of the analysis. The file is - * called "debloat-result.json" and it is located in the root of the project. + * If this is true, DepClean creates a JSON file with the result of the analysis. The file is called + * "debloat-result.json" and it is located in the root of the project. */ @Parameter(property = "createResultJson", defaultValue = "false") private boolean createResultJson; /** - * Add a list of dependencies, identified by their coordinates, to be ignored by DepClean during - * the analysis and considered as used dependencies. Useful to override incomplete result caused - * by bytecode-level analysis Dependency format is groupId:artifactId:version. + * Add a list of dependencies, identified by their coordinates, to be ignored by DepClean during the analysis and + * considered as used dependencies. Useful to override incomplete result caused by bytecode-level analysis Dependency + * format is groupId:artifactId:version. */ @Parameter(property = "ignoreDependencies") private Set ignoreDependencies; @@ -125,31 +124,30 @@ public class DepCleanMojo extends AbstractMojo { private Set ignoreScopes; /** - * If this is true, DepClean will not analyze the test sources in the project, and, therefore, the - * dependencies that are only used for testing will be considered unused. This property is useful - * to detect dependencies that have a compile scope but are only used during testing. Hence, these - * dependencies should have a test scope. + * If this is true, DepClean will not analyze the test sources in the project, and, therefore, the dependencies that + * are only used for testing will be considered unused. This property is useful to detect dependencies that have a + * compile scope but are only used during testing. Hence, these dependencies should have a test scope. */ @Parameter(property = "ignoreTests", defaultValue = "false") private boolean ignoreTests; /** - * If this is true, and DepClean reported any unused direct dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. + * If this is true, and DepClean reported any unused direct dependency in the dependency tree, then the project's + * build fails immediately after running DepClean. */ @Parameter(property = "failIfUnusedDirect", defaultValue = "false") private boolean failIfUnusedDirect; /** - * If this is true, and DepClean reported any unused transitive dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. + * If this is true, and DepClean reported any unused transitive dependency in the dependency tree, then the project's + * build fails immediately after running DepClean. */ @Parameter(property = "failIfUnusedTransitive", defaultValue = "false") private boolean failIfUnusedTransitive; /** - * If this is true, and DepClean reported any unused inherited dependency in the dependency tree, - * then the project's build fails immediately after running DepClean. + * If this is true, and DepClean reported any unused inherited dependency in the dependency tree, then the project's + * build fails immediately after running DepClean. */ @Parameter(property = "failIfUnusedInherited", defaultValue = "false") private boolean failIfUnusedInherited; @@ -182,8 +180,7 @@ private static void writePom(final Path pomFile, final Model model) throws IOExc } /** - * Print the status of the depenencies to the standard output. The format is: - * "[coordinates][scope] [(size)]" + * Print the status of the depenencies to the standard output. The format is: "[coordinates][scope] [(size)]" * * @param sizeOfDependencies A map with the size of the dependencies. * @param dependencies The set dependencies to print. @@ -198,9 +195,8 @@ private void printDependencies(Map sizeOfDependencies, Set } /** - * Utility method to obtain the size of a dependency from a map of dependency -> size. If the size - * of the dependency cannot be obtained form the map (no key with the name of the dependency - * exists), then it returns 0. + * Utility method to obtain the size of a dependency from a map of dependency -> size. If the size of the dependency + * cannot be obtained form the map (no key with the name of the dependency exists), then it returns 0. * * @param sizeOfDependencies A map of dependency -> size. * @param dependency The coordinates of a dependency. @@ -222,8 +218,8 @@ private Long getSizeOfDependency(Map sizeOfDependencies, String de * Get the size of the dependency in human readable format. * * @param dependency The dependency. - * @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the - * downloaded jar file i.e., [artifactId]-[version].jar + * @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the downloaded jar file i.e., + * [artifactId]-[version].jar * @return The human readable representation of the dependency size. */ private String getSize(String dependency, Map sizeOfDependencies) { @@ -265,8 +261,8 @@ private boolean isChildren(Artifact artifact, Dependency dependency) List dependencyNodes = getDependencyNodes(); for (DependencyNode node : dependencyNodes) { Dependency dependencyNode = createDependency(node.getArtifact()); - if (dependency.getGroupId().equals(dependencyNode.getGroupId()) && - dependency.getArtifactId().equals(dependencyNode.getArtifactId())) { + if (dependency.getGroupId().equals(dependencyNode.getGroupId()) + && dependency.getArtifactId().equals(dependencyNode.getArtifactId())) { // now we are in the target dependency for (DependencyNode child : node.getChildren()) { if (child.getArtifact().equals(artifact)) { @@ -284,7 +280,8 @@ private boolean isChildren(Artifact artifact, Dependency dependency) * This method returns a list of dependency nodes from a graph of dependency tree. * * @return The nodes in the dependency graph. - * @throws DependencyGraphBuilderException If the graph cannot be built. + * @throws {@link org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException} if the graph cannot be + * built. */ private List getDependencyNodes() throws DependencyGraphBuilderException { ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest( @@ -354,8 +351,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { /* Copy direct dependencies locally */ try { - MavenInvoker.runCommand("mvn dependency:copy-dependencies -DoutputDirectory=" + - project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES); + MavenInvoker.runCommand("mvn dependency:copy-dependencies -DoutputDirectory=" + + project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES); } catch (IOException | InterruptedException e) { getLog().error("Unable to resolve all the dependencies."); Thread.currentThread().interrupt(); @@ -365,11 +362,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { // TODO remove this workaround later if (new File(project.getBuild().getDirectory() + File.separator + "libs").exists()) { try { - FileUtils - .copyDirectory(new File(project.getBuild().getDirectory() + File.separator + "libs"), - new File(project.getBuild().getDirectory() + File.separator - + DIRECTORY_TO_COPY_DEPENDENCIES) - ); + FileUtils.copyDirectory(new File(project.getBuild().getDirectory() + File.separator + "libs"), + new File(project.getBuild().getDirectory() + File.separator + DIRECTORY_TO_COPY_DEPENDENCIES) + ); } catch (IOException e) { getLog().error("Error copying directory libs to dependency"); } @@ -462,8 +457,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } - // TODO Fix: The used transitive dependencies induced by inherited dependencies should be considered as used - // inherited + // TODO Fix: The used transitive dependencies induced by inherited dependencies should be considered + // as used inherited for (Artifact artifact : usedTransitiveArtifacts) { String artifactGA = artifact.getGroupId() + ":" + artifact.getArtifactId(); String artifactGAVS = @@ -542,62 +537,54 @@ public void execute() throws MojoExecutionException, MojoFailureException { printString(" D E P C L E A N A N A L Y S I S R E S U L T S"); printString(SEPARATOR); - printString( - "Used direct dependencies".toUpperCase() + " [" + usedDirectArtifactsCoordinates.size() - + "]" + ": "); + printString("Used direct dependencies".toUpperCase() + + " [" + usedDirectArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, usedDirectArtifactsCoordinates); - printString( - "Used inherited dependencies".toUpperCase() + " [" + usedInheritedArtifactsCoordinates - .size() + "]" + ": "); + printString("Used inherited dependencies".toUpperCase() + + " [" + usedInheritedArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, usedInheritedArtifactsCoordinates); - printString( - "Used transitive dependencies".toUpperCase() + " [" + usedTransitiveArtifactsCoordinates - .size() + - "]" + ": "); + printString("Used transitive dependencies".toUpperCase() + + " [" + usedTransitiveArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, usedTransitiveArtifactsCoordinates); - printString("Potentially unused direct dependencies".toUpperCase() + " [" - + unusedDirectArtifactsCoordinates.size() + "]" + ": "); + printString("Potentially unused direct dependencies".toUpperCase() + + " [" + unusedDirectArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, unusedDirectArtifactsCoordinates); - printString("Potentially unused inherited dependencies".toUpperCase() + " [" - + unusedInheritedArtifactsCoordinates.size() + "]" + ": "); + printString("Potentially unused inherited dependencies".toUpperCase() + + " [" + unusedInheritedArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, unusedInheritedArtifactsCoordinates); - printString("Potentially unused transitive dependencies".toUpperCase() + " [" - + unusedTransitiveArtifactsCoordinates.size() + "]" + ": "); + printString("Potentially unused transitive dependencies".toUpperCase() + + " [" + unusedTransitiveArtifactsCoordinates.size() + "]" + ": "); printDependencies(sizeOfDependencies, unusedTransitiveArtifactsCoordinates); if (!ignoreDependencies.isEmpty()) { printString(SEPARATOR); printString( - "Dependencies ignored in the analysis by the user" + " [" + ignoreDependencies.size() - + "]" + ":" + - " "); + "Dependencies ignored in the analysis by the user" + + " [" + ignoreDependencies.size() + "]" + ":" + " "); ignoreDependencies.stream().forEach(s -> printString("\t" + s)); } /* Fail the build if there are unused direct dependencies */ if (failIfUnusedDirect && !unusedDirectArtifactsCoordinates.isEmpty()) { throw new MojoExecutionException( - "Build failed due to unused direct dependencies in the dependency tree " + - "of the project."); + "Build failed due to unused direct dependencies in the dependency tree of the project."); } /* Fail the build if there are unused direct dependencies */ if (failIfUnusedTransitive && !unusedTransitiveArtifactsCoordinates.isEmpty()) { throw new MojoExecutionException( - "Build failed due to unused transitive dependencies in the dependency " + - "tree of the project."); + "Build failed due to unused transitive dependencies in the dependency tree of the project."); } /* Fail the build if there are unused direct dependencies */ if (failIfUnusedInherited && !unusedInheritedArtifactsCoordinates.isEmpty()) { throw new MojoExecutionException( - "Build failed due to unused inherited dependencies in the dependency " + - "tree of the project."); + "Build failed due to unused inherited dependencies in the dependency tree of the project."); } /* Writing the debloated version of the pom */ @@ -608,8 +595,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { if (!usedTransitiveArtifacts.isEmpty()) { getLog() - .info("Adding " + unusedTransitiveArtifactsCoordinates.size() + " used transitive " + - "dependencies as direct dependencies."); + .info("Adding " + unusedTransitiveArtifactsCoordinates.size() + + " used transitive dependencies as direct dependencies."); for (Artifact usedUndeclaredArtifact : usedTransitiveArtifacts) { model.addDependency(createDependency(usedUndeclaredArtifact)); } @@ -625,8 +612,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { + " unused direct dependencies."); for (Artifact unusedDeclaredArtifact : unusedDirectArtifacts) { for (Dependency dependency : model.getDependencies()) { - if (dependency.getGroupId().equals(unusedDeclaredArtifact.getGroupId()) && - dependency.getArtifactId().equals(unusedDeclaredArtifact.getArtifactId())) { + if (dependency.getGroupId().equals(unusedDeclaredArtifact.getGroupId()) + && dependency.getArtifactId().equals(unusedDeclaredArtifact.getArtifactId())) { model.removeDependency(dependency); break; } @@ -641,8 +628,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { if (!unusedTransitiveArtifacts.isEmpty()) { getLog().info( - "Excluding " + unusedTransitiveArtifacts.size() + " unused transitive dependencies " + - "one-by-one."); + "Excluding " + unusedTransitiveArtifacts.size() + + " unused transitive dependencies one-by-one."); for (Dependency dependency : model.getDependencies()) { for (Artifact artifact : unusedTransitiveArtifacts) { if (isChildren(artifact, dependency)) { diff --git a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java index 95d33803..2833040b 100644 --- a/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java +++ b/depclean-maven-plugin/src/main/java/se/kth/depclean/util/json/NodeAdapter.java @@ -51,8 +51,8 @@ public void write(JsonWriter jsonWriter, Node node) throws IOException { String coordinates = node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getVersion() + ":" + node.getScope(); String canonical = - node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getPackaging() + ":" + node.getVersion() + - ":" + node.getScope(); + node.getGroupId() + ":" + node.getArtifactId() + ":" + node.getPackaging() + ":" + node.getVersion() + + ":" + node.getScope(); String dependencyJar = node.getArtifactId() + "-" + node.getVersion() + ".jar"; // Write to the class-usage.csv file @@ -107,21 +107,18 @@ public void write(JsonWriter jsonWriter, Node node) throws IOException { (usedInheritedArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates .contains(coordinates)) ? "inherited" : (usedTransitiveArtifactsCoordinates.contains(coordinates) || unusedTransitiveArtifactsCoordinates - .contains(coordinates)) ? "transitive" : - "unknown") + .contains(coordinates)) ? "transitive" : "unknown") .name("status") .value((usedDirectArtifactsCoordinates.contains(coordinates) || usedInheritedArtifactsCoordinates - .contains(coordinates) || usedTransitiveArtifactsCoordinates.contains(coordinates)) ? - "used" : + .contains(coordinates) || usedTransitiveArtifactsCoordinates.contains(coordinates)) + ? "used" : (unusedDirectArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates - .contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) ? - "bloated" : - "unknown") + .contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) + ? "bloated" : "unknown") .name("parent") - .value(node.getParent() != null ? - node.getParent().getArtifactCanonicalForm() : - "unknown"); + .value(node.getParent() != null + ? node.getParent().getArtifactCanonicalForm() : "unknown"); JsonWriter allTypes = localWriter.name("allTypes").beginArray(); if (dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical)) { @@ -140,12 +137,11 @@ public void write(JsonWriter jsonWriter, Node node) throws IOException { usedTypes.endArray(); localWriter.name("usageRatio") - .value(dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) ? - dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().isEmpty() ? - 0 : // handle division by zero - ((double) dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes().size() / - dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().size()) : - -1) + .value(dependencyAnalyzer.getArtifactClassesMap().containsKey(canonical) + ? dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().isEmpty() + ? 0 : // handle division by zero + ((double) dependencyAnalyzer.getArtifactClassesMap().get(canonical).getUsedTypes().size() + / dependencyAnalyzer.getArtifactClassesMap().get(canonical).getAllTypes().size()) : -1) .name("children") .beginArray();