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