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