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