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;
20  
21  import javax.inject.Inject;
22  
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.TreeMap;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
38  import org.apache.maven.doxia.sink.Sink;
39  import org.apache.maven.doxia.sink.SinkEventAttributes;
40  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
41  import org.apache.maven.model.Dependency;
42  import org.apache.maven.plugins.annotations.Mojo;
43  import org.apache.maven.project.DefaultProjectBuildingRequest;
44  import org.apache.maven.project.MavenProject;
45  import org.apache.maven.project.ProjectBuilder;
46  import org.apache.maven.project.ProjectBuildingRequest;
47  import org.apache.maven.report.projectinfo.dependencies.DependencyVersionMap;
48  import org.apache.maven.report.projectinfo.dependencies.SinkSerializingDependencyNodeVisitor;
49  import org.apache.maven.reporting.MavenReportException;
50  import org.apache.maven.repository.RepositorySystem;
51  import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
52  import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
53  import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
54  import org.apache.maven.shared.dependency.graph.DependencyNode;
55  import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
56  import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
57  import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
58  import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
59  import org.apache.maven.shared.dependency.graph.traversal.BuildingDependencyNodeVisitor;
60  import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
61  import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
62  import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
63  import org.codehaus.plexus.i18n.I18N;
64  
65  /**
66   * Generates the Project Dependency Convergence report for (reactor) builds.
67   *
68   * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
69   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton </a>
70   * @author <a href="mailto:wangyf2010@gmail.com">Simon Wang </a>
71   * @since 2.0
72   */
73  @Mojo(name = "dependency-convergence", aggregator = true)
74  public class DependencyConvergenceReport extends AbstractProjectInfoReport {
75      /**
76       * URL for the 'icon_success_sml.gif' image
77       */
78      private static final String IMG_SUCCESS_URL = "images/icon_success_sml.gif";
79  
80      /**
81       * URL for the 'icon_error_sml.gif' image
82       */
83      private static final String IMG_ERROR_URL = "images/icon_error_sml.gif";
84  
85      private static final int FULL_CONVERGENCE = 100;
86  
87      private ArtifactFilter filter = null;
88  
89      private Map<MavenProject, DependencyNode> projectMap = new HashMap<>();
90  
91      // ----------------------------------------------------------------------
92      // Mojo parameters
93      // ----------------------------------------------------------------------
94  
95      /**
96       * Raw dependency collector builder, will use it to build dependency tree.
97       */
98      private final DependencyCollectorBuilder dependencyCollectorBuilder;
99  
100     @Inject
101     protected DependencyConvergenceReport(
102             RepositorySystem repositorySystem,
103             I18N i18n,
104             ProjectBuilder projectBuilder,
105             DependencyCollectorBuilder dependencyCollectorBuilder) {
106         super(repositorySystem, i18n, projectBuilder);
107         this.dependencyCollectorBuilder = dependencyCollectorBuilder;
108     }
109 
110     // ----------------------------------------------------------------------
111     // Public methods
112     // ----------------------------------------------------------------------
113 
114     /**
115      * {@inheritDoc}
116      */
117     public String getOutputName() {
118         return "dependency-convergence";
119     }
120 
121     @Override
122     protected String getI18Nsection() {
123         return "dependency-convergence";
124     }
125 
126     // ----------------------------------------------------------------------
127     // Protected methods
128     // ----------------------------------------------------------------------
129 
130     @Override
131     protected void executeReport(Locale locale) throws MavenReportException {
132         try (Sink sink = getSink()) {
133             sink.head();
134             sink.title();
135 
136             if (isReactorBuild()) {
137                 sink.text(getI18nString(locale, "reactor.title"));
138             } else {
139                 sink.text(getI18nString(locale, "title"));
140             }
141 
142             sink.title_();
143             sink.head_();
144 
145             sink.body();
146 
147             sink.section1();
148 
149             sink.sectionTitle1();
150 
151             if (isReactorBuild()) {
152                 sink.text(getI18nString(locale, "reactor.title"));
153             } else {
154                 sink.text(getI18nString(locale, "title"));
155             }
156 
157             sink.sectionTitle1_();
158 
159             DependencyAnalyzeResult dependencyResult = analyzeDependencyTree();
160             int convergence = calculateConvergence(dependencyResult);
161 
162             if (convergence < FULL_CONVERGENCE) {
163                 // legend
164                 generateLegend(locale, sink);
165                 sink.lineBreak();
166             }
167 
168             // stats
169             generateStats(locale, sink, dependencyResult);
170 
171             sink.section1_();
172 
173             if (convergence < FULL_CONVERGENCE) {
174                 // convergence
175                 generateConvergence(locale, sink, dependencyResult);
176             }
177 
178             sink.body_();
179         }
180     }
181 
182     // ----------------------------------------------------------------------
183     // Private methods
184     // ----------------------------------------------------------------------
185 
186     /**
187      * Get snapshots dependencies from all dependency map.
188      *
189      * @param dependencyMap
190      * @return snapshots dependencies
191      */
192     private List<ReverseDependencyLink> getSnapshotDependencies(
193             Map<String, List<ReverseDependencyLink>> dependencyMap) {
194         List<ReverseDependencyLink> snapshots = new ArrayList<>();
195         for (Map.Entry<String, List<ReverseDependencyLink>> entry : dependencyMap.entrySet()) {
196             List<ReverseDependencyLink> depList = entry.getValue();
197             Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap(depList);
198             for (Map.Entry<String, List<ReverseDependencyLink>> artEntry : artifactMap.entrySet()) {
199                 String version = artEntry.getKey();
200                 boolean isReactorProject = false;
201 
202                 Iterator<ReverseDependencyLink> iterator = artEntry.getValue().iterator();
203                 // It if enough to check just the first dependency here, because
204                 // the dependency is the same in all the RDLs in the List. It's the
205                 // reactorProjects that are different.
206                 ReverseDependencyLink rdl = null;
207                 if (iterator.hasNext()) {
208                     rdl = iterator.next();
209                     if (isReactorProject(rdl.getDependency())) {
210                         isReactorProject = true;
211                     }
212                 }
213 
214                 if (version.endsWith("-SNAPSHOT") && !isReactorProject && rdl != null) {
215                     snapshots.add(rdl);
216                 }
217             }
218         }
219 
220         return snapshots;
221     }
222 
223     /**
224      * Generate the convergence table for all dependencies
225      *
226      * @param locale
227      * @param sink
228      * @param result
229      */
230     private void generateConvergence(Locale locale, Sink sink, DependencyAnalyzeResult result) {
231         sink.section2();
232 
233         sink.sectionTitle2();
234 
235         if (isReactorBuild()) {
236             sink.text(getI18nString(locale, "convergence.caption"));
237         } else {
238             sink.text(getI18nString(locale, "convergence.single.caption"));
239         }
240 
241         sink.sectionTitle2_();
242 
243         // print conflicting dependencies
244         for (Map.Entry<String, List<ReverseDependencyLink>> entry :
245                 result.getConflicting().entrySet()) {
246             String key = entry.getKey();
247             List<ReverseDependencyLink> depList = entry.getValue();
248 
249             sink.section3();
250             sink.sectionTitle3();
251             sink.text(key);
252             sink.sectionTitle3_();
253 
254             generateDependencyDetails(locale, sink, depList);
255 
256             sink.section3_();
257         }
258 
259         // print out snapshots jars
260         for (ReverseDependencyLink dependencyLink : result.getSnapshots()) {
261             sink.section3();
262             sink.sectionTitle3();
263 
264             Dependency dep = dependencyLink.getDependency();
265 
266             sink.text(dep.getGroupId() + ":" + dep.getArtifactId());
267             sink.sectionTitle3_();
268 
269             List<ReverseDependencyLink> depList = new ArrayList<>();
270             depList.add(dependencyLink);
271             generateDependencyDetails(locale, sink, depList);
272 
273             sink.section3_();
274         }
275 
276         sink.section2_();
277     }
278 
279     /**
280      * Generate the detail table for a given dependency
281      *
282      * @param sink
283      * @param depList
284      */
285     private void generateDependencyDetails(Locale locale, Sink sink, List<ReverseDependencyLink> depList) {
286         sink.table();
287         sink.tableRows();
288 
289         Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap(depList);
290 
291         sink.tableRow();
292 
293         sink.tableCell();
294 
295         iconError(locale, sink);
296 
297         sink.tableCell_();
298 
299         sink.tableCell();
300 
301         sink.table();
302         sink.tableRows();
303 
304         for (String version : artifactMap.keySet()) {
305             sink.tableRow();
306             sink.tableCell(new SinkEventAttributeSet(SinkEventAttributes.WIDTH, "25%"));
307             sink.text(version);
308             sink.tableCell_();
309 
310             sink.tableCell();
311             generateVersionDetails(sink, artifactMap, version);
312             sink.tableCell_();
313 
314             sink.tableRow_();
315         }
316         sink.tableRows_();
317         sink.table_();
318 
319         sink.tableCell_();
320 
321         sink.tableRow_();
322 
323         sink.tableRows_();
324         sink.table_();
325     }
326 
327     /**
328      * Generate version details for a given dependency
329      *
330      * @param sink
331      * @param artifactMap
332      * @param version
333      */
334     private void generateVersionDetails(
335             Sink sink, Map<String, List<ReverseDependencyLink>> artifactMap, String version) {
336         sink.numberedList(0); // Use lower alpha numbering
337         List<ReverseDependencyLink> depList = artifactMap.get(version);
338 
339         List<DependencyNode> projectNodes = getProjectNodes(depList);
340 
341         if (projectNodes.isEmpty()) {
342             getLog().warn("Can't find project nodes for dependency list: "
343                     + depList.get(0).getDependency());
344             return;
345         }
346         Collections.sort(projectNodes, new DependencyNodeComparator());
347 
348         for (DependencyNode projectNode : projectNodes) {
349             if (isReactorBuild()) {
350                 sink.numberedListItem();
351             }
352 
353             showVersionDetails(projectNode, depList, sink);
354 
355             if (isReactorBuild()) {
356                 sink.numberedListItem_();
357             }
358 
359             sink.lineBreak();
360         }
361 
362         sink.numberedList_();
363     }
364 
365     private List<DependencyNode> getProjectNodes(List<ReverseDependencyLink> depList) {
366         List<DependencyNode> projectNodes = new ArrayList<>();
367 
368         for (ReverseDependencyLink depLink : depList) {
369             MavenProject project = depLink.getProject();
370             DependencyNode projectNode = this.projectMap.get(project);
371 
372             if (projectNode != null && !projectNodes.contains(projectNode)) {
373                 projectNodes.add(projectNode);
374             }
375         }
376         return projectNodes;
377     }
378 
379     private void showVersionDetails(DependencyNode projectNode, List<ReverseDependencyLink> depList, Sink sink) {
380         if (depList == null || depList.isEmpty()) {
381             return;
382         }
383 
384         Dependency dependency = depList.get(0).getDependency();
385         String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getType() + ":"
386                 + dependency.getVersion();
387 
388         serializeDependencyTree(projectNode, key, sink);
389     }
390 
391     /**
392      * Serializes the specified dependency tree to a string.
393      *
394      * @param rootNode the dependency tree root node to serialize
395      * @return the serialized dependency tree
396      */
397     private void serializeDependencyTree(DependencyNode rootNode, String key, Sink sink) {
398         DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor(sink);
399 
400         visitor = new BuildingDependencyNodeVisitor(visitor);
401 
402         DependencyNodeFilter nodeFilter = createDependencyNodeFilter(key);
403 
404         if (nodeFilter != null) {
405             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
406             DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, nodeFilter);
407             rootNode.accept(firstPassVisitor);
408 
409             DependencyNodeFilter secondPassFilter =
410                     new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
411             visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
412         }
413 
414         rootNode.accept(visitor);
415     }
416 
417     /**
418      * Gets the dependency node filter to use when serializing the dependency graph.
419      *
420      * @return the dependency node filter, or <code>null</code> if none required
421      */
422     private DependencyNodeFilter createDependencyNodeFilter(String includes) {
423         List<DependencyNodeFilter> filters = new ArrayList<>();
424 
425         // filter includes
426         if (includes != null) {
427             List<String> patterns = Arrays.asList(includes.split(","));
428 
429             getLog().debug("+ Filtering dependency tree by artifact include patterns: " + patterns);
430 
431             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter(patterns);
432             filters.add(new ArtifactDependencyNodeFilter(artifactFilter));
433         }
434 
435         return filters.isEmpty() ? null : new AndDependencyNodeFilter(filters);
436     }
437 
438     /**
439      * @param sink {@link Sink}
440      * @return {@link DependencyNodeVisitor}
441      */
442     public DependencyNodeVisitor getSerializingDependencyNodeVisitor(Sink sink) {
443         return new SinkSerializingDependencyNodeVisitor(sink);
444     }
445 
446     /**
447      * Produce a Map of relationships between dependencies (its version) and reactor projects. This is the structure of
448      * the Map:
449      *
450      * <pre>
451      * +--------------------+----------------------------------+
452      * | key                | value                            |
453      * +--------------------+----------------------------------+
454      * | version of a       | A List of ReverseDependencyLinks |
455      * | dependency         | which each look like this:       |
456      * |                    | +------------+-----------------+ |
457      * |                    | | dependency | reactor project | |
458      * |                    | +------------+-----------------+ |
459      * +--------------------+----------------------------------+
460      * </pre>
461      *
462      * @return A Map of sorted unique artifacts
463      */
464     private Map<String, List<ReverseDependencyLink>> getSortedUniqueArtifactMap(List<ReverseDependencyLink> depList) {
465         Map<String, List<ReverseDependencyLink>> uniqueArtifactMap = new TreeMap<>();
466 
467         for (ReverseDependencyLink rdl : depList) {
468             String key = rdl.getDependency().getVersion();
469             List<ReverseDependencyLink> projectList = uniqueArtifactMap.get(key);
470             if (projectList == null) {
471                 projectList = new ArrayList<>();
472             }
473             projectList.add(rdl);
474             uniqueArtifactMap.put(key, projectList);
475         }
476 
477         return uniqueArtifactMap;
478     }
479 
480     /**
481      * Generate the legend table
482      *
483      * @param locale
484      * @param sink
485      */
486     private void generateLegend(Locale locale, Sink sink) {
487         sink.table();
488         sink.tableRows();
489         sink.tableCaption();
490         sink.bold();
491         sink.text(getI18nString(locale, "legend"));
492         sink.bold_();
493         sink.tableCaption_();
494 
495         sink.tableRow();
496 
497         sink.tableCell();
498         iconError(locale, sink);
499         sink.tableCell_();
500         sink.tableCell();
501         sink.text(getI18nString(locale, "legend.different"));
502         sink.tableCell_();
503 
504         sink.tableRow_();
505 
506         sink.tableRows_();
507         sink.table_();
508     }
509 
510     /**
511      * Generate the statistic table
512      *
513      * @param locale
514      * @param sink
515      * @param result
516      */
517     private void generateStats(Locale locale, Sink sink, DependencyAnalyzeResult result) {
518         int depCount = result.getDependencyCount();
519 
520         int artifactCount = result.getArtifactCount();
521         int snapshotCount = result.getSnapshotCount();
522         int conflictingCount = result.getConflictingCount();
523 
524         int convergence = calculateConvergence(result);
525 
526         // Create report
527         sink.table();
528         sink.tableRows();
529         sink.tableCaption();
530         sink.bold();
531         sink.text(getI18nString(locale, "stats.caption"));
532         sink.bold_();
533         sink.tableCaption_();
534 
535         if (isReactorBuild()) {
536             sink.tableRow();
537             sink.tableHeaderCell();
538             sink.text(getI18nString(locale, "stats.modules"));
539             sink.tableHeaderCell_();
540             sink.tableCell();
541             sink.text(String.valueOf(reactorProjects.size()));
542             sink.tableCell_();
543             sink.tableRow_();
544         }
545 
546         sink.tableRow();
547         sink.tableHeaderCell();
548         sink.text(getI18nString(locale, "stats.dependencies"));
549         sink.tableHeaderCell_();
550         sink.tableCell();
551         sink.text(String.valueOf(depCount));
552         sink.tableCell_();
553         sink.tableRow_();
554 
555         sink.tableRow();
556         sink.tableHeaderCell();
557         sink.text(getI18nString(locale, "stats.artifacts"));
558         sink.tableHeaderCell_();
559         sink.tableCell();
560         sink.text(String.valueOf(artifactCount));
561         sink.tableCell_();
562         sink.tableRow_();
563 
564         sink.tableRow();
565         sink.tableHeaderCell();
566         sink.text(getI18nString(locale, "stats.conflicting"));
567         sink.tableHeaderCell_();
568         sink.tableCell();
569         sink.text(String.valueOf(conflictingCount));
570         sink.tableCell_();
571         sink.tableRow_();
572 
573         sink.tableRow();
574         sink.tableHeaderCell();
575         sink.text(getI18nString(locale, "stats.snapshots"));
576         sink.tableHeaderCell_();
577         sink.tableCell();
578         sink.text(String.valueOf(snapshotCount));
579         sink.tableCell_();
580         sink.tableRow_();
581 
582         sink.tableRow();
583         sink.tableHeaderCell();
584         sink.text(getI18nString(locale, "stats.convergence"));
585         sink.tableHeaderCell_();
586         sink.tableCell();
587         if (convergence < FULL_CONVERGENCE) {
588             iconError(locale, sink);
589         } else {
590             iconSuccess(locale, sink);
591         }
592         sink.nonBreakingSpace();
593         sink.bold();
594         sink.text(String.valueOf(convergence) + " %");
595         sink.bold_();
596         sink.tableCell_();
597         sink.tableRow_();
598 
599         sink.tableRow();
600         sink.tableHeaderCell();
601         sink.text(getI18nString(locale, "stats.readyrelease"));
602         sink.tableHeaderCell_();
603         sink.tableCell();
604         if (convergence >= FULL_CONVERGENCE && snapshotCount <= 0) {
605             iconSuccess(locale, sink);
606             sink.nonBreakingSpace();
607             sink.bold();
608             sink.text(getI18nString(locale, "stats.readyrelease.success"));
609             sink.bold_();
610         } else {
611             iconError(locale, sink);
612             sink.nonBreakingSpace();
613             sink.bold();
614             sink.text(getI18nString(locale, "stats.readyrelease.error"));
615             sink.bold_();
616             if (convergence < FULL_CONVERGENCE) {
617                 sink.lineBreak();
618                 sink.text(getI18nString(locale, "stats.readyrelease.error.convergence"));
619             }
620             if (snapshotCount > 0) {
621                 sink.lineBreak();
622                 sink.text(getI18nString(locale, "stats.readyrelease.error.snapshots"));
623             }
624         }
625         sink.tableCell_();
626         sink.tableRow_();
627 
628         sink.tableRows_();
629         sink.table_();
630     }
631 
632     /**
633      * Check to see if the specified dependency is among the reactor projects.
634      *
635      * @param dependency The dependency to check
636      * @return true if and only if the dependency is a reactor project
637      */
638     private boolean isReactorProject(Dependency dependency) {
639         for (MavenProject mavenProject : reactorProjects) {
640             if (mavenProject.getGroupId().equals(dependency.getGroupId())
641                     && mavenProject.getArtifactId().equals(dependency.getArtifactId())) {
642                 if (getLog().isDebugEnabled()) {
643                     getLog().debug(dependency + " is a reactor project");
644                 }
645                 return true;
646             }
647         }
648         return false;
649     }
650 
651     private boolean isReactorBuild() {
652         return this.reactorProjects.size() > 1;
653     }
654 
655     private void iconSuccess(Locale locale, Sink sink) {
656         SinkEventAttributes attributes =
657                 new SinkEventAttributeSet(SinkEventAttributes.ALT, getI18nString(locale, "icon.success"));
658         sink.figureGraphics(IMG_SUCCESS_URL, attributes);
659     }
660 
661     private void iconError(Locale locale, Sink sink) {
662         SinkEventAttributes attributes =
663                 new SinkEventAttributeSet(SinkEventAttributes.ALT, getI18nString(locale, "icon.error"));
664         sink.figureGraphics(IMG_ERROR_URL, attributes);
665     }
666 
667     /**
668      * Produce a DependencyAnalyzeResult, it contains conflicting dependencies map, snapshot dependencies map and all
669      * dependencies map. Map structure is the relationships between dependencies (its groupId:artifactId) and reactor
670      * projects. This is the structure of the Map:
671      *
672      * <pre>
673      * +--------------------+----------------------------------+---------------|
674      * | key                | value                                            |
675      * +--------------------+----------------------------------+---------------|
676      * | groupId:artifactId | A List of ReverseDependencyLinks                 |
677      * | of a dependency    | which each look like this:                       |
678      * |                    | +------------+-----------------+-----------------|
679      * |                    | | dependency | reactor project | dependency node |
680      * |                    | +------------+-----------------+-----------------|
681      * +--------------------+--------------------------------------------------|
682      * </pre>
683      *
684      * @return DependencyAnalyzeResult contains conflicting dependencies map, snapshot dependencies map and all
685      * dependencies map.
686      * @throws MavenReportException
687      */
688     private DependencyAnalyzeResult analyzeDependencyTree() throws MavenReportException {
689         Map<String, List<ReverseDependencyLink>> conflictingDependencyMap = new TreeMap<>();
690         Map<String, List<ReverseDependencyLink>> allDependencies = new TreeMap<>();
691 
692         ProjectBuildingRequest buildingRequest =
693                 new DefaultProjectBuildingRequest(getSession().getProjectBuildingRequest());
694 
695         for (MavenProject reactorProject : reactorProjects) {
696             buildingRequest.setProject(reactorProject);
697 
698             DependencyNode node = getNode(buildingRequest);
699 
700             this.projectMap.put(reactorProject, node);
701 
702             getConflictingDependencyMap(conflictingDependencyMap, reactorProject, node);
703 
704             getAllDependencyMap(allDependencies, reactorProject, node);
705         }
706 
707         return populateDependencyAnalyzeResult(conflictingDependencyMap, allDependencies);
708     }
709 
710     /**
711      * Produce DependencyAnalyzeResult base on conflicting dependencies map, all dependencies map.
712      *
713      * @param conflictingDependencyMap
714      * @param allDependencies
715      * @return DependencyAnalyzeResult contains conflicting dependencies map, snapshot dependencies map and all
716      * dependencies map.
717      */
718     private DependencyAnalyzeResult populateDependencyAnalyzeResult(
719             Map<String, List<ReverseDependencyLink>> conflictingDependencyMap,
720             Map<String, List<ReverseDependencyLink>> allDependencies) {
721         DependencyAnalyzeResult dependencyResult = new DependencyAnalyzeResult();
722 
723         dependencyResult.setAll(allDependencies);
724         dependencyResult.setConflicting(conflictingDependencyMap);
725 
726         List<ReverseDependencyLink> snapshots = getSnapshotDependencies(allDependencies);
727         dependencyResult.setSnapshots(snapshots);
728         return dependencyResult;
729     }
730 
731     /**
732      * Get conflicting dependency map base on specified dependency node.
733      *
734      * @param conflictingDependencyMap
735      * @param reactorProject
736      * @param node
737      */
738     private void getConflictingDependencyMap(
739             Map<String, List<ReverseDependencyLink>> conflictingDependencyMap,
740             MavenProject reactorProject,
741             DependencyNode node) {
742         DependencyVersionMap visitor = new DependencyVersionMap();
743         visitor.setUniqueVersions(true);
744 
745         node.accept(visitor);
746 
747         for (List<DependencyNode> nodes : visitor.getConflictedVersionNumbers()) {
748             DependencyNode dependencyNode = nodes.get(0);
749 
750             String key = dependencyNode.getArtifact().getGroupId() + ":"
751                     + dependencyNode.getArtifact().getArtifactId();
752 
753             List<ReverseDependencyLink> dependencyList = conflictingDependencyMap.get(key);
754             if (dependencyList == null) {
755                 dependencyList = new ArrayList<>();
756             }
757 
758             dependencyList.add(new ReverseDependencyLink(toDependency(dependencyNode.getArtifact()), reactorProject));
759 
760             for (DependencyNode workNode : nodes.subList(1, nodes.size())) {
761                 dependencyList.add(new ReverseDependencyLink(toDependency(workNode.getArtifact()), reactorProject));
762             }
763 
764             conflictingDependencyMap.put(key, dependencyList);
765         }
766     }
767 
768     /**
769      * Get all dependencies (both directive & transitive dependencies) by specified dependency node.
770      *
771      * @param allDependencies
772      * @param reactorProject
773      * @param node
774      */
775     private void getAllDependencyMap(
776             Map<String, List<ReverseDependencyLink>> allDependencies,
777             MavenProject reactorProject,
778             DependencyNode node) {
779         Set<Artifact> artifacts = getAllDescendants(node);
780 
781         for (Artifact art : artifacts) {
782             String key = art.getGroupId() + ":" + art.getArtifactId();
783 
784             List<ReverseDependencyLink> reverseDepependencies = allDependencies.get(key);
785             if (reverseDepependencies == null) {
786                 reverseDepependencies = new ArrayList<>();
787             }
788 
789             if (!containsDependency(reverseDepependencies, art)) {
790                 reverseDepependencies.add(new ReverseDependencyLink(toDependency(art), reactorProject));
791             }
792 
793             allDependencies.put(key, reverseDepependencies);
794         }
795     }
796 
797     /**
798      * Convert Artifact to Dependency
799      *
800      * @param artifact
801      * @return Dependency object
802      */
803     private Dependency toDependency(Artifact artifact) {
804         Dependency dependency = new Dependency();
805         dependency.setGroupId(artifact.getGroupId());
806         dependency.setArtifactId(artifact.getArtifactId());
807         dependency.setVersion(artifact.getVersion());
808         dependency.setClassifier(artifact.getClassifier());
809         dependency.setScope(artifact.getScope());
810 
811         return dependency;
812     }
813 
814     /**
815      * To check whether dependency list contains a given artifact.
816      *
817      * @param reverseDependencies
818      * @param art
819      * @return contains:true; Not contains:false;
820      */
821     private boolean containsDependency(List<ReverseDependencyLink> reverseDependencies, Artifact art) {
822 
823         for (ReverseDependencyLink revDependency : reverseDependencies) {
824             Dependency dep = revDependency.getDependency();
825             if (dep.getGroupId().equals(art.getGroupId())
826                     && dep.getArtifactId().equals(art.getArtifactId())
827                     && dep.getVersion().equals(art.getVersion())) {
828                 return true;
829             }
830         }
831 
832         return false;
833     }
834 
835     /**
836      * Get root node of dependency tree for a given project
837      *
838      * @param buildingRequest
839      * @return root node of dependency tree
840      * @throws MavenReportException
841      */
842     private DependencyNode getNode(ProjectBuildingRequest buildingRequest) throws MavenReportException {
843         try {
844             return dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, filter);
845         } catch (DependencyCollectorBuilderException e) {
846             throw new MavenReportException("Could not build dependency tree: " + e.getMessage(), e);
847         }
848     }
849 
850     /**
851      * Get all descendants nodes for a given dependency node.
852      *
853      * @param node
854      * @return set of descendants artifacts.
855      */
856     private Set<Artifact> getAllDescendants(DependencyNode node) {
857         Set<Artifact> children = null;
858         if (node.getChildren() != null) {
859             children = new HashSet<>();
860             for (DependencyNode depNode : node.getChildren()) {
861                 children.add(depNode.getArtifact());
862                 Set<Artifact> subNodes = getAllDescendants(depNode);
863                 if (subNodes != null) {
864                     children.addAll(subNodes);
865                 }
866             }
867         }
868         return children;
869     }
870 
871     private int calculateConvergence(DependencyAnalyzeResult result) {
872         return (int) (((double) result.getDependencyCount() / (double) result.getArtifactCount()) * FULL_CONVERGENCE);
873     }
874 
875     /**
876      * Internal object
877      */
878     private static class ReverseDependencyLink {
879         private Dependency dependency;
880 
881         protected MavenProject project;
882 
883         ReverseDependencyLink(Dependency dependency, MavenProject project) {
884             this.dependency = dependency;
885             this.project = project;
886         }
887 
888         public Dependency getDependency() {
889             return dependency;
890         }
891 
892         public MavenProject getProject() {
893             return project;
894         }
895 
896         @Override
897         public String toString() {
898             return project.getId();
899         }
900     }
901 
902     /**
903      * Internal ReverseDependencyLink comparator
904      */
905     static class DependencyNodeComparator implements Comparator<DependencyNode> {
906         /**
907          * {@inheritDoc}
908          */
909         public int compare(DependencyNode p1, DependencyNode p2) {
910             return p1.getArtifact().getId().compareTo(p2.getArtifact().getId());
911         }
912     }
913 
914     /**
915      * Internal object
916      */
917     private class DependencyAnalyzeResult {
918         Map<String, List<ReverseDependencyLink>> all;
919 
920         List<ReverseDependencyLink> snapshots;
921 
922         Map<String, List<ReverseDependencyLink>> conflicting;
923 
924         public void setAll(Map<String, List<ReverseDependencyLink>> all) {
925             this.all = all;
926         }
927 
928         public List<ReverseDependencyLink> getSnapshots() {
929             return snapshots;
930         }
931 
932         public void setSnapshots(List<ReverseDependencyLink> snapshots) {
933             this.snapshots = snapshots;
934         }
935 
936         public Map<String, List<ReverseDependencyLink>> getConflicting() {
937             return conflicting;
938         }
939 
940         public void setConflicting(Map<String, List<ReverseDependencyLink>> conflicting) {
941             this.conflicting = conflicting;
942         }
943 
944         public int getDependencyCount() {
945             return all.size();
946         }
947 
948         public int getSnapshotCount() {
949             return this.snapshots.size();
950         }
951 
952         public int getConflictingCount() {
953             return this.conflicting.size();
954         }
955 
956         public int getArtifactCount() {
957             int artifactCount = 0;
958             for (List<ReverseDependencyLink> depList : this.all.values()) {
959                 Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap(depList);
960                 artifactCount += artifactMap.size();
961             }
962 
963             return artifactCount;
964         }
965     }
966 }