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