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