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.LinkedHashSet;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  
32  /**
33   * Project dependencies analysis result.
34   *
35   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
36   */
37  public class ProjectDependencyAnalysis {
38      // fields -----------------------------------------------------------------
39  
40      private final Set<Artifact> usedDeclaredArtifacts;
41  
42      private final Map<Artifact, Set<String>> usedUndeclaredArtifacts;
43  
44      private final Set<Artifact> unusedDeclaredArtifacts;
45  
46      private final Set<Artifact> testArtifactsWithNonTestScope;
47  
48      /**
49       * <p>Constructor for ProjectDependencyAnalysis.</p>
50       */
51      public ProjectDependencyAnalysis() {
52          this(null, (Map<Artifact, Set<String>>) null, null, null);
53      }
54  
55      /**
56       * <p>Constructor for ProjectDependencyAnalysis to maintain compatibility with old API</p>
57       *
58       * @param usedDeclaredArtifacts artifacts both used and declared
59       * @param usedUndeclaredArtifacts artifacts used but not declared
60       * @param unusedDeclaredArtifacts artifacts declared but not used
61       */
62      public ProjectDependencyAnalysis(
63              Set<Artifact> usedDeclaredArtifacts,
64              Set<Artifact> usedUndeclaredArtifacts,
65              Set<Artifact> unusedDeclaredArtifacts) {
66          this(usedDeclaredArtifacts, usedUndeclaredArtifacts, unusedDeclaredArtifacts, Collections.<Artifact>emptySet());
67      }
68  
69      /**
70       * <p>Constructor for ProjectDependencyAnalysis.</p>
71       *
72       * @param usedDeclaredArtifacts artifacts both used and declared
73       * @param usedUndeclaredArtifacts artifacts used but not declared
74       * @param unusedDeclaredArtifacts artifacts declared but not used
75       * @param testArtifactsWithNonTestScope artifacts only used in tests but not declared with test scope
76       */
77      public ProjectDependencyAnalysis(
78              Set<Artifact> usedDeclaredArtifacts,
79              Set<Artifact> usedUndeclaredArtifacts,
80              Set<Artifact> unusedDeclaredArtifacts,
81              Set<Artifact> testArtifactsWithNonTestScope) {
82          this(
83                  usedDeclaredArtifacts,
84                  mapWithKeys(usedUndeclaredArtifacts),
85                  unusedDeclaredArtifacts,
86                  testArtifactsWithNonTestScope);
87      }
88  
89      public ProjectDependencyAnalysis(
90              Set<Artifact> usedDeclaredArtifacts,
91              Map<Artifact, Set<String>> usedUndeclaredArtifacts,
92              Set<Artifact> unusedDeclaredArtifacts,
93              Set<Artifact> testArtifactsWithNonTestScope) {
94          this.usedDeclaredArtifacts = safeCopy(usedDeclaredArtifacts);
95          this.usedUndeclaredArtifacts = safeCopy(usedUndeclaredArtifacts);
96          this.unusedDeclaredArtifacts = safeCopy(unusedDeclaredArtifacts);
97          this.testArtifactsWithNonTestScope = safeCopy(testArtifactsWithNonTestScope);
98      }
99  
100     /**
101      * Returns artifacts both used and declared.
102      *
103      * @return artifacts both used and declared
104      */
105     public Set<Artifact> getUsedDeclaredArtifacts() {
106         return safeCopy(usedDeclaredArtifacts);
107     }
108 
109     /**
110      * Returns artifacts used but not declared.
111      *
112      * @return artifacts used but not declared
113      */
114     public Set<Artifact> getUsedUndeclaredArtifacts() {
115         return safeCopy(usedUndeclaredArtifacts.keySet());
116     }
117 
118     /**
119      * Returns artifacts used but not declared.
120      *
121      * @return artifacts used but not declared
122      */
123     public Map<Artifact, Set<String>> getUsedUndeclaredArtifactsWithClasses() {
124         return safeCopy(usedUndeclaredArtifacts);
125     }
126 
127     /**
128      * Returns artifacts declared but not used.
129      *
130      * @return artifacts declared but not used
131      */
132     public Set<Artifact> getUnusedDeclaredArtifacts() {
133         return safeCopy(unusedDeclaredArtifacts);
134     }
135 
136     /**
137      * Returns artifacts only used in tests but not declared with test scope.
138      *
139      * @return  artifacts only used in tests but not declared with test scope
140      */
141     public Set<Artifact> getTestArtifactsWithNonTestScope() {
142         return safeCopy(testArtifactsWithNonTestScope);
143     }
144 
145     /**
146      * Filter non-compile scoped artifacts from unused declared.
147      *
148      * @return updated project dependency analysis
149      * @since 1.3
150      */
151     public ProjectDependencyAnalysis ignoreNonCompile() {
152         Set<Artifact> filteredUnusedDeclared = new HashSet<>(unusedDeclaredArtifacts);
153         filteredUnusedDeclared.removeIf(artifact -> !artifact.getScope().equals(Artifact.SCOPE_COMPILE));
154 
155         return new ProjectDependencyAnalysis(
156                 usedDeclaredArtifacts, usedUndeclaredArtifacts, filteredUnusedDeclared, testArtifactsWithNonTestScope);
157     }
158 
159     /**
160      * Force use status of some declared dependencies, to manually fix consequences of bytecode-level analysis which
161      * happens to not detect some effective use (constants, annotation with source-retention, javadoc).
162      *
163      * @param forceUsedDependencies dependencies to move from "unused-declared" to "used-declared", with
164      *                              <code>groupId:artifactId</code> format
165      * @return updated project dependency analysis
166      * @throws ProjectDependencyAnalyzerException if dependencies forced were either not declared or already detected as
167      *                                            used
168      * @since 1.3
169      */
170     @SuppressWarnings("UnusedReturnValue")
171     public ProjectDependencyAnalysis forceDeclaredDependenciesUsage(String[] forceUsedDependencies)
172             throws ProjectDependencyAnalyzerException {
173         Set<String> forced = new HashSet<>(Arrays.asList(forceUsedDependencies));
174 
175         Set<Artifact> forcedUnusedDeclared = new HashSet<>(unusedDeclaredArtifacts);
176         Set<Artifact> forcedUsedDeclared = new HashSet<>(usedDeclaredArtifacts);
177 
178         Iterator<Artifact> iter = forcedUnusedDeclared.iterator();
179         while (iter.hasNext()) {
180             Artifact artifact = iter.next();
181 
182             if (forced.remove(artifact.getGroupId() + ':' + artifact.getArtifactId())) {
183                 // ok, change artifact status from unused-declared to used-declared
184                 iter.remove();
185                 forcedUsedDeclared.add(artifact);
186             }
187         }
188 
189         if (!forced.isEmpty()) {
190             // trying to force dependencies as used-declared which were not declared or already detected as used
191             Set<String> used = new HashSet<>();
192             for (Artifact artifact : usedDeclaredArtifacts) {
193                 String id = artifact.getGroupId() + ':' + artifact.getArtifactId();
194                 if (forced.remove(id)) {
195                     used.add(id);
196                 }
197             }
198 
199             StringBuilder builder = new StringBuilder();
200             if (!forced.isEmpty()) {
201                 builder.append("not declared: ").append(forced);
202             }
203             if (!used.isEmpty()) {
204                 if (builder.length() > 0) {
205                     builder.append(" and ");
206                 }
207                 builder.append("declared but already detected as used: ").append(used);
208             }
209             throw new ProjectDependencyAnalyzerException("Trying to force use of dependencies which are " + builder);
210         }
211 
212         return new ProjectDependencyAnalysis(
213                 forcedUsedDeclared, usedUndeclaredArtifacts, forcedUnusedDeclared, testArtifactsWithNonTestScope);
214     }
215 
216     /**
217      * <p>hashCode.</p>
218      *
219      * @return an int
220      */
221     public int hashCode() {
222         int hashCode = getUsedDeclaredArtifacts().hashCode();
223         hashCode = (hashCode * 37) + getUsedUndeclaredArtifacts().hashCode();
224         hashCode = (hashCode * 37) + getUnusedDeclaredArtifacts().hashCode();
225         hashCode = (hashCode * 37) + getTestArtifactsWithNonTestScope().hashCode();
226 
227         return hashCode;
228     }
229 
230     /** {@inheritDoc} */
231     public boolean equals(Object object) {
232         if (object instanceof ProjectDependencyAnalysis) {
233             ProjectDependencyAnalysis analysis = (ProjectDependencyAnalysis) object;
234 
235             return getUsedDeclaredArtifacts().equals(analysis.getUsedDeclaredArtifacts())
236                     && getUsedUndeclaredArtifacts().equals(analysis.getUsedUndeclaredArtifacts())
237                     && getUnusedDeclaredArtifacts().equals(analysis.getUnusedDeclaredArtifacts())
238                     && getTestArtifactsWithNonTestScope().equals(analysis.getTestArtifactsWithNonTestScope());
239         }
240 
241         return false;
242     }
243 
244     /**
245      * <p>toString.</p>
246      *
247      * @return a {@link java.lang.String} object.
248      */
249     public String toString() {
250         StringBuilder buffer = new StringBuilder();
251 
252         if (!getUsedDeclaredArtifacts().isEmpty()) {
253             buffer.append("usedDeclaredArtifacts=").append(getUsedDeclaredArtifacts());
254         }
255 
256         if (!getUsedUndeclaredArtifacts().isEmpty()) {
257             if (buffer.length() > 0) {
258                 buffer.append(",");
259             }
260 
261             buffer.append("usedUndeclaredArtifacts=").append(getUsedUndeclaredArtifacts());
262         }
263 
264         if (!getUnusedDeclaredArtifacts().isEmpty()) {
265             if (buffer.length() > 0) {
266                 buffer.append(",");
267             }
268 
269             buffer.append("unusedDeclaredArtifacts=").append(getUnusedDeclaredArtifacts());
270         }
271 
272         if (!getTestArtifactsWithNonTestScope().isEmpty()) {
273             if (buffer.length() > 0) {
274                 buffer.append(",");
275             }
276 
277             buffer.append("testArtifactsWithNonTestScope=").append(getTestArtifactsWithNonTestScope());
278         }
279 
280         buffer.insert(0, "[");
281         buffer.insert(0, getClass().getName());
282 
283         buffer.append("]");
284 
285         return buffer.toString();
286     }
287 
288     // private methods --------------------------------------------------------
289 
290     private Set<Artifact> safeCopy(Set<Artifact> set) {
291         return (set == null) ? Collections.emptySet() : Collections.unmodifiableSet(new LinkedHashSet<>(set));
292     }
293 
294     private static Map<Artifact, Set<String>> safeCopy(Map<Artifact, Set<String>> origMap) {
295         if (origMap == null) {
296             return Collections.emptyMap();
297         }
298 
299         Map<Artifact, Set<String>> map = new HashMap<>();
300 
301         for (Map.Entry<Artifact, Set<String>> e : origMap.entrySet()) {
302             map.put(e.getKey(), Collections.unmodifiableSet(new LinkedHashSet<>(e.getValue())));
303         }
304 
305         return map;
306     }
307 
308     private static Map<Artifact, Set<String>> mapWithKeys(Set<Artifact> keys) {
309         if (keys == null) {
310             return Collections.emptyMap();
311         }
312 
313         Map<Artifact, Set<String>> map = new HashMap<>();
314 
315         for (Artifact k : keys) {
316             map.put(k, Collections.<String>emptySet());
317         }
318 
319         return map;
320     }
321 }