View Javadoc
1   package org.apache.maven.shared.dependency.analyzer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.LinkedHashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.maven.artifact.Artifact;
32  
33  /**
34   * Project dependencies analysis result.
35   *
36   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
37   */
38  public class ProjectDependencyAnalysis
39  {
40      // fields -----------------------------------------------------------------
41  
42      private final Set<Artifact> usedDeclaredArtifacts;
43  
44      private final Map<Artifact, Set<String>> usedUndeclaredArtifacts;
45  
46      private final Set<Artifact> unusedDeclaredArtifacts;
47  
48      private final Set<Artifact> testArtifactsWithNonTestScope;
49  
50      /**
51       * <p>Constructor for ProjectDependencyAnalysis.</p>
52       */
53      public ProjectDependencyAnalysis()
54      {
55          this( null, (Map<Artifact, Set<String>>) null, null, null );
56      }
57  
58      /**
59       * <p>Constructor for ProjectDependencyAnalysis to maintain compatibility with old API</p>
60       *
61       * @param usedDeclaredArtifacts artifacts both used and declared
62       * @param usedUndeclaredArtifacts artifacts used but not declared
63       * @param unusedDeclaredArtifacts artifacts declared but not used
64       */
65      public ProjectDependencyAnalysis( Set<Artifact> usedDeclaredArtifacts, Set<Artifact> usedUndeclaredArtifacts,
66                                        Set<Artifact> unusedDeclaredArtifacts )
67      {
68          this( usedDeclaredArtifacts, usedUndeclaredArtifacts,
69                  unusedDeclaredArtifacts, Collections.<Artifact>emptySet() );
70      }
71  
72      /**
73       * <p>Constructor for ProjectDependencyAnalysis.</p>
74       *
75       * @param usedDeclaredArtifacts artifacts both used and declared
76       * @param usedUndeclaredArtifacts artifacts used but not declared
77       * @param unusedDeclaredArtifacts artifacts declared but not used
78       * @param testArtifactsWithNonTestScope artifacts only used in tests but not declared with test scope
79       */
80      public ProjectDependencyAnalysis( Set<Artifact> usedDeclaredArtifacts, Set<Artifact> usedUndeclaredArtifacts,
81                                        Set<Artifact> unusedDeclaredArtifacts,
82                                        Set<Artifact> testArtifactsWithNonTestScope )
83      {
84          this( usedDeclaredArtifacts,
85                  mapWithKeys( usedUndeclaredArtifacts ),
86                  unusedDeclaredArtifacts,
87                  testArtifactsWithNonTestScope );
88      }
89  
90      public ProjectDependencyAnalysis( Set<Artifact> usedDeclaredArtifacts,
91              Map<Artifact, Set<String>> usedUndeclaredArtifacts,
92              Set<Artifact> unusedDeclaredArtifacts,
93              Set<Artifact> testArtifactsWithNonTestScope )
94      {
95          this.usedDeclaredArtifacts = safeCopy( usedDeclaredArtifacts );
96          this.usedUndeclaredArtifacts = safeCopy( usedUndeclaredArtifacts );
97          this.unusedDeclaredArtifacts = safeCopy( unusedDeclaredArtifacts );
98          this.testArtifactsWithNonTestScope = safeCopy( testArtifactsWithNonTestScope );
99      }
100 
101     /**
102      * Returns artifacts both used and declared.
103      *
104      * @return artifacts both used and declared
105      */
106     public Set<Artifact> getUsedDeclaredArtifacts()
107     {
108         return safeCopy( usedDeclaredArtifacts );
109     }
110 
111     /**
112      * Returns artifacts used but not declared.
113      *
114      * @return artifacts used but not declared
115      */
116     public Set<Artifact> getUsedUndeclaredArtifacts()
117     {
118         return safeCopy( usedUndeclaredArtifacts.keySet() );
119     }
120 
121     /**
122      * Returns artifacts used but not declared.
123      *
124      * @return artifacts used but not declared
125      */
126     public Map<Artifact, Set<String>> getUsedUndeclaredArtifactsWithClasses()
127     {
128         return safeCopy( usedUndeclaredArtifacts );
129     }
130 
131     /**
132      * Returns artifacts declared but not used.
133      *
134      * @return artifacts declared but not used
135      */
136     public Set<Artifact> getUnusedDeclaredArtifacts()
137     {
138         return safeCopy( unusedDeclaredArtifacts );
139     }
140 
141     /**
142      * Returns artifacts only used in tests but not declared with test scope.
143      *
144      * @return  artifacts only used in tests but not declared with test scope
145      */
146     public Set<Artifact> getTestArtifactsWithNonTestScope()
147     {
148         return safeCopy( testArtifactsWithNonTestScope );
149     }
150 
151     /**
152      * Filter non-compile scoped artifacts from unused declared.
153      *
154      * @return updated project dependency analysis
155      * @since 1.3
156      */
157     public ProjectDependencyAnalysis ignoreNonCompile()
158     {
159         Set<Artifact> filteredUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts );
160         filteredUnusedDeclared.removeIf( artifact -> !artifact.getScope().equals( Artifact.SCOPE_COMPILE ) );
161 
162         return new ProjectDependencyAnalysis( usedDeclaredArtifacts, usedUndeclaredArtifacts, filteredUnusedDeclared,
163                 testArtifactsWithNonTestScope );
164     }
165 
166     /**
167      * Force use status of some declared dependencies, to manually fix consequences of bytecode-level analysis which
168      * happens to not detect some effective use (constants, annotation with source-retention, javadoc).
169      *
170      * @param forceUsedDependencies dependencies to move from "unused-declared" to "used-declared", with
171      *                              <code>groupId:artifactId</code> format
172      * @return updated project dependency analysis
173      * @throws ProjectDependencyAnalyzerException if dependencies forced were either not declared or already detected as
174      *                                            used
175      * @since 1.3
176      */
177     @SuppressWarnings( "UnusedReturnValue" )
178     public ProjectDependencyAnalysis forceDeclaredDependenciesUsage( String[] forceUsedDependencies )
179         throws ProjectDependencyAnalyzerException
180     {
181         Set<String> forced = new HashSet<>( Arrays.asList( forceUsedDependencies ) );
182 
183         Set<Artifact> forcedUnusedDeclared = new HashSet<>( unusedDeclaredArtifacts );
184         Set<Artifact> forcedUsedDeclared = new HashSet<>( usedDeclaredArtifacts );
185 
186         Iterator<Artifact> iter = forcedUnusedDeclared.iterator();
187         while ( iter.hasNext() )
188         {
189             Artifact artifact = iter.next();
190 
191             if ( forced.remove( artifact.getGroupId() + ':' + artifact.getArtifactId() ) )
192             {
193                 // ok, change artifact status from unused-declared to used-declared
194                 iter.remove();
195                 forcedUsedDeclared.add( artifact );
196             }
197         }
198 
199         if ( !forced.isEmpty() )
200         {
201             // trying to force dependencies as used-declared which were not declared or already detected as used
202             Set<String> used = new HashSet<>();
203             for ( Artifact artifact : usedDeclaredArtifacts )
204             {
205                 String id = artifact.getGroupId() + ':' + artifact.getArtifactId();
206                 if ( forced.remove( id ) )
207                 {
208                     used.add( id );
209                 }
210             }
211 
212             StringBuilder builder = new StringBuilder();
213             if ( !forced.isEmpty() )
214             {
215                 builder.append( "not declared: " ).append( forced );
216             }
217             if ( !used.isEmpty() )
218             {
219                 if ( builder.length() > 0 )
220                 {
221                     builder.append( " and " );
222                 }
223                 builder.append( "declared but already detected as used: " ).append( used );
224             }
225             throw new ProjectDependencyAnalyzerException( "Trying to force use of dependencies which are " + builder );
226         }
227 
228         return new ProjectDependencyAnalysis( forcedUsedDeclared, usedUndeclaredArtifacts, forcedUnusedDeclared,
229                 testArtifactsWithNonTestScope );
230     }
231 
232     /**
233      * <p>hashCode.</p>
234      *
235      * @return an int
236      */
237     public int hashCode()
238     {
239         int hashCode = getUsedDeclaredArtifacts().hashCode();
240         hashCode = ( hashCode * 37 ) + getUsedUndeclaredArtifacts().hashCode();
241         hashCode = ( hashCode * 37 ) + getUnusedDeclaredArtifacts().hashCode();
242         hashCode = ( hashCode * 37 ) + getTestArtifactsWithNonTestScope().hashCode();
243 
244         return hashCode;
245     }
246 
247     /** {@inheritDoc} */
248     public boolean equals( Object object )
249     {
250         if ( object instanceof ProjectDependencyAnalysis )
251         {
252             ProjectDependencyAnalysis analysis = (ProjectDependencyAnalysis) object;
253 
254             return getUsedDeclaredArtifacts().equals( analysis.getUsedDeclaredArtifacts() )
255                 && getUsedUndeclaredArtifacts().equals( analysis.getUsedUndeclaredArtifacts() )
256                 && getUnusedDeclaredArtifacts().equals( analysis.getUnusedDeclaredArtifacts() )
257                 && getTestArtifactsWithNonTestScope().equals( analysis.getTestArtifactsWithNonTestScope() );
258         }
259 
260         return false;
261     }
262 
263     /**
264      * <p>toString.</p>
265      *
266      * @return a {@link java.lang.String} object.
267      */
268     public String toString()
269     {
270         StringBuilder buffer = new StringBuilder();
271 
272         if ( !getUsedDeclaredArtifacts().isEmpty() )
273         {
274             buffer.append( "usedDeclaredArtifacts=" ).append( getUsedDeclaredArtifacts() );
275         }
276 
277         if ( !getUsedUndeclaredArtifacts().isEmpty() )
278         {
279             if ( buffer.length() > 0 )
280             {
281                 buffer.append( "," );
282             }
283 
284             buffer.append( "usedUndeclaredArtifacts=" ).append( getUsedUndeclaredArtifacts() );
285         }
286 
287         if ( !getUnusedDeclaredArtifacts().isEmpty() )
288         {
289             if ( buffer.length() > 0 )
290             {
291                 buffer.append( "," );
292             }
293 
294             buffer.append( "unusedDeclaredArtifacts=" ).append( getUnusedDeclaredArtifacts() );
295         }
296 
297         if ( !getTestArtifactsWithNonTestScope().isEmpty() )
298         {
299             if ( buffer.length() > 0 )
300             {
301                 buffer.append( "," );
302             }
303 
304             buffer.append( "testArtifactsWithNonTestScope=" ).append( getTestArtifactsWithNonTestScope() );
305         }
306 
307         buffer.insert( 0, "[" );
308         buffer.insert( 0, getClass().getName() );
309 
310         buffer.append( "]" );
311 
312         return buffer.toString();
313     }
314 
315     // private methods --------------------------------------------------------
316 
317     private Set<Artifact> safeCopy( Set<Artifact> set )
318     {
319         return ( set == null ) ? Collections.emptySet() : Collections.unmodifiableSet( new LinkedHashSet<>( set ) );
320     }
321 
322     private static Map<Artifact, Set<String>> safeCopy( Map<Artifact, Set<String>> origMap )
323     {
324         if ( origMap == null )
325         {
326             return Collections.emptyMap();
327         }
328 
329         Map<Artifact, Set<String>> map = new HashMap<>();
330 
331         for ( Map.Entry<Artifact, Set<String>> e : origMap.entrySet() )
332         {
333             map.put( e.getKey(), Collections.unmodifiableSet( new LinkedHashSet<>( e.getValue() ) ) );
334         }
335 
336         return map;
337     }
338 
339     private static Map<Artifact, Set<String>> mapWithKeys( Set<Artifact> keys )
340     {
341         if ( keys == null )
342         {
343             return Collections.emptyMap();
344         }
345 
346         Map<Artifact, Set<String>> map = new HashMap<>();
347 
348         for ( Artifact k : keys )
349         {
350             map.put( k, Collections.<String>emptySet() );
351         }
352 
353         return map;
354     }
355 }