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