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