From 9e7830181c868dfd03ce82233be6eae8417e1ee4 Mon Sep 17 00:00:00 2001 From: smarkwal Date: Tue, 24 Sep 2024 14:53:01 +0200 Subject: [PATCH] Feature: JAR Files: If there are multiple artifacts with the same checksum, include them all in column "Artifact coordinates". --- .../org/jarhc/it/MainTest/report.txt | 3 +- .../jarhc/analyzer/DependenciesAnalyzer.java | 53 +++++++++++-------- .../org/jarhc/analyzer/JarFilesAnalyzer.java | 13 +++-- .../org/jarhc/loader/AbstractFileLoader.java | 15 +++--- .../main/java/org/jarhc/model/JarFile.java | 21 ++++---- .../analyzer/DependenciesAnalyzerTest.java | 30 +++++------ .../jarhc/analyzer/JarFilesAnalyzerTest.java | 8 +-- .../java/org/jarhc/test/ClasspathBuilder.java | 3 +- 8 files changed, 79 insertions(+), 67 deletions(-) diff --git a/jarhc/src/integrationTest/resources/org/jarhc/it/MainTest/report.txt b/jarhc/src/integrationTest/resources/org/jarhc/it/MainTest/report.txt index b7f3ac9b..aeb86131 100644 --- a/jarhc/src/integrationTest/resources/org/jarhc/it/MainTest/report.txt +++ b/jarhc/src/integrationTest/resources/org/jarhc/it/MainTest/report.txt @@ -6,8 +6,9 @@ JAR Files List of JAR files found in classpath. JAR file | Size | Classes | Resources | Checksum (SHA-1) | Artifact coordinates ----------------------+---------+---------+-----------+------------------------------------------+--------------------------- +---------------------+---------+---------+-----------+------------------------------------------+------------------------------------------- slf4j-api-1.7.28.jar | 40.2 KB | 34 | 0 | 2cd9b264f76e3d087ee21bfc99305928e1bdb443 | org.slf4j:slf4j-api:1.7.28 + | | | | | org.netbeans.external:slf4j-api:RELEASE113 Classpath | 40.2 KB | 34 | 0 | - | - Modules diff --git a/jarhc/src/main/java/org/jarhc/analyzer/DependenciesAnalyzer.java b/jarhc/src/main/java/org/jarhc/analyzer/DependenciesAnalyzer.java index 0d8cc5e1..d046e148 100644 --- a/jarhc/src/main/java/org/jarhc/analyzer/DependenciesAnalyzer.java +++ b/jarhc/src/main/java/org/jarhc/analyzer/DependenciesAnalyzer.java @@ -138,10 +138,13 @@ private String getStatus(List dependencies, Classpath classpath) { // check if it is an exact match StringBuilder line = new StringBuilder("OK"); - String coordinates = jarFile.getCoordinates(); - Artifact artifact = new Artifact(coordinates); - if (!artifact.equals(dependency.toArtifact())) { - line.append(" (version ").append(artifact.getVersion()).append(")"); + Artifact artifact = jarFile.getArtifacts().get(0); + if (artifact.getGroupId().equals(dependency.getGroupId()) && artifact.getArtifactId().equals(dependency.getArtifactId())) { + if (!artifact.getVersion().equals(dependency.getVersion())) { + line.append(" (version ").append(artifact.getVersion()).append(")"); + } + } else { + line.append(" (").append(artifact.toCoordinates()).append(")"); } String classLoader = jarFile.getClassLoader(); @@ -160,38 +163,42 @@ private String getStatus(List dependencies, Classpath classpath) { private boolean matches(JarFile jarFile, Dependency dependency) { - String coordinates = jarFile.getCoordinates(); - if (coordinates == null) { + List artifacts = jarFile.getArtifacts(); + if (artifacts == null || artifacts.isEmpty()) { // skip JAR files without coordinates return false; } - Artifact jarArtifact = new Artifact(coordinates); - Artifact dependencyArtifact = dependency.toArtifact(); + for (Artifact artifact : artifacts) { - // check if group ID matches - if (!Objects.equals(jarArtifact.getGroupId(), dependencyArtifact.getGroupId())) { - return false; - } + // check if group ID matches + if (!Objects.equals(artifact.getGroupId(), dependency.getGroupId())) { + continue; + } - // check if artifact ID matches - if (!Objects.equals(jarArtifact.getArtifactId(), dependencyArtifact.getArtifactId())) { - return false; - } + // check if artifact ID matches + if (!Objects.equals(artifact.getArtifactId(), dependency.getArtifactId())) { + continue; + } - // TODO: check if type matches? + // TODO: check if type matches? + + // note: version may be different + return true; + } - // note: version may be different - return true; + return false; } private String getCoordinates(JarFile jarFile) { - // TODO: return "[timeout]" if Maven Search API request timed out - String coordinates = jarFile.getCoordinates(); - if (coordinates == null) { + List artifacts = jarFile.getArtifacts(); + if (artifacts == null) { + return ERROR; // most likely a response timeout + } else if (artifacts.isEmpty()) { return UNKNOWN; } - return coordinates; + // return only coordinates of "primary" artifact + return artifacts.get(0).toCoordinates(); } } diff --git a/jarhc/src/main/java/org/jarhc/analyzer/JarFilesAnalyzer.java b/jarhc/src/main/java/org/jarhc/analyzer/JarFilesAnalyzer.java index 75aca4bb..2604aa89 100644 --- a/jarhc/src/main/java/org/jarhc/analyzer/JarFilesAnalyzer.java +++ b/jarhc/src/main/java/org/jarhc/analyzer/JarFilesAnalyzer.java @@ -19,15 +19,18 @@ import static org.jarhc.utils.FileUtils.formatFileSize; import java.util.List; +import org.jarhc.artifacts.Artifact; import org.jarhc.model.ClassDef; import org.jarhc.model.Classpath; import org.jarhc.model.JarFile; import org.jarhc.report.ReportSection; import org.jarhc.report.ReportTable; +import org.jarhc.utils.StringUtils; public class JarFilesAnalyzer implements Analyzer { private static final String UNKNOWN = "[unknown]"; + private static final String ERROR = "[error]"; @Override public ReportSection analyze(Classpath classpath) { @@ -80,12 +83,14 @@ private String getChecksumInfo(JarFile jarFile) { } private String getCoordinates(JarFile jarFile) { - // TODO: return "[timeout]" if Maven Search API request timed out - String coordinates = jarFile.getCoordinates(); - if (coordinates == null || coordinates.isEmpty()) { + List artifacts = jarFile.getArtifacts(); + if (artifacts == null) { + return ERROR; // most likely a response timeout + } else if (artifacts.isEmpty()) { return UNKNOWN; } - return coordinates; + // return coordinates of all artifacts + return artifacts.stream().map(Artifact::toCoordinates).collect(StringUtils.joinLines()); } } diff --git a/jarhc/src/main/java/org/jarhc/loader/AbstractFileLoader.java b/jarhc/src/main/java/org/jarhc/loader/AbstractFileLoader.java index fdd7affb..46480941 100644 --- a/jarhc/src/main/java/org/jarhc/loader/AbstractFileLoader.java +++ b/jarhc/src/main/java/org/jarhc/loader/AbstractFileLoader.java @@ -215,19 +215,16 @@ protected void load(String fileName, Archive archive, List jarFiles) th String checksum = archive.getFileChecksum(); // try to identify JAR file as Maven artifact - // TODO: if artifact was given as coordinates, skip this step and re-use the original coordinates instead. - String coordinates = null; + List artifacts = null; try { - List artifacts = repository.findArtifacts(checksum); - // TODO: remember all artifact coordinates for this JAR file - if (!artifacts.isEmpty()) { - Artifact artifact = artifacts.get(0); - coordinates = artifact.toCoordinates(); - } + artifacts = repository.findArtifacts(checksum); } catch (RepositoryException e) { logger.warn("Artifact resolution error", e); } + // TODO: if artifact was given as coordinates, make sure these coordinates are included in the list of artifacts + // TODO: check if JAR file contains POM information + // normalize file name (optional) if (fileNameNormalizer != null) { fileName = fileNameNormalizer.getFileName(fileName, checksum); @@ -236,7 +233,7 @@ protected void load(String fileName, Archive archive, List jarFiles) th JarFile jarFile = JarFile.withName(fileName) .withFileSize(archive.getFileSize()) .withChecksum(checksum) - .withCoordinates(coordinates) + .withArtifacts(artifacts) .withClassLoader(classLoader) .withReleases(releases) .withModuleInfo(moduleInfo) diff --git a/jarhc/src/main/java/org/jarhc/model/JarFile.java b/jarhc/src/main/java/org/jarhc/model/JarFile.java index 856b05d2..f4367194 100644 --- a/jarhc/src/main/java/org/jarhc/model/JarFile.java +++ b/jarhc/src/main/java/org/jarhc/model/JarFile.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.jarhc.artifacts.Artifact; /** * Represents the content of a JAR file. @@ -49,7 +50,7 @@ public class JarFile { /** * Maven coordinates of the JAR file */ - private final String coordinates; + private final List artifacts; /** * Name of the class loader used to load this JAR file. @@ -97,7 +98,7 @@ public class JarFile { * @param fileName JAR file name * @param fileSize JAR file size in bytes * @param checksum JAR file SHA-1 checksum - * @param coordinates JAR file Maven coordinates + * @param artifacts List of Maven coordinates * @param classLoader Class loader name * @param releases List of releases supported by this JAR file (for multi-release JAR files) * @param moduleInfo Module information (for modular JAR files) @@ -106,7 +107,7 @@ public class JarFile { * @throws IllegalArgumentException If fileName or classDefs is null. */ @SuppressWarnings("java:S107") // Methods should not have too many parameters - private JarFile(String fileName, long fileSize, String checksum, String coordinates, String classLoader, Set releases, ModuleInfo moduleInfo, List classDefs, List resourceDefs) { + private JarFile(String fileName, long fileSize, String checksum, List artifacts, String classLoader, Set releases, ModuleInfo moduleInfo, List classDefs, List resourceDefs) { if (fileName == null) throw new IllegalArgumentException("fileName"); if (releases == null) throw new IllegalArgumentException("releases"); if (moduleInfo == null) throw new IllegalArgumentException("moduleInfo"); @@ -114,7 +115,7 @@ private JarFile(String fileName, long fileSize, String checksum, String coordina this.fileName = fileName; this.fileSize = fileSize; this.checksum = checksum; - this.coordinates = coordinates; + this.artifacts = artifacts; this.classLoader = classLoader; this.releases = new TreeSet<>(releases); this.moduleInfo = moduleInfo; @@ -171,8 +172,8 @@ public String getChecksum() { return checksum; } - public String getCoordinates() { - return coordinates; + public List getArtifacts() { + return artifacts; } public String getClassLoader() { @@ -256,7 +257,7 @@ public static class Builder { private final String fileName; private long fileSize = -1; private String checksum; - private String coordinates; + private List artifacts; private String classLoader; private final Set releases = new TreeSet<>(); private ModuleInfo moduleInfo = ModuleInfo.UNNAMED; @@ -277,8 +278,8 @@ public Builder withChecksum(String checksum) { return this; } - public Builder withCoordinates(String coordinates) { - this.coordinates = coordinates; + public Builder withArtifacts(List artifacts) { + this.artifacts = artifacts; return this; } @@ -319,7 +320,7 @@ public Builder withResourceDefs(Collection resourceDefs) { } public JarFile build() { - return new JarFile(fileName, fileSize, checksum, coordinates, classLoader, releases, moduleInfo, classDefs, resourceDefs); + return new JarFile(fileName, fileSize, checksum, artifacts, classLoader, releases, moduleInfo, classDefs, resourceDefs); } } diff --git a/jarhc/src/test/java/org/jarhc/analyzer/DependenciesAnalyzerTest.java b/jarhc/src/test/java/org/jarhc/analyzer/DependenciesAnalyzerTest.java index c8a3fab7..f33c17b2 100644 --- a/jarhc/src/test/java/org/jarhc/analyzer/DependenciesAnalyzerTest.java +++ b/jarhc/src/test/java/org/jarhc/analyzer/DependenciesAnalyzerTest.java @@ -64,19 +64,19 @@ void analyze() { // prepare: provided List jarFiles = new ArrayList<>(); - jarFiles.add(JarFile.withName("lib-with-deps-1.jar").withCoordinates("group:lib-with-deps-1:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-with-deps-2.jar").withCoordinates("group:lib-with-deps-2:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-with-deps-3.jar").withCoordinates("group:lib-with-deps-3:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-with-deps-4.jar").withCoordinates("group:lib-with-deps-4:1.1:jar").build()); + jarFiles.add(JarFile.withName("lib-with-deps-1.jar").withArtifacts(List.of(new Artifact("group:lib-with-deps-1:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-with-deps-2.jar").withArtifacts(List.of(new Artifact("group:lib-with-deps-2:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-with-deps-3.jar").withArtifacts(List.of(new Artifact("group:lib-with-deps-3:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-with-deps-4.jar").withArtifacts(List.of(new Artifact("group:lib-with-deps-4:1.1:jar"))).build()); Classpath provided = new Classpath(jarFiles, null, ClassLoaderStrategy.ParentLast); // prepare: classpath jarFiles = new ArrayList<>(); - jarFiles.add(JarFile.withName("lib-with-deps.jar").withCoordinates("group:lib-with-deps:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-no-deps.jar").withCoordinates("group:lib-no-deps:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-no-pom.jar").withCoordinates("group:lib-no-pom:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-repo-error.jar").withCoordinates("group:lib-repo-error:1.0:jar").build()); - jarFiles.add(JarFile.withName("lib-unknown.jar").withCoordinates(null).build()); + jarFiles.add(JarFile.withName("lib-with-deps.jar").withArtifacts(List.of(new Artifact("group:lib-with-deps:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-no-deps.jar").withArtifacts(List.of(new Artifact("group:lib-no-deps:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-no-pom.jar").withArtifacts(List.of(new Artifact("group:lib-no-pom:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-repo-error.jar").withArtifacts(List.of(new Artifact("group:lib-repo-error:1.0:jar"))).build()); + jarFiles.add(JarFile.withName("lib-unknown.jar").withArtifacts(List.of()).build()); Classpath classpath = new Classpath(jarFiles, provided, ClassLoaderStrategy.ParentLast); Logger logger = LoggerBuilder.collect(DependenciesAnalyzer.class); @@ -103,15 +103,15 @@ void analyze() { List rows = table.getRows(); assertEquals(5, rows.size()); - assertValuesEquals(rows.get(0), "lib-with-deps.jar", "group:lib-with-deps:1.0:jar", "group:lib-with-deps-1:1.0 (provided, optional)\ngroup:lib-with-deps-2:1.0 (runtime)\ngroup:lib-with-deps-4:1.0 (system)\ngroup:lib-with-deps-5:1.0 (import, optional)", "OK\nOK\nOK (version 1.1)\nUnsatisfied"); - assertValuesEquals(rows.get(1), "lib-no-deps.jar", "group:lib-no-deps:1.0:jar", "[none]", ""); - assertValuesEquals(rows.get(2), "lib-no-pom.jar", "group:lib-no-pom:1.0:jar", "[error]", ""); - assertValuesEquals(rows.get(3), "lib-repo-error.jar", "group:lib-repo-error:1.0:jar", "[error]", ""); + assertValuesEquals(rows.get(0), "lib-with-deps.jar", "group:lib-with-deps:1.0", "group:lib-with-deps-1:1.0 (provided, optional)\ngroup:lib-with-deps-2:1.0 (runtime)\ngroup:lib-with-deps-4:1.0 (system)\ngroup:lib-with-deps-5:1.0 (import, optional)", "OK\nOK\nOK (version 1.1)\nUnsatisfied"); + assertValuesEquals(rows.get(1), "lib-no-deps.jar", "group:lib-no-deps:1.0", "[none]", ""); + assertValuesEquals(rows.get(2), "lib-no-pom.jar", "group:lib-no-pom:1.0", "[error]", ""); + assertValuesEquals(rows.get(3), "lib-repo-error.jar", "group:lib-repo-error:1.0", "[error]", ""); assertValuesEquals(rows.get(4), "lib-unknown.jar", "[unknown]", "[unknown]", ""); assertLogger(logger).inAnyOrder() - .hasError("Resolver error for artifact: group:lib-no-pom:1.0:jar", new RepositoryException("test")) - .hasError("Resolver error for artifact: group:lib-repo-error:1.0:jar", new RepositoryException("test")) + .hasError("Resolver error for artifact: group:lib-no-pom:1.0", new RepositoryException("test")) + .hasError("Resolver error for artifact: group:lib-repo-error:1.0", new RepositoryException("test")) .isEmpty(); } diff --git a/jarhc/src/test/java/org/jarhc/analyzer/JarFilesAnalyzerTest.java b/jarhc/src/test/java/org/jarhc/analyzer/JarFilesAnalyzerTest.java index c3eba4c7..819d7f86 100644 --- a/jarhc/src/test/java/org/jarhc/analyzer/JarFilesAnalyzerTest.java +++ b/jarhc/src/test/java/org/jarhc/analyzer/JarFilesAnalyzerTest.java @@ -66,10 +66,10 @@ void test_analyze() { List rows = table.getRows(); assertEquals(5, rows.size()); - assertValuesEquals(rows.get(0), "a.jar", "128 B", "1", "1", "0a4c26b96ef92cceb7c2c7c0e19c808baeb8d696", "org.jarhc:0a4c2:1.0:jar"); - assertValuesEquals(rows.get(1), "b.jar", "4.00 KB", "2", "0", "1271677b4f55e181e4c8192f0edf87bb3ff9fde5", "org.jarhc:12716:1.0:jar"); - assertValuesEquals(rows.get(2), "c.jar", "23.4 KB", "1", "0", "fa2798370b42e2616cb0d374b2ae4be836439077", "org.jarhc:fa279:1.0:jar"); - assertValuesEquals(rows.get(3), "d.jar", "1.18 MB", "0", "0", "458dea9210ea076f4c422be47390a9f2c0fcb0f8", "org.jarhc:458de:1.0:jar"); + assertValuesEquals(rows.get(0), "a.jar", "128 B", "1", "1", "0a4c26b96ef92cceb7c2c7c0e19c808baeb8d696", "org.jarhc:0a4c2:1.0"); + assertValuesEquals(rows.get(1), "b.jar", "4.00 KB", "2", "0", "1271677b4f55e181e4c8192f0edf87bb3ff9fde5", "org.jarhc:12716:1.0"); + assertValuesEquals(rows.get(2), "c.jar", "23.4 KB", "1", "0", "fa2798370b42e2616cb0d374b2ae4be836439077", "org.jarhc:fa279:1.0"); + assertValuesEquals(rows.get(3), "d.jar", "1.18 MB", "0", "0", "458dea9210ea076f4c422be47390a9f2c0fcb0f8", "org.jarhc:458de:1.0"); assertValuesEquals(rows.get(4), "Classpath", "1.20 MB", "4", "1", "-", "-"); } diff --git a/jarhc/src/testFixtures/java/org/jarhc/test/ClasspathBuilder.java b/jarhc/src/testFixtures/java/org/jarhc/test/ClasspathBuilder.java index 339cc236..785f2d9d 100644 --- a/jarhc/src/testFixtures/java/org/jarhc/test/ClasspathBuilder.java +++ b/jarhc/src/testFixtures/java/org/jarhc/test/ClasspathBuilder.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import org.jarhc.artifacts.Artifact; import org.jarhc.java.ClassLoader; import org.jarhc.java.ClassLoaderStrategy; import org.jarhc.model.ClassDef; @@ -154,7 +155,7 @@ private void closeJarFile() { JarFile jarFile = JarFile.withName(fileName) .withFileSize(fileSize) .withChecksum(checksum) - .withCoordinates(coordinates) + .withArtifacts(List.of(new Artifact(coordinates))) .withReleases(releases) .withModuleInfo(moduleInfo) .withClassDefs(classDefs)