Skip to content

Commit 064fa0e

Browse files
authored
[MSHARED-1407] Track dependency usage by referencing classes (#125)
1 parent 13e10ad commit 064fa0e

File tree

13 files changed

+273
-96
lines changed

13 files changed

+273
-96
lines changed

src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.Set;
3737
import java.util.jar.JarEntry;
3838
import java.util.jar.JarFile;
39+
import java.util.stream.Collectors;
3940

4041
import org.apache.maven.artifact.Artifact;
4142
import org.apache.maven.project.MavenProject;
@@ -68,17 +69,17 @@ public ProjectDependencyAnalysis analyze(MavenProject project, Collection<String
6869
ClassesPatterns excludedClassesPatterns = new ClassesPatterns(excludedClasses);
6970
Map<Artifact, Set<String>> artifactClassMap = buildArtifactClassMap(project, excludedClassesPatterns);
7071

71-
Set<String> mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns);
72-
Set<String> testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns);
72+
Set<DependencyUsage> mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns);
73+
Set<DependencyUsage> testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns);
7374

74-
Set<String> dependencyClasses = new HashSet<>();
75+
Set<DependencyUsage> dependencyClasses = new HashSet<>();
7576
dependencyClasses.addAll(mainDependencyClasses);
7677
dependencyClasses.addAll(testDependencyClasses);
7778

78-
Set<String> testOnlyDependencyClasses =
79+
Set<DependencyUsage> testOnlyDependencyClasses =
7980
buildTestOnlyDependencyClasses(mainDependencyClasses, testDependencyClasses);
8081

81-
Map<Artifact, Set<String>> usedArtifacts = buildUsedArtifacts(artifactClassMap, dependencyClasses);
82+
Map<Artifact, Set<DependencyUsage>> usedArtifacts = buildUsedArtifacts(artifactClassMap, dependencyClasses);
8283
Set<Artifact> mainUsedArtifacts =
8384
buildUsedArtifacts(artifactClassMap, mainDependencyClasses).keySet();
8485

@@ -90,7 +91,7 @@ public ProjectDependencyAnalysis analyze(MavenProject project, Collection<String
9091
Set<Artifact> usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts);
9192
usedDeclaredArtifacts.retainAll(usedArtifacts.keySet());
9293

93-
Map<Artifact, Set<String>> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>(usedArtifacts);
94+
Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>(usedArtifacts);
9495
Set<Artifact> usedUndeclaredArtifacts =
9596
removeAll(usedUndeclaredArtifactsWithClasses.keySet(), declaredArtifacts);
9697
usedUndeclaredArtifactsWithClasses.keySet().retainAll(usedUndeclaredArtifacts);
@@ -190,29 +191,33 @@ private Map<Artifact, Set<String>> buildArtifactClassMap(MavenProject project, C
190191
return artifactClassMap;
191192
}
192193

193-
private static Set<String> buildTestOnlyDependencyClasses(
194-
Set<String> mainDependencyClasses, Set<String> testDependencyClasses) {
195-
Set<String> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
196-
testOnlyDependencyClasses.removeAll(mainDependencyClasses);
194+
private static Set<DependencyUsage> buildTestOnlyDependencyClasses(
195+
Set<DependencyUsage> mainDependencyClasses, Set<DependencyUsage> testDependencyClasses) {
196+
Set<DependencyUsage> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
197+
Set<String> mainDepClassNames = mainDependencyClasses.stream()
198+
.map(DependencyUsage::getDependencyClass)
199+
.collect(Collectors.toSet());
200+
testOnlyDependencyClasses.removeIf(u -> mainDepClassNames.contains(u.getDependencyClass()));
197201
return testOnlyDependencyClasses;
198202
}
199203

200-
private Set<String> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
204+
private Set<DependencyUsage> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
201205
throws IOException {
202206
String outputDirectory = project.getBuild().getOutputDirectory();
203207
return buildDependencyClasses(outputDirectory, excludedClasses);
204208
}
205209

206-
private Set<String> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
210+
private Set<DependencyUsage> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
207211
throws IOException {
208212
String testOutputDirectory = project.getBuild().getTestOutputDirectory();
209213
return buildDependencyClasses(testOutputDirectory, excludedClasses);
210214
}
211215

212-
private Set<String> buildDependencyClasses(String path, ClassesPatterns excludedClasses) throws IOException {
216+
private Set<DependencyUsage> buildDependencyClasses(String path, ClassesPatterns excludedClasses)
217+
throws IOException {
213218
URL url = new File(path).toURI().toURL();
214219

215-
return dependencyAnalyzer.analyze(url, excludedClasses);
220+
return dependencyAnalyzer.analyzeUsages(url, excludedClasses);
216221
}
217222

218223
private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
@@ -225,20 +230,20 @@ private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
225230
return declaredArtifacts;
226231
}
227232

228-
private static Map<Artifact, Set<String>> buildUsedArtifacts(
229-
Map<Artifact, Set<String>> artifactClassMap, Set<String> dependencyClasses) {
230-
Map<Artifact, Set<String>> usedArtifacts = new HashMap<>();
233+
private static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
234+
Map<Artifact, Set<String>> artifactClassMap, Set<DependencyUsage> dependencyClasses) {
235+
Map<Artifact, Set<DependencyUsage>> usedArtifacts = new HashMap<>();
231236

232-
for (String className : dependencyClasses) {
233-
Artifact artifact = findArtifactForClassName(artifactClassMap, className);
237+
for (DependencyUsage classUsage : dependencyClasses) {
238+
Artifact artifact = findArtifactForClassName(artifactClassMap, classUsage.getDependencyClass());
234239

235240
if (artifact != null) {
236-
Set<String> classesFromArtifact = usedArtifacts.get(artifact);
241+
Set<DependencyUsage> classesFromArtifact = usedArtifacts.get(artifact);
237242
if (classesFromArtifact == null) {
238243
classesFromArtifact = new HashSet<>();
239244
usedArtifacts.put(artifact, classesFromArtifact);
240245
}
241-
classesFromArtifact.add(className);
246+
classesFromArtifact.add(classUsage);
242247
}
243248
}
244249

src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.IOException;
2222
import java.net.URL;
2323
import java.util.Set;
24+
import java.util.stream.Collectors;
2425

2526
/**
2627
* Gets the set of classes referenced by a library given either as a jar file or an exploded directory.
@@ -48,5 +49,19 @@ default Set<String> analyze(URL url) throws IOException {
4849
* @return the set of class names referenced by the library
4950
* @throws IOException if an error occurs reading a JAR or .class file
5051
*/
51-
Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException;
52+
default Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException {
53+
return analyzeUsages(url, excludeClasses).stream()
54+
.map(DependencyUsage::getDependencyClass)
55+
.collect(Collectors.toSet());
56+
}
57+
58+
/**
59+
* <p>analyzeUsages.</p>
60+
*
61+
* @param url the JAR file or directory to analyze
62+
* @return the set of class names referenced by the library, paired with the
63+
* classes declaring those references.
64+
* @throws IOException if an error occurs reading a JAR or .class file
65+
*/
66+
Set<DependencyUsage> analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException;
5267
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.shared.dependency.analyzer;
20+
21+
/**
22+
* Usage of a dependency class by a project class.
23+
*
24+
* @author <a href="mailto:[email protected]">Jonathan Haber</a>
25+
*/
26+
public class DependencyUsage {
27+
28+
private final String dependencyClass;
29+
30+
private final String usedBy;
31+
32+
public DependencyUsage(String dependencyClass, String usedBy) {
33+
this.dependencyClass = dependencyClass;
34+
this.usedBy = usedBy;
35+
}
36+
37+
/**
38+
* @return the dependency class used by the project class
39+
*/
40+
public String getDependencyClass() {
41+
return dependencyClass;
42+
}
43+
44+
/**
45+
* @return the project class using the dependency class
46+
*/
47+
public String getUsedBy() {
48+
return usedBy;
49+
}
50+
51+
/*
52+
* @see java.lang.Object#hashCode()
53+
*/
54+
public int hashCode() {
55+
int hashCode = dependencyClass.hashCode();
56+
hashCode = (hashCode * 37) + usedBy.hashCode();
57+
58+
return hashCode;
59+
}
60+
61+
/*
62+
* @see java.lang.Object#equals(java.lang.Object)
63+
*/
64+
public boolean equals(Object object) {
65+
if (object instanceof DependencyUsage) {
66+
DependencyUsage usage = (DependencyUsage) object;
67+
68+
return getDependencyClass().equals(usage.getDependencyClass())
69+
&& getUsedBy().equals(usage.getUsedBy());
70+
}
71+
72+
return false;
73+
}
74+
75+
/*
76+
* @see java.lang.Object#toString()
77+
*/
78+
public String toString() {
79+
StringBuilder buffer = new StringBuilder();
80+
81+
buffer.append("dependencyClass=").append(getDependencyClass());
82+
buffer.append(",");
83+
buffer.append("usedBy=").append(getUsedBy());
84+
85+
buffer.insert(0, "[");
86+
buffer.insert(0, getClass().getName());
87+
88+
buffer.append("]");
89+
90+
return buffer.toString();
91+
}
92+
}

src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.LinkedHashSet;
2727
import java.util.Map;
2828
import java.util.Set;
29+
import java.util.stream.Collectors;
2930

3031
import org.apache.maven.artifact.Artifact;
3132

@@ -39,7 +40,7 @@ public class ProjectDependencyAnalysis {
3940

4041
private final Set<Artifact> usedDeclaredArtifacts;
4142

42-
private final Map<Artifact, Set<String>> usedUndeclaredArtifacts;
43+
private final Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifacts;
4344

4445
private final Set<Artifact> unusedDeclaredArtifacts;
4546

@@ -49,7 +50,7 @@ public class ProjectDependencyAnalysis {
4950
* <p>Constructor for ProjectDependencyAnalysis.</p>
5051
*/
5152
public ProjectDependencyAnalysis() {
52-
this(null, (Map<Artifact, Set<String>>) null, null, null);
53+
this(null, (Map<Artifact, Set<DependencyUsage>>) null, null, null);
5354
}
5455

5556
/**
@@ -88,7 +89,7 @@ public ProjectDependencyAnalysis(
8889

8990
public ProjectDependencyAnalysis(
9091
Set<Artifact> usedDeclaredArtifacts,
91-
Map<Artifact, Set<String>> usedUndeclaredArtifacts,
92+
Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifacts,
9293
Set<Artifact> unusedDeclaredArtifacts,
9394
Set<Artifact> testArtifactsWithNonTestScope) {
9495
this.usedDeclaredArtifacts = safeCopy(usedDeclaredArtifacts);
@@ -121,6 +122,20 @@ public Set<Artifact> getUsedUndeclaredArtifacts() {
121122
* @return artifacts used but not declared
122123
*/
123124
public Map<Artifact, Set<String>> getUsedUndeclaredArtifactsWithClasses() {
125+
Map<Artifact, Set<String>> usedUndeclaredArtifactsWithClasses = new HashMap<>();
126+
127+
for (Map.Entry<Artifact, Set<DependencyUsage>> entry : usedUndeclaredArtifacts.entrySet()) {
128+
usedUndeclaredArtifactsWithClasses.put(
129+
entry.getKey(),
130+
entry.getValue().stream()
131+
.map(DependencyUsage::getDependencyClass)
132+
.collect(Collectors.toSet()));
133+
}
134+
135+
return usedUndeclaredArtifactsWithClasses;
136+
}
137+
138+
public Map<Artifact, Set<DependencyUsage>> getUsedUndeclaredArtifactsWithUsages() {
124139
return safeCopy(usedUndeclaredArtifacts);
125140
}
126141

@@ -294,29 +309,29 @@ private Set<Artifact> safeCopy(Set<Artifact> set) {
294309
return (set == null) ? Collections.emptySet() : Collections.unmodifiableSet(new LinkedHashSet<>(set));
295310
}
296311

297-
private static Map<Artifact, Set<String>> safeCopy(Map<Artifact, Set<String>> origMap) {
312+
private static Map<Artifact, Set<DependencyUsage>> safeCopy(Map<Artifact, Set<DependencyUsage>> origMap) {
298313
if (origMap == null) {
299314
return Collections.emptyMap();
300315
}
301316

302-
Map<Artifact, Set<String>> map = new HashMap<>();
317+
Map<Artifact, Set<DependencyUsage>> map = new HashMap<>();
303318

304-
for (Map.Entry<Artifact, Set<String>> e : origMap.entrySet()) {
319+
for (Map.Entry<Artifact, Set<DependencyUsage>> e : origMap.entrySet()) {
305320
map.put(e.getKey(), Collections.unmodifiableSet(new LinkedHashSet<>(e.getValue())));
306321
}
307322

308323
return map;
309324
}
310325

311-
private static Map<Artifact, Set<String>> mapWithKeys(Set<Artifact> keys) {
326+
private static Map<Artifact, Set<DependencyUsage>> mapWithKeys(Set<Artifact> keys) {
312327
if (keys == null) {
313328
return Collections.emptyMap();
314329
}
315330

316-
Map<Artifact, Set<String>> map = new HashMap<>();
331+
Map<Artifact, Set<DependencyUsage>> map = new HashMap<>();
317332

318333
for (Artifact k : keys) {
319-
map.put(k, Collections.<String>emptySet());
334+
map.put(k, Collections.<DependencyUsage>emptySet());
320335
}
321336

322337
return map;

src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.maven.shared.dependency.analyzer.ClassFileVisitorUtils;
2929
import org.apache.maven.shared.dependency.analyzer.ClassesPatterns;
3030
import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer;
31+
import org.apache.maven.shared.dependency.analyzer.DependencyUsage;
3132

3233
/**
3334
* ASMDependencyAnalyzer
@@ -39,11 +40,11 @@
3940
public class ASMDependencyAnalyzer implements DependencyAnalyzer {
4041

4142
@Override
42-
public Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException {
43+
public Set<DependencyUsage> analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException {
4344
DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(excludeClasses);
4445

4546
ClassFileVisitorUtils.accept(url, visitor);
4647

47-
return visitor.getDependencies();
48+
return visitor.getDependencyUsages();
4849
}
4950
}

src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultAnnotationVisitor.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,37 @@
3131
public class DefaultAnnotationVisitor extends AnnotationVisitor {
3232
private final ResultCollector resultCollector;
3333

34+
private final String usedByClass;
35+
3436
/**
3537
* <p>Constructor for DefaultAnnotationVisitor.</p>
3638
*
3739
* @param resultCollector a {@link org.apache.maven.shared.dependency.analyzer.asm.ResultCollector} object.
3840
*/
39-
public DefaultAnnotationVisitor(ResultCollector resultCollector) {
41+
public DefaultAnnotationVisitor(ResultCollector resultCollector, String usedByClass) {
4042
super(Opcodes.ASM9);
4143
this.resultCollector = resultCollector;
44+
this.usedByClass = usedByClass;
4245
}
4346

4447
/** {@inheritDoc} */
4548
@Override
4649
public void visit(final String name, final Object value) {
4750
if (value instanceof Type) {
48-
resultCollector.addType((Type) value);
51+
resultCollector.addType(usedByClass, (Type) value);
4952
}
5053
}
5154

5255
/** {@inheritDoc} */
5356
@Override
5457
public void visitEnum(final String name, final String desc, final String value) {
55-
resultCollector.addDesc(desc);
58+
resultCollector.addDesc(usedByClass, desc);
5659
}
5760

5861
/** {@inheritDoc} */
5962
@Override
6063
public AnnotationVisitor visitAnnotation(final String name, final String desc) {
61-
resultCollector.addDesc(desc);
64+
resultCollector.addDesc(usedByClass, desc);
6265

6366
return this;
6467
}

0 commit comments

Comments
 (0)