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     @SuppressWarnings("UnusedReturnValue")
196     public ProjectDependencyAnalysis forceDeclaredDependenciesUsage(String[] forceUsedDependencies)
197             throws ProjectDependencyAnalyzerException {
198         Set<String> forced = new HashSet<>(Arrays.asList(forceUsedDependencies));
199 
200         Set<Artifact> forcedUnusedDeclared = new HashSet<>(unusedDeclaredArtifacts);
201         Set<Artifact> forcedUsedDeclared = new HashSet<>(usedDeclaredArtifacts.keySet());
202 
203         Iterator<Artifact> iter = forcedUnusedDeclared.iterator();
204         while (iter.hasNext()) {
205             Artifact artifact = iter.next();
206 
207             if (forced.remove(artifact.getGroupId() + ':' + artifact.getArtifactId())) {
208                 // ok, change artifact status from unused-declared to used-declared
209                 iter.remove();
210                 forcedUsedDeclared.add(artifact);
211             }
212         }
213 
214         if (!forced.isEmpty()) {
215             // trying to force dependencies as used-declared which were not declared or already detected as used
216             Set<String> used = new HashSet<>();
217             for (Artifact artifact : usedDeclaredArtifacts.keySet()) {
218                 String id = artifact.getGroupId() + ':' + artifact.getArtifactId();
219                 if (forced.remove(id)) {
220                     used.add(id);
221                 }
222             }
223 
224             StringBuilder builder = new StringBuilder();
225             if (!forced.isEmpty()) {
226                 builder.append("not declared: ").append(forced);
227             }
228             if (!used.isEmpty()) {
229                 if (builder.length() > 0) {
230                     builder.append(" and ");
231                 }
232                 builder.append("declared but already detected as used: ").append(used);
233             }
234             throw new ProjectDependencyAnalyzerException("Trying to force use of dependencies which are " + builder);
235         }
236 
237         return new ProjectDependencyAnalysis(
238                 mapWithKeys(forcedUsedDeclared),
239                 usedUndeclaredArtifacts,
240                 forcedUnusedDeclared,
241                 testArtifactsWithNonTestScope);
242     }
243 
244     /**
245      * <p>hashCode.</p>
246      *
247      * @return an int
248      */
249     @Override
250     public int hashCode() {
251         int hashCode = getUsedDeclaredArtifacts().hashCode();
252         hashCode = (hashCode * 37) + getUsedUndeclaredArtifacts().hashCode();
253         hashCode = (hashCode * 37) + getUnusedDeclaredArtifacts().hashCode();
254         hashCode = (hashCode * 37) + getTestArtifactsWithNonTestScope().hashCode();
255 
256         return hashCode;
257     }
258 
259     /** {@inheritDoc} */
260     @Override
261     public boolean equals(Object object) {
262         if (object instanceof ProjectDependencyAnalysis) {
263             ProjectDependencyAnalysis analysis = (ProjectDependencyAnalysis) object;
264 
265             return getUsedDeclaredArtifacts().equals(analysis.getUsedDeclaredArtifacts())
266                     && getUsedUndeclaredArtifacts().equals(analysis.getUsedUndeclaredArtifacts())
267                     && getUnusedDeclaredArtifacts().equals(analysis.getUnusedDeclaredArtifacts())
268                     && getTestArtifactsWithNonTestScope().equals(analysis.getTestArtifactsWithNonTestScope());
269         }
270 
271         return false;
272     }
273 
274     /**
275      * <p>toString.</p>
276      *
277      * @return a {@link java.lang.String} object.
278      */
279     @Override
280     public String toString() {
281         StringBuilder buffer = new StringBuilder();
282 
283         if (!getUsedDeclaredArtifacts().isEmpty()) {
284             buffer.append("usedDeclaredArtifacts=").append(getUsedDeclaredArtifacts());
285         }
286 
287         if (!getUsedUndeclaredArtifacts().isEmpty()) {
288             if (buffer.length() > 0) {
289                 buffer.append(",");
290             }
291 
292             buffer.append("usedUndeclaredArtifacts=").append(getUsedUndeclaredArtifacts());
293         }
294 
295         if (!getUnusedDeclaredArtifacts().isEmpty()) {
296             if (buffer.length() > 0) {
297                 buffer.append(",");
298             }
299 
300             buffer.append("unusedDeclaredArtifacts=").append(getUnusedDeclaredArtifacts());
301         }
302 
303         if (!getTestArtifactsWithNonTestScope().isEmpty()) {
304             if (buffer.length() > 0) {
305                 buffer.append(",");
306             }
307 
308             buffer.append("testArtifactsWithNonTestScope=").append(getTestArtifactsWithNonTestScope());
309         }
310 
311         buffer.insert(0, "[");
312         buffer.insert(0, getClass().getName());
313 
314         buffer.append("]");
315 
316         return buffer.toString();
317     }
318 
319     // private methods --------------------------------------------------------
320 
321     private Set<Artifact> safeCopy(Set<Artifact> set) {
322         return (set == null) ? Collections.emptySet() : Collections.unmodifiableSet(new LinkedHashSet<>(set));
323     }
324 
325     private static Map<Artifact, Set<DependencyUsage>> safeCopy(Map<Artifact, Set<DependencyUsage>> origMap) {
326         if (origMap == null) {
327             return Collections.emptyMap();
328         }
329 
330         Map<Artifact, Set<DependencyUsage>> map = new LinkedHashMap<>();
331 
332         for (Map.Entry<Artifact, Set<DependencyUsage>> e : origMap.entrySet()) {
333             map.put(e.getKey(), Collections.unmodifiableSet(new LinkedHashSet<>(e.getValue())));
334         }
335 
336         return map;
337     }
338 
339     private static Map<Artifact, Set<DependencyUsage>> mapWithKeys(Set<Artifact> keys) {
340         if (keys == null) {
341             return Collections.emptyMap();
342         }
343 
344         Map<Artifact, Set<DependencyUsage>> map = new LinkedHashMap<>();
345 
346         for (Artifact k : keys) {
347             map.put(k, Collections.<DependencyUsage>emptySet());
348         }
349 
350         return map;
351     }
352 }