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