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
103 usedUndeclaredArtifactsWithClasses.keySet().retainAll(usedUndeclaredArtifacts);
104
105 Set<Artifact> unusedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts);
106 unusedDeclaredArtifacts = removeAll(unusedDeclaredArtifacts, usedArtifacts.keySet());
107
108 Set<Artifact> testArtifactsWithNonTestScope = getTestArtifactsWithNonTestScope(testOnlyArtifacts);
109
110 return new ProjectDependencyAnalysis(
111 usedDeclaredArtifactsWithClasses, usedUndeclaredArtifactsWithClasses,
112 unusedDeclaredArtifacts, testArtifactsWithNonTestScope);
113 } catch (IOException exception) {
114 throw new ProjectDependencyAnalyzerException("Cannot analyze dependencies", exception);
115 }
116 }
117
118
119
120
121
122
123
124
125
126 private static Set<Artifact> removeAll(Set<Artifact> start, Set<Artifact> remove) {
127 Set<Artifact> results = new LinkedHashSet<>(start.size());
128
129 for (Artifact artifact : start) {
130 boolean found = false;
131
132 for (Artifact artifact2 : remove) {
133 if (artifact.getDependencyConflictId().equals(artifact2.getDependencyConflictId())) {
134 found = true;
135 break;
136 }
137 }
138
139 if (!found) {
140 results.add(artifact);
141 }
142 }
143
144 return results;
145 }
146
147 private static Set<Artifact> getTestArtifactsWithNonTestScope(Set<Artifact> testOnlyArtifacts) {
148 Set<Artifact> nonTestScopeArtifacts = new LinkedHashSet<>();
149
150 for (Artifact artifact : testOnlyArtifacts) {
151 if (artifact.getScope().equals("compile")) {
152 nonTestScopeArtifacts.add(artifact);
153 }
154 }
155
156 return nonTestScopeArtifacts;
157 }
158
159 protected Map<Artifact, Set<String>> buildArtifactClassMap(MavenProject project, ClassesPatterns excludedClasses)
160 throws IOException {
161 Map<Artifact, Set<String>> artifactClassMap = new LinkedHashMap<>();
162
163 Set<Artifact> dependencyArtifacts = project.getArtifacts();
164
165 for (Artifact artifact : dependencyArtifacts) {
166 File file = artifact.getFile();
167
168 if (file != null && file.getName().endsWith(".jar")) {
169
170
171 try (JarFile jarFile = new JarFile(file)) {
172 Enumeration<JarEntry> jarEntries = jarFile.entries();
173
174 Set<String> classes = new HashSet<>();
175
176 while (jarEntries.hasMoreElements()) {
177 String entry = jarEntries.nextElement().getName();
178 if (entry.endsWith(".class")) {
179 String className = entry.replace('/', '.');
180 className = className.substring(0, className.length() - ".class".length());
181 if (!excludedClasses.isMatch(className)) {
182 classes.add(className);
183 }
184 }
185 }
186
187 artifactClassMap.put(artifact, classes);
188 }
189 } else if (file != null && file.isDirectory()) {
190 URL url = file.toURI().toURL();
191 Set<String> classes = classAnalyzer.analyze(url, excludedClasses);
192
193 artifactClassMap.put(artifact, classes);
194 }
195 }
196
197 return artifactClassMap;
198 }
199
200 private static Set<DependencyUsage> buildTestOnlyDependencyClasses(
201 Set<DependencyUsage> mainDependencyClasses, Set<DependencyUsage> testDependencyClasses) {
202 Set<DependencyUsage> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
203 Set<String> mainDepClassNames = mainDependencyClasses.stream()
204 .map(DependencyUsage::getDependencyClass)
205 .collect(Collectors.toSet());
206 testOnlyDependencyClasses.removeIf(u -> mainDepClassNames.contains(u.getDependencyClass()));
207 return testOnlyDependencyClasses;
208 }
209
210 private Set<DependencyUsage> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
211 throws IOException {
212 String outputDirectory = project.getBuild().getOutputDirectory();
213 return buildDependencyClasses(outputDirectory, excludedClasses);
214 }
215
216 private Set<DependencyUsage> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
217 throws IOException {
218 String testOutputDirectory = project.getBuild().getTestOutputDirectory();
219 return buildDependencyClasses(testOutputDirectory, excludedClasses);
220 }
221
222 private Set<DependencyUsage> buildDependencyClasses(String path, ClassesPatterns excludedClasses)
223 throws IOException {
224 URL url = new File(path).toURI().toURL();
225
226 return dependencyAnalyzer.analyzeUsages(url, excludedClasses);
227 }
228
229 private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
230 Set<Artifact> declaredArtifacts = project.getDependencyArtifacts();
231
232 if (declaredArtifacts == null) {
233 declaredArtifacts = Collections.emptySet();
234 }
235
236 return declaredArtifacts;
237 }
238
239 private static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
240 Map<Artifact, Set<String>> artifactClassMap, Set<DependencyUsage> dependencyClasses) {
241 Map<Artifact, Set<DependencyUsage>> usedArtifacts = new HashMap<>();
242
243 for (DependencyUsage classUsage : dependencyClasses) {
244 Artifact artifact = findArtifactForClassName(artifactClassMap, classUsage.getDependencyClass());
245
246 if (artifact != null && !includedInJDK(artifact)) {
247 Set<DependencyUsage> classesFromArtifact = usedArtifacts.get(artifact);
248 if (classesFromArtifact == null) {
249 classesFromArtifact = new HashSet<>();
250 usedArtifacts.put(artifact, classesFromArtifact);
251 }
252 classesFromArtifact.add(classUsage);
253 }
254 }
255
256 return usedArtifacts;
257 }
258
259
260
261 private static boolean includedInJDK(Artifact artifact) {
262 if ("xml-apis".equals(artifact.getGroupId())) {
263 if ("xml-apis".equals(artifact.getArtifactId())) {
264 return true;
265 }
266 } else if ("xerces".equals(artifact.getGroupId())) {
267 if ("xmlParserAPIs".equals(artifact.getArtifactId())) {
268 return true;
269 }
270 }
271 return false;
272 }
273
274 private static Artifact findArtifactForClassName(Map<Artifact, Set<String>> artifactClassMap, String className) {
275 for (Map.Entry<Artifact, Set<String>> entry : artifactClassMap.entrySet()) {
276 if (entry.getValue().contains(className)) {
277 return entry.getKey();
278 }
279 }
280
281 return null;
282 }
283 }