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