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");

Reply via email to