This is an automated email from the ASF dual-hosted git repository.

slawekjaranowski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-dependency-analyzer.git


The following commit(s) were added to refs/heads/master by this push:
     new a514b25  Optimize artifact lookup in DefaultProjectDependencyAnalyzer 
(#270)
a514b25 is described below

commit a514b25bf228076fbd99c047d88de7fecbde8f38
Author: Sanjana <[email protected]>
AuthorDate: Tue May 12 23:24:14 2026 +0530

    Optimize artifact lookup in DefaultProjectDependencyAnalyzer (#270)
    
    * Optimize artifact lookup and improve code quality in 
DefaultProjectDependencyAnalyzer
    
    * Use putIfAbsent in buildClassToArtifactMap to preserve first-match 
semantics
    
    The original findArtifactForClassName returned the first matching artifact
    during iteration. Using put() overwrites previous entries, making the last
    artifact win instead. putIfAbsent preserves the first-match-wins behavior,
    preventing incorrect used/unused dependency reporting for shaded/duplicate
    classes.
    
    * Optimize artifact lookup and add unit tests covering duplicate class 
deterministic resolution and JDK exclusion
    
    * fix formating by spotless
    
    ---------
    
    Co-authored-by: Slawomir Jaranowski <[email protected]>
---
 .../analyzer/DefaultProjectDependencyAnalyzer.java |  41 +++---
 .../DefaultProjectDependencyAnalyzerTest.java      | 147 +++++++++++++++++++++
 2 files changed, 168 insertions(+), 20 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 b59bd41..9732771 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
@@ -69,6 +69,7 @@ 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<DependencyUsage> mainDependencyClasses = new HashSet<>();
             for (MainDependencyClassesProvider provider : 
mainDependencyClassesProviders) {
@@ -87,11 +88,12 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
             Set<DependencyUsage> testOnlyDependencyClasses =
                     buildTestOnlyDependencyClasses(mainDependencyClasses, 
testDependencyClasses);
 
-            Map<Artifact, Set<DependencyUsage>> usedArtifacts = 
buildUsedArtifacts(artifactClassMap, dependencyClasses);
-            Set<Artifact> mainUsedArtifacts =
-                    buildUsedArtifacts(artifactClassMap, 
mainDependencyClasses).keySet();
+            Map<Artifact, Set<DependencyUsage>> usedArtifacts =
+                    buildUsedArtifacts(classToArtifactMap, dependencyClasses);
+            Set<Artifact> mainUsedArtifacts = 
buildUsedArtifacts(classToArtifactMap, mainDependencyClasses)
+                    .keySet();
 
-            Set<Artifact> testArtifacts = buildUsedArtifacts(artifactClassMap, 
testOnlyDependencyClasses)
+            Set<Artifact> testArtifacts = 
buildUsedArtifacts(classToArtifactMap, testOnlyDependencyClasses)
                     .keySet();
             Set<Artifact> testOnlyArtifacts = removeAll(testArtifacts, 
mainUsedArtifacts);
 
@@ -124,7 +126,8 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
     }
 
     /**
-     * This method defines a new way to remove the artifacts by using the 
conflict id. We don't care about the version
+     * 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
@@ -156,7 +159,7 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
         Set<Artifact> nonTestScopeArtifacts = new LinkedHashSet<>();
 
         for (Artifact artifact : testOnlyArtifacts) {
-            if (artifact.getScope().equals("compile")) {
+            if (Artifact.SCOPE_COMPILE.equals(artifact.getScope())) {
                 nonTestScopeArtifacts.add(artifact);
             }
         }
@@ -225,20 +228,15 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
         return declaredArtifacts;
     }
 
-    private static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
-            Map<Artifact, Set<String>> artifactClassMap, Set<DependencyUsage> 
dependencyClasses) {
+    static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
+            Map<String, Artifact> classToArtifactMap, Set<DependencyUsage> 
dependencyClasses) {
         Map<Artifact, Set<DependencyUsage>> usedArtifacts = new HashMap<>();
 
         for (DependencyUsage classUsage : dependencyClasses) {
-            Artifact artifact = findArtifactForClassName(artifactClassMap, 
classUsage.getDependencyClass());
+            Artifact artifact = 
classToArtifactMap.get(classUsage.getDependencyClass());
 
             if (artifact != null && !includedInJDK(artifact)) {
-                Set<DependencyUsage> classesFromArtifact = 
usedArtifacts.get(artifact);
-                if (classesFromArtifact == null) {
-                    classesFromArtifact = new HashSet<>();
-                    usedArtifacts.put(artifact, classesFromArtifact);
-                }
-                classesFromArtifact.add(classUsage);
+                usedArtifacts.computeIfAbsent(artifact, k -> new 
HashSet<>()).add(classUsage);
             }
         }
 
@@ -247,7 +245,7 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
 
     // MSHARED-47 an uncommon case where a commonly used
     // third party dependency was added to the JDK
-    private static boolean includedInJDK(Artifact artifact) {
+    static boolean includedInJDK(Artifact artifact) {
         if ("xml-apis".equals(artifact.getGroupId())) {
             if ("xml-apis".equals(artifact.getArtifactId())) {
                 return true;
@@ -260,13 +258,16 @@ public class DefaultProjectDependencyAnalyzer implements 
ProjectDependencyAnalyz
         return false;
     }
 
-    private static Artifact findArtifactForClassName(Map<Artifact, 
Set<String>> artifactClassMap, String className) {
+    static Map<String, Artifact> buildClassToArtifactMap(Map<Artifact, 
Set<String>> artifactClassMap) {
+        Map<String, Artifact> classToArtifactMap = new HashMap<>();
+
         for (Map.Entry<Artifact, Set<String>> entry : 
artifactClassMap.entrySet()) {
-            if (entry.getValue().contains(className)) {
-                return entry.getKey();
+            Artifact artifact = entry.getKey();
+            for (String className : entry.getValue()) {
+                classToArtifactMap.putIfAbsent(className, artifact);
             }
         }
 
-        return null;
+        return classToArtifactMap;
     }
 }
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
new file mode 100644
index 0000000..3ea7fc7
--- /dev/null
+++ 
b/src/test/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.shared.dependency.analyzer;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests <code>DefaultProjectDependencyAnalyzer</code>.
+ *
+ * @see DefaultProjectDependencyAnalyzer
+ */
+class DefaultProjectDependencyAnalyzerTest {
+
+    @Test
+    void testBuildClassToArtifactMap() {
+        Artifact artifact1 = aTestArtifact("artifact1");
+        Artifact artifact2 = aTestArtifact("artifact2");
+
+        Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>();
+        artifactClassMap.put(artifact1, Collections.singleton("class1"));
+        artifactClassMap.put(artifact2, Collections.singleton("class2"));
+
+        Map<String, Artifact> result = 
DefaultProjectDependencyAnalyzer.buildClassToArtifactMap(artifactClassMap);
+
+        assertThat(result).hasSize(2);
+        assertThat(result.get("class1")).isEqualTo(artifact1);
+        assertThat(result.get("class2")).isEqualTo(artifact2);
+    }
+
+    @Test
+    void testBuildClassToArtifactMapWithDuplicates() {
+        Artifact artifact1 = aTestArtifact("artifact1");
+        Artifact artifact2 = aTestArtifact("artifact2");
+
+        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);
+
+        assertThat(result).hasSize(1);
+        // Should favor the first artifact encountered
+        assertThat(result.get("duplicateClass")).isEqualTo(artifact1);
+    }
+
+    @Test
+    void testBuildClassToArtifactMapWithMultipleClasses() {
+        Artifact artifact1 = aTestArtifact("artifact1");
+
+        Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>();
+        artifactClassMap.put(artifact1, new HashSet<>(Arrays.asList("class1", 
"class2")));
+
+        Map<String, Artifact> result = 
DefaultProjectDependencyAnalyzer.buildClassToArtifactMap(artifactClassMap);
+
+        assertThat(result).hasSize(2);
+        assertThat(result.get("class1")).isEqualTo(artifact1);
+        assertThat(result.get("class2")).isEqualTo(artifact1);
+    }
+
+    @Test
+    void testBuildUsedArtifacts() {
+        Artifact artifact1 = aTestArtifact("artifact1");
+        Map<String, Artifact> classToArtifactMap = 
Collections.singletonMap("class1", artifact1);
+        Set<DependencyUsage> dependencyClasses = Collections.singleton(new 
DependencyUsage("class1", "main"));
+
+        Map<Artifact, Set<DependencyUsage>> result =
+                
DefaultProjectDependencyAnalyzer.buildUsedArtifacts(classToArtifactMap, 
dependencyClasses);
+
+        assertThat(result).hasSize(1);
+        assertThat(result.get(artifact1)).hasSize(1);
+        
assertThat(result.get(artifact1).iterator().next().getDependencyClass()).isEqualTo("class1");
+    }
+
+    @Test
+    void testBuildUsedArtifactsWithMultipleClasses() {
+        Artifact artifact1 = aTestArtifact("artifact1");
+        Map<String, Artifact> classToArtifactMap = 
Collections.singletonMap("class1", artifact1);
+        Set<DependencyUsage> dependencyClasses = new HashSet<>(
+                Arrays.asList(new DependencyUsage("class1", "main"), new 
DependencyUsage("class1", "test")));
+
+        Map<Artifact, Set<DependencyUsage>> result =
+                
DefaultProjectDependencyAnalyzer.buildUsedArtifacts(classToArtifactMap, 
dependencyClasses);
+
+        assertThat(result).hasSize(1);
+        assertThat(result.get(artifact1)).hasSize(2);
+    }
+
+    @Test
+    void testBuildUsedArtifactsWithJDKExcluded() {
+        Artifact artifact1 = aTestArtifact("xml-apis", "xml-apis");
+        Map<String, Artifact> classToArtifactMap = 
Collections.singletonMap("class1", artifact1);
+        Set<DependencyUsage> dependencyClasses = Collections.singleton(new 
DependencyUsage("class1", "main"));
+
+        Map<Artifact, Set<DependencyUsage>> result =
+                
DefaultProjectDependencyAnalyzer.buildUsedArtifacts(classToArtifactMap, 
dependencyClasses);
+
+        // Being in JDK, it should be excluded from used artifacts
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    void testIncludedInJDK() {
+        
assertThat(DefaultProjectDependencyAnalyzer.includedInJDK(aTestArtifact("xml-apis",
 "xml-apis")))
+                .isTrue();
+        
assertThat(DefaultProjectDependencyAnalyzer.includedInJDK(aTestArtifact("xerces",
 "xmlParserAPIs")))
+                .isTrue();
+        
assertThat(DefaultProjectDependencyAnalyzer.includedInJDK(aTestArtifact("groupId",
 "artifactId")))
+                .isFalse();
+    }
+
+    private Artifact aTestArtifact(String artifactId) {
+        return aTestArtifact("groupId", artifactId);
+    }
+
+    private Artifact aTestArtifact(String groupId, String artifactId) {
+        return new DefaultArtifact(
+                groupId, artifactId, VersionRange.createFromVersion("1.0"), 
"compile", "jar", "", null);
+    }
+}

Reply via email to