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.report.projectinfo.dependencies;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.jar.JarEntry;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.project.MavenProject;
31  import org.apache.maven.shared.dependency.graph.DependencyNode;
32  import org.apache.maven.shared.jar.JarAnalyzer;
33  import org.apache.maven.shared.jar.JarData;
34  import org.apache.maven.shared.jar.classes.JarClasses;
35  import org.apache.maven.shared.jar.classes.JarClassesAnalysis;
36  import org.codehaus.plexus.util.StringUtils;
37  
38  /**
39   * @since 2.1
40   */
41  public class Dependencies {
42      private final MavenProject project;
43  
44      private final DependencyNode dependencyNode;
45  
46      private final JarClassesAnalysis classesAnalyzer;
47  
48      /**
49       * @since 2.1
50       */
51      private List<Artifact> projectDependencies;
52  
53      /**
54       * @since 2.1
55       */
56      private List<Artifact> projectTransitiveDependencies;
57  
58      /**
59       * @since 2.1
60       */
61      private List<Artifact> allDependencies;
62  
63      /**
64       * @since 2.1
65       */
66      private Map<String, List<Artifact>> dependenciesByScope;
67  
68      /**
69       * @since 2.1
70       */
71      private Map<String, List<Artifact>> transitiveDependenciesByScope;
72  
73      /**
74       * @since 2.1
75       */
76      private Map<String, JarData> dependencyDetails;
77  
78      /**
79       * Default constructor
80       *
81       * @param project the MavenProject.
82       * @param dependencyTreeNode the DependencyNode.
83       * @param classesAnalyzer the JarClassesAnalysis.
84       */
85      public Dependencies(MavenProject project, DependencyNode dependencyTreeNode, JarClassesAnalysis classesAnalyzer) {
86          this.project = project;
87          this.dependencyNode = dependencyTreeNode;
88          this.classesAnalyzer = classesAnalyzer;
89      }
90  
91      /**
92       * Getter for the project
93       *
94       * @return the project
95       */
96      public MavenProject getProject() {
97          return project;
98      }
99  
100     /**
101      * @return <code>true</code> if getProjectDependencies() is not empty, <code>false</code> otherwise.
102      */
103     public boolean hasDependencies() {
104         return (getProjectDependencies() != null) && (!getProjectDependencies().isEmpty());
105     }
106 
107     /**
108      * @return a list of <code>Artifact</code> from the project.
109      */
110     public List<Artifact> getProjectDependencies() {
111         if (projectDependencies != null) {
112             return projectDependencies;
113         }
114 
115         projectDependencies = new ArrayList<>();
116         for (DependencyNode dep : dependencyNode.getChildren()) {
117             projectDependencies.add(dep.getArtifact());
118         }
119 
120         return projectDependencies;
121     }
122 
123     /**
124      * @return a list of transitive <code>Artifact</code> from the project.
125      */
126     public List<Artifact> getTransitiveDependencies() {
127         if (projectTransitiveDependencies != null) {
128             return projectTransitiveDependencies;
129         }
130 
131         projectTransitiveDependencies = new ArrayList<>(getAllDependencies());
132         projectTransitiveDependencies.removeAll(getProjectDependencies());
133 
134         return projectTransitiveDependencies;
135     }
136 
137     /**
138      * @return a list of included <code>Artifact</code> returned by the dependency tree.
139      */
140     public List<Artifact> getAllDependencies() {
141         if (allDependencies != null) {
142             return allDependencies;
143         }
144 
145         allDependencies = new ArrayList<>();
146 
147         addAllChildrenDependencies(dependencyNode);
148 
149         return allDependencies;
150     }
151 
152     /**
153      * @param isTransitively <code>true</code> to return transitive dependencies, <code>false</code> otherwise.
154      * @return a map with supported scopes as key and a list of <code>Artifact</code> as values.
155      * @see Artifact#SCOPE_COMPILE
156      * @see Artifact#SCOPE_PROVIDED
157      * @see Artifact#SCOPE_RUNTIME
158      * @see Artifact#SCOPE_SYSTEM
159      * @see Artifact#SCOPE_TEST
160      */
161     public Map<String, List<Artifact>> getDependenciesByScope(boolean isTransitively) {
162         if (isTransitively) {
163             if (transitiveDependenciesByScope != null) {
164                 return transitiveDependenciesByScope;
165             }
166 
167             transitiveDependenciesByScope = new HashMap<>();
168             for (Artifact artifact : getTransitiveDependencies()) {
169                 List<Artifact> multiValue = transitiveDependenciesByScope.get(artifact.getScope());
170                 if (multiValue == null) {
171                     multiValue = new ArrayList<>();
172                 }
173 
174                 if (!multiValue.contains(artifact)) {
175                     multiValue.add(artifact);
176                 }
177                 transitiveDependenciesByScope.put(artifact.getScope(), multiValue);
178             }
179 
180             return transitiveDependenciesByScope;
181         }
182 
183         if (dependenciesByScope != null) {
184             return dependenciesByScope;
185         }
186 
187         dependenciesByScope = new HashMap<>();
188         for (Artifact artifact : getProjectDependencies()) {
189             List<Artifact> multiValue = dependenciesByScope.get(artifact.getScope());
190             if (multiValue == null) {
191                 multiValue = new ArrayList<>();
192             }
193 
194             if (!multiValue.contains(artifact)) {
195                 multiValue.add(artifact);
196             }
197             dependenciesByScope.put(artifact.getScope(), multiValue);
198         }
199 
200         return dependenciesByScope;
201     }
202 
203     /**
204      * @param artifact the artifact.
205      * @return the jardata object from the artifact
206      * @throws IOException if any
207      */
208     public JarData getJarDependencyDetails(Artifact artifact) throws IOException {
209         if (dependencyDetails == null) {
210             dependencyDetails = new HashMap<>();
211         }
212 
213         JarData jarData = dependencyDetails.get(artifact.getId());
214         if (jarData != null) {
215             return jarData;
216         }
217 
218         File file = getFile(artifact);
219 
220         if (file.isDirectory()) {
221             jarData = new JarData(artifact.getFile(), null, new ArrayList<JarEntry>());
222 
223             jarData.setJarClasses(new JarClasses());
224         } else {
225             JarAnalyzer jarAnalyzer = new JarAnalyzer(file);
226 
227             try {
228                 classesAnalyzer.analyze(jarAnalyzer);
229             } finally {
230                 jarAnalyzer.closeQuietly();
231             }
232 
233             jarData = jarAnalyzer.getJarData();
234         }
235 
236         dependencyDetails.put(artifact.getId(), jarData);
237 
238         return jarData;
239     }
240 
241     // ----------------------------------------------------------------------
242     // Private methods
243     // ----------------------------------------------------------------------
244 
245     /**
246      * Recursive method to get all dependencies from a given <code>dependencyNode</code>
247      *
248      * @param dependencyNode not null
249      */
250     private void addAllChildrenDependencies(DependencyNode dependencyNode) {
251         for (DependencyNode subdependencyNode : dependencyNode.getChildren()) {
252             Artifact artifact = subdependencyNode.getArtifact();
253 
254             if (artifact.getGroupId().equals(project.getGroupId())
255                     && artifact.getArtifactId().equals(project.getArtifactId())
256                     && artifact.getVersion().equals(project.getVersion())) {
257                 continue;
258             }
259 
260             if (!allDependencies.contains(artifact)) {
261                 allDependencies.add(artifact);
262             }
263 
264             addAllChildrenDependencies(subdependencyNode);
265         }
266     }
267 
268     /**
269      * get the artifact's file, with detection of target/classes directory with already packaged jar available.
270      *
271      * @param artifact the artifact to retrieve the physical file
272      * @return the physical file representing the given artifact
273      */
274     public File getFile(Artifact artifact) {
275         File file = artifact.getFile();
276 
277         if (file.isDirectory()) {
278             // MPIR-322: if target/classes directory, try target/artifactId-version[-classifier].jar
279             String filename = artifact.getArtifactId() + '-' + artifact.getVersion();
280             if (StringUtils.isNotEmpty(artifact.getClassifier())) {
281                 filename += '-' + artifact.getClassifier();
282             }
283             filename += '.' + artifact.getType();
284 
285             File jar = new File(file, "../" + filename);
286 
287             if (jar.exists()) {
288                 return jar;
289             }
290         }
291 
292         return file;
293     }
294 }