1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.shared.dependency.analyzer;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.net.URL;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedHashMap;
34 import java.util.LinkedHashSet;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.jar.JarEntry;
38 import java.util.jar.JarFile;
39 import java.util.stream.Collectors;
40
41 import org.apache.maven.artifact.Artifact;
42 import org.apache.maven.project.MavenProject;
43
44
45
46
47
48
49 @Named
50 @Singleton
51 public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyzer {
52
53
54
55 @Inject
56 private ClassAnalyzer classAnalyzer;
57
58
59
60
61 @Inject
62 private DependencyAnalyzer dependencyAnalyzer;
63
64
65 @Override
66 public ProjectDependencyAnalysis analyze(MavenProject project, Collection<String> excludedClasses)
67 throws ProjectDependencyAnalyzerException {
68 try {
69 ClassesPatterns excludedClassesPatterns = new ClassesPatterns(excludedClasses);
70 Map<Artifact, Set<String>> artifactClassMap = buildArtifactClassMap(project, excludedClassesPatterns);
71
72 Set<DependencyUsage> mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns);
73 Set<DependencyUsage> testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns);
74
75 Set<DependencyUsage> dependencyClasses = new HashSet<>();
76 dependencyClasses.addAll(mainDependencyClasses);
77 dependencyClasses.addAll(testDependencyClasses);
78
79 Set<DependencyUsage> testOnlyDependencyClasses =
80 buildTestOnlyDependencyClasses(mainDependencyClasses, testDependencyClasses);
81
82 Map<Artifact, Set<DependencyUsage>> usedArtifacts = buildUsedArtifacts(artifactClassMap, dependencyClasses);
83 Set<Artifact> mainUsedArtifacts =
84 buildUsedArtifacts(artifactClassMap, mainDependencyClasses).keySet();
85
86 Set<Artifact> testArtifacts = buildUsedArtifacts(artifactClassMap, testOnlyDependencyClasses)
87 .keySet();
88 Set<Artifact> testOnlyArtifacts = removeAll(testArtifacts, mainUsedArtifacts);
89
90 Set<Artifact> declaredArtifacts = buildDeclaredArtifacts(project);
91 Set<Artifact> usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts);
92 usedDeclaredArtifacts.retainAll(usedArtifacts.keySet());
93
94 Map<Artifact, Set<DependencyUsage>> usedDeclaredArtifactsWithClasses = new LinkedHashMap<>();
95 for (Artifact a : usedDeclaredArtifacts) {
96 usedDeclaredArtifactsWithClasses.put(a, usedArtifacts.get(a));
97 }
98
99 Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>(usedArtifacts);
100 Set<Artifact> usedUndeclaredArtifacts =
101 removeAll(usedUndeclaredArtifactsWithClasses.keySet(), declaredArtifacts);
102 usedUndeclaredArtifactsWithClasses.keySet().retainAll(usedUndeclaredArtifacts);
103
104 Set<Artifact> unusedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts);
105 unusedDeclaredArtifacts = removeAll(unusedDeclaredArtifacts, usedArtifacts.keySet());
106
107 Set<Artifact> testArtifactsWithNonTestScope = getTestArtifactsWithNonTestScope(testOnlyArtifacts);
108
109 return new ProjectDependencyAnalysis(
110 usedDeclaredArtifactsWithClasses, usedUndeclaredArtifactsWithClasses,
111 unusedDeclaredArtifacts, testArtifactsWithNonTestScope);
112 } catch (IOException exception) {
113 throw new ProjectDependencyAnalyzerException("Cannot analyze dependencies", exception);
114 }
115 }
116
117
118
119
120
121
122
123
124
125 private static Set<Artifact> removeAll(Set<Artifact> start, Set<Artifact> remove) {
126 Set<Artifact> results = new LinkedHashSet<>(start.size());
127
128 for (Artifact artifact : start) {
129 boolean found = false;
130
131 for (Artifact artifact2 : remove) {
132 if (artifact.getDependencyConflictId().equals(artifact2.getDependencyConflictId())) {
133 found = true;
134 break;
135 }
136 }
137
138 if (!found) {
139 results.add(artifact);
140 }
141 }
142
143 return results;
144 }
145
146 private static Set<Artifact> getTestArtifactsWithNonTestScope(Set<Artifact> testOnlyArtifacts) {
147 Set<Artifact> nonTestScopeArtifacts = new LinkedHashSet<>();
148
149 for (Artifact artifact : testOnlyArtifacts) {
150 if (artifact.getScope().equals("compile")) {
151 nonTestScopeArtifacts.add(artifact);
152 }
153 }
154
155 return nonTestScopeArtifacts;
156 }
157
158 protected Map<Artifact, Set<String>> buildArtifactClassMap(MavenProject project, ClassesPatterns excludedClasses)
159 throws IOException {
160 Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>();
161
162 Set<Artifact> dependencyArtifacts = project.getArtifacts();
163
164 for (Artifact artifact : dependencyArtifacts) {
165 File file = artifact.getFile();
166
167 if (file != null && file.getName().endsWith(".jar")) {
168
169
170 try (JarFile jarFile = new JarFile(file)) {
171 Enumeration<JarEntry> jarEntries = jarFile.entries();
172
173 Set<String> classes = new HashSet<>();
174
175 while (jarEntries.hasMoreElements()) {
176 String entry = jarEntries.nextElement().getName();
177 if (entry.endsWith(".class")) {
178 String className = entry.replace('/', '.');
179 className = className.substring(0, className.length() - ".class".length());
180 if (!excludedClasses.isMatch(className)) {
181 classes.add(className);
182 }
183 }
184 }
185
186 artifactClassMap.put(artifact, classes);
187 }
188 } else if (file != null && file.isDirectory()) {
189 URL url = file.toURI().toURL();
190 Set<String> classes = classAnalyzer.analyze(url, excludedClasses);
191
192 artifactClassMap.put(artifact, classes);
193 }
194 }
195
196 return artifactClassMap;
197 }
198
199 private static Set<DependencyUsage> buildTestOnlyDependencyClasses(
200 Set<DependencyUsage> mainDependencyClasses, Set<DependencyUsage> testDependencyClasses) {
201 Set<DependencyUsage> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
202 Set<String> mainDepClassNames = mainDependencyClasses.stream()
203 .map(DependencyUsage::getDependencyClass)
204 .collect(Collectors.toSet());
205 testOnlyDependencyClasses.removeIf(u -> mainDepClassNames.contains(u.getDependencyClass()));
206 return testOnlyDependencyClasses;
207 }
208
209 private Set<DependencyUsage> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
210 throws IOException {
211 String outputDirectory = project.getBuild().getOutputDirectory();
212 return buildDependencyClasses(outputDirectory, excludedClasses);
213 }
214
215 private Set<DependencyUsage> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
216 throws IOException {
217 String testOutputDirectory = project.getBuild().getTestOutputDirectory();
218 return buildDependencyClasses(testOutputDirectory, excludedClasses);
219 }
220
221 private Set<DependencyUsage> buildDependencyClasses(String path, ClassesPatterns excludedClasses)
222 throws IOException {
223 URL url = new File(path).toURI().toURL();
224
225 return dependencyAnalyzer.analyzeUsages(url, excludedClasses);
226 }
227
228 private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
229 Set<Artifact> declaredArtifacts = project.getDependencyArtifacts();
230
231 if (declaredArtifacts == null) {
232 declaredArtifacts = Collections.emptySet();
233 }
234
235 return declaredArtifacts;
236 }
237
238 private static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
239 Map<Artifact, Set<String>> artifactClassMap, Set<DependencyUsage> dependencyClasses) {
240 Map<Artifact, Set<DependencyUsage>> usedArtifacts = new HashMap<>();
241
242 for (DependencyUsage classUsage : dependencyClasses) {
243 Artifact artifact = findArtifactForClassName(artifactClassMap, classUsage.getDependencyClass());
244
245 if (artifact != null) {
246 Set<DependencyUsage> classesFromArtifact = usedArtifacts.get(artifact);
247 if (classesFromArtifact == null) {
248 classesFromArtifact = new HashSet<>();
249 usedArtifacts.put(artifact, classesFromArtifact);
250 }
251 classesFromArtifact.add(classUsage);
252 }
253 }
254
255 return usedArtifacts;
256 }
257
258 private static Artifact findArtifactForClassName(Map<Artifact, Set<String>> artifactClassMap, String className) {
259 for (Map.Entry<Artifact, Set<String>> entry : artifactClassMap.entrySet()) {
260 if (entry.getValue().contains(className)) {
261 return entry.getKey();
262 }
263 }
264
265 return null;
266 }
267 }