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);
+ }
+}