This is an automated email from the ASF dual-hosted git repository. slachiewicz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-dependency-analyzer.git
commit 45f5cbe1c5b04adba18fcd6c07c5f9df7d4573cf Author: Sylwester Lachiewicz <[email protected]> AuthorDate: Thu Oct 29 20:49:32 2026 +0000 Credit declared artifact when a class exists in several artifacts The first artifact containing a class used to win, so a declared artifact could be reported unused while a duplicate jar was reported used undeclared. --- .../analyzer/DefaultProjectDependencyAnalyzer.java | 37 +++++++++++++++- .../DefaultProjectDependencyAnalyzerTest.java | 50 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java index 5e82317..bc4320f 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java @@ -120,7 +120,8 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz try { ClassesPatterns excludedClassesPatterns = new ClassesPatterns(excludedClasses); Map<Artifact, Set<String>> artifactClassMap = buildArtifactClassMap(project, excludedClassesPatterns); - Map<String, Artifact> classToArtifactMap = buildClassToArtifactMap(artifactClassMap); + Set<Artifact> declaredArtifacts = buildDeclaredArtifacts(project, artifactHandlerManager); + Map<String, Artifact> classToArtifactMap = buildClassToArtifactMap(artifactClassMap, declaredArtifacts); Set<DependencyUsage> mainDependencyClasses = new HashSet<>(); for (MainDependencyClassesProvider provider : mainDependencyClassesProviders) { @@ -148,7 +149,6 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz .keySet(); Set<Artifact> testOnlyArtifacts = removeAll(testArtifacts, mainUsedArtifacts); - Set<Artifact> declaredArtifacts = buildDeclaredArtifacts(project, artifactHandlerManager); Set<Artifact> usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts); usedDeclaredArtifacts.retainAll(usedArtifacts.keySet()); @@ -485,9 +485,42 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz return false; } + /** + * Maps each class to the artifact that provides it. When several artifacts contain the class, the first one + * wins. + * + * @param artifactClassMap the classes of each artifact + * @return the providing artifact per class + */ static Map<String, Artifact> buildClassToArtifactMap(Map<Artifact, Set<String>> artifactClassMap) { + return buildClassToArtifactMap(artifactClassMap, Collections.emptySet()); + } + + /** + * Maps each class to the artifact that provides it. When several artifacts contain the class, a declared + * artifact wins over an undeclared one, and otherwise the first one wins. If two declared artifacts contain the + * class, only the first of them is credited. + * + * @param artifactClassMap the classes of each artifact + * @param declaredArtifacts the artifacts declared by the project + * @return the providing artifact per class + */ + static Map<String, Artifact> buildClassToArtifactMap( + Map<Artifact, Set<String>> artifactClassMap, Set<Artifact> declaredArtifacts) { + Set<String> declaredIds = declaredArtifacts.stream() + .map(Artifact::getDependencyConflictId) + .collect(Collectors.toSet()); Map<String, Artifact> classToArtifactMap = new HashMap<>(); + for (Map.Entry<Artifact, Set<String>> entry : artifactClassMap.entrySet()) { + Artifact artifact = entry.getKey(); + if (declaredIds.contains(artifact.getDependencyConflictId())) { + for (String className : entry.getValue()) { + classToArtifactMap.putIfAbsent(className, artifact); + } + } + } + for (Map.Entry<Artifact, Set<String>> entry : artifactClassMap.entrySet()) { Artifact artifact = entry.getKey(); for (String className : entry.getValue()) { diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzerTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzerTest.java index 27a0d49..b7ee223 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzerTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzerTest.java @@ -118,6 +118,56 @@ class DefaultProjectDependencyAnalyzerTest { assertThat(result.get("duplicateClass")).isEqualTo(artifact1); } + @Test + void testBuildClassToArtifactMapWithDuplicatesPrefersDeclared() { + Artifact undeclared = aTestArtifact("undeclared"); + Artifact declared = aTestArtifact("declared"); + + Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>(); + artifactClassMap.put(undeclared, new HashSet<>(Arrays.asList("duplicateClass", "otherClass"))); + artifactClassMap.put(declared, Collections.singleton("duplicateClass")); + + Map<String, Artifact> result = DefaultProjectDependencyAnalyzer.buildClassToArtifactMap( + artifactClassMap, Collections.singleton(declared)); + + assertThat(result).hasSize(2); + assertThat(result.get("duplicateClass")).isEqualTo(declared); + assertThat(result.get("otherClass")).isEqualTo(undeclared); + } + + @Test + void testBuildClassToArtifactMapWithDuplicatesAndNoneDeclaredKeepsFirst() { + Artifact artifact1 = aTestArtifact("artifact1"); + Artifact artifact2 = aTestArtifact("artifact2"); + Artifact declaredButAbsent = aTestArtifact("absent"); + + Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>(); + artifactClassMap.put(artifact1, Collections.singleton("duplicateClass")); + artifactClassMap.put(artifact2, Collections.singleton("duplicateClass")); + + Map<String, Artifact> result = DefaultProjectDependencyAnalyzer.buildClassToArtifactMap( + artifactClassMap, Collections.singleton(declaredButAbsent)); + + assertThat(result.get("duplicateClass")).isEqualTo(artifact1); + } + + @Test + void testDuplicateClassIsCreditedToDeclaredArtifactOnly() { + Artifact plexusUtils = aTestArtifact("plexus-utils"); + Artifact plexusXml = aTestArtifact("plexus-xml"); + + Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>(); + artifactClassMap.put(plexusUtils, Collections.singleton("Xpp3Dom")); + artifactClassMap.put(plexusXml, Collections.singleton("Xpp3Dom")); + + Map<String, Artifact> classToArtifactMap = DefaultProjectDependencyAnalyzer.buildClassToArtifactMap( + artifactClassMap, Collections.singleton(plexusXml)); + Map<Artifact, Set<DependencyUsage>> used = DefaultProjectDependencyAnalyzer.buildUsedArtifacts( + classToArtifactMap, Collections.singleton(new DependencyUsage("Xpp3Dom", "Main"))); + + assertThat(used.keySet()).containsExactly(plexusXml); + } + @Test void testBuildClassToArtifactMapWithMultipleClasses() { Artifact artifact1 = aTestArtifact("artifact1");
