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     private List<ReverseDependencyLink> getSnapshotDependencies(
191                     Map<String, List<ReverseDependencyLink>> dependencyMap )
192     {
193         List<ReverseDependencyLink> snapshots = new ArrayList<>();
194         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : dependencyMap.entrySet() )
195         {
196             List<ReverseDependencyLink> depList = entry.getValue();
197             Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
198             for ( Map.Entry<String, List<ReverseDependencyLink>> artEntry : artifactMap.entrySet() )
199             {
200                 String version = artEntry.getKey();
201                 boolean isReactorProject = false;
202 
203                 Iterator<ReverseDependencyLink> iterator = artEntry.getValue().iterator();
204                 // It if enough to check just the first dependency here, because
205                 // the dependency is the same in all the RDLs in the List. It's the
206                 // reactorProjects that are different.
207                 ReverseDependencyLink rdl = null;
208                 if ( iterator.hasNext() )
209                 {
210                     rdl = iterator.next();
211                     if ( isReactorProject( rdl.getDependency() ) )
212                     {
213                         isReactorProject = true;
214                     }
215                 }
216 
217                 if ( version.endsWith( "-SNAPSHOT" ) && !isReactorProject && rdl != null )
218                 {
219                     snapshots.add( rdl );
220                 }
221             }
222         }
223 
224         return snapshots;
225     }
226 
227     /**
228      * Generate the convergence table for all dependencies
229      *
230      * @param locale
231      * @param sink
232      * @param result
233      */
234     private void generateConvergence( Locale locale, Sink sink, DependencyAnalyzeResult result )
235     {
236         sink.section2();
237 
238         sink.sectionTitle2();
239 
240         if ( isReactorBuild() )
241         {
242             sink.text( getI18nString( locale, "convergence.caption" ) );
243         }
244         else
245         {
246             sink.text( getI18nString( locale, "convergence.single.caption" ) );
247         }
248 
249         sink.sectionTitle2_();
250 
251         // print conflicting dependencies
252         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : result.getConflicting().entrySet() )
253         {
254             String key = entry.getKey();
255             List<ReverseDependencyLink> depList = entry.getValue();
256 
257             sink.section3();
258             sink.sectionTitle3();
259             sink.text( key );
260             sink.sectionTitle3_();
261 
262             generateDependencyDetails( locale, sink, depList );
263 
264             sink.section3_();
265         }
266 
267         // print out snapshots jars
268         for ( ReverseDependencyLink dependencyLink : result.getSnapshots() )
269         {
270             sink.section3();
271             sink.sectionTitle3();
272 
273             Dependency dep = dependencyLink.getDependency();
274 
275             sink.text( dep.getGroupId() + ":" + dep.getArtifactId() );
276             sink.sectionTitle3_();
277 
278             List<ReverseDependencyLink> depList = new ArrayList<>();
279             depList.add( dependencyLink );
280             generateDependencyDetails( locale, sink, depList );
281 
282             sink.section3_();
283         }
284 
285         sink.section2_();
286     }
287 
288     /**
289      * Generate the detail table for a given dependency
290      *
291      * @param sink
292      * @param depList
293      */
294     private void generateDependencyDetails( Locale locale, Sink sink, List<ReverseDependencyLink> depList )
295     {
296         sink.table();
297 
298         Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
299 
300         sink.tableRow();
301 
302         sink.tableCell();
303 
304         iconError( locale, sink );
305 
306         sink.tableCell_();
307 
308         sink.tableCell();
309 
310         sink.table();
311 
312         for ( String version : artifactMap.keySet() )
313         {
314             sink.tableRow();
315             sink.tableCell( new SinkEventAttributeSet( SinkEventAttributes.WIDTH, "25%" ) );
316             sink.text( version );
317             sink.tableCell_();
318 
319             sink.tableCell();
320             generateVersionDetails( sink, artifactMap, version );
321             sink.tableCell_();
322 
323             sink.tableRow_();
324         }
325         sink.table_();
326         sink.tableCell_();
327 
328         sink.tableRow_();
329 
330         sink.table_();
331     }
332 
333     /**
334      * Generate version details for a given dependency
335      *
336      * @param sink
337      * @param artifactMap
338      * @param version
339      */
340     private void generateVersionDetails( Sink sink, Map<String, List<ReverseDependencyLink>> artifactMap,
341                     String version )
342     {
343         sink.numberedList( 0 ); // Use lower alpha numbering
344         List<ReverseDependencyLink> depList = artifactMap.get( version );
345 
346         List<DependencyNode> projectNodes = getProjectNodes( depList );
347 
348         if ( projectNodes.isEmpty() )
349         {
350             getLog().warn( "Can't find project nodes for dependency list: " + depList.get( 0 ).getDependency() );
351             return;
352         }
353         Collections.sort( projectNodes, new DependencyNodeComparator() );
354 
355         for ( DependencyNode projectNode : projectNodes )
356         {
357             if ( isReactorBuild() )
358             {
359                 sink.numberedListItem();
360             }
361 
362             showVersionDetails( projectNode, depList, sink );
363 
364             if ( isReactorBuild() )
365             {
366                 sink.numberedListItem_();
367             }
368 
369             sink.lineBreak();
370         }
371 
372         sink.numberedList_();
373     }
374 
375     private List<DependencyNode> getProjectNodes( List<ReverseDependencyLink> depList )
376     {
377         List<DependencyNode> projectNodes = new ArrayList<>();
378 
379         for ( ReverseDependencyLink depLink : depList )
380         {
381             MavenProject project = depLink.getProject();
382             DependencyNode projectNode = this.projectMap.get( project );
383 
384             if ( projectNode != null && !projectNodes.contains( projectNode ) )
385             {
386                 projectNodes.add( projectNode );
387             }
388         }
389         return projectNodes;
390     }
391 
392     private void showVersionDetails( DependencyNode projectNode, List<ReverseDependencyLink> depList, Sink sink )
393     {
394         if ( depList == null || depList.isEmpty() )
395         {
396             return;
397         }
398 
399         Dependency dependency = depList.get( 0 ).getDependency();
400         String key =
401             dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getType() + ":"
402                 + dependency.getVersion();
403 
404         serializeDependencyTree( projectNode, key, sink );
405 
406     }
407 
408     /**
409      * Serializes the specified dependency tree to a string.
410      *
411      * @param rootNode the dependency tree root node to serialize
412      * @return the serialized dependency tree
413      */
414     private void serializeDependencyTree( DependencyNode rootNode, String key, Sink sink )
415     {
416         DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor( sink );
417 
418         visitor = new BuildingDependencyNodeVisitor( visitor );
419 
420         DependencyNodeFilter nodeFilter = createDependencyNodeFilter( key );
421 
422         if ( nodeFilter != null )
423         {
424             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
425             DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(
426                     collectingVisitor, nodeFilter );
427             rootNode.accept( firstPassVisitor );
428 
429             DependencyNodeFilter secondPassFilter =
430                 new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
431             visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
432         }
433 
434         rootNode.accept( visitor );
435     }
436 
437     /**
438      * Gets the dependency node filter to use when serializing the dependency graph.
439      *
440      * @return the dependency node filter, or <code>null</code> if none required
441      */
442     private DependencyNodeFilter createDependencyNodeFilter( String includes )
443     {
444         List<DependencyNodeFilter> filters = new ArrayList<>();
445 
446         // filter includes
447         if ( includes != null )
448         {
449             List<String> patterns = Arrays.asList( includes.split( "," ) );
450 
451             getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
452 
453             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
454             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
455         }
456 
457         return filters.isEmpty() ? null : new AndDependencyNodeFilter( filters );
458     }
459 
460     /**
461      * @param sink {@link Sink}
462      * @return {@link DependencyNodeVisitor}
463      */
464     public DependencyNodeVisitor getSerializingDependencyNodeVisitor( Sink sink )
465     {
466         return new SinkSerializingDependencyNodeVisitor( sink );
467     }
468 
469     /**
470      * Produce a Map of relationships between dependencies (its version) and reactor projects. This is the structure of
471      * the Map:
472      *
473      * <pre>
474      * +--------------------+----------------------------------+
475      * | key                | value                            |
476      * +--------------------+----------------------------------+
477      * | version of a       | A List of ReverseDependencyLinks |
478      * | dependency         | which each look like this:       |
479      * |                    | +------------+-----------------+ |
480      * |                    | | dependency | reactor project | |
481      * |                    | +------------+-----------------+ |
482      * +--------------------+----------------------------------+
483      * </pre>
484      *
485      * @return A Map of sorted unique artifacts
486      */
487     private Map<String, List<ReverseDependencyLink>> getSortedUniqueArtifactMap( List<ReverseDependencyLink> depList )
488     {
489         Map<String, List<ReverseDependencyLink>> uniqueArtifactMap = new TreeMap<>();
490 
491         for ( ReverseDependencyLink rdl : depList )
492         {
493             String key = rdl.getDependency().getVersion();
494             List<ReverseDependencyLink> projectList = uniqueArtifactMap.get( key );
495             if ( projectList == null )
496             {
497                 projectList = new ArrayList<>();
498             }
499             projectList.add( rdl );
500             uniqueArtifactMap.put( key, projectList );
501         }
502 
503         return uniqueArtifactMap;
504     }
505 
506     /**
507      * Generate the legend table
508      *
509      * @param locale
510      * @param sink
511      */
512     private void generateLegend( Locale locale, Sink sink )
513     {
514         sink.table();
515         sink.tableCaption();
516         sink.bold();
517         sink.text( getI18nString( locale, "legend" ) );
518         sink.bold_();
519         sink.tableCaption_();
520 
521         sink.tableRow();
522 
523         sink.tableCell();
524         iconError( locale, sink );
525         sink.tableCell_();
526         sink.tableCell();
527         sink.text( getI18nString( locale, "legend.different" ) );
528         sink.tableCell_();
529 
530         sink.tableRow_();
531 
532         sink.table_();
533     }
534 
535     /**
536      * Generate the statistic table
537      *
538      * @param locale
539      * @param sink
540      * @param result
541      */
542     private void generateStats( Locale locale, Sink sink, DependencyAnalyzeResult result )
543     {
544         int depCount = result.getDependencyCount();
545 
546         int artifactCount = result.getArtifactCount();
547         int snapshotCount = result.getSnapshotCount();
548         int conflictingCount = result.getConflictingCount();
549 
550         int convergence = calculateConvergence( result );
551 
552         // Create report
553         sink.table();
554         sink.tableCaption();
555         sink.bold();
556         sink.text( getI18nString( locale, "stats.caption" ) );
557         sink.bold_();
558         sink.tableCaption_();
559 
560         if ( isReactorBuild() )
561         {
562             sink.tableRow();
563             sink.tableHeaderCell();
564             sink.text( getI18nString( locale, "stats.modules" ) );
565             sink.tableHeaderCell_();
566             sink.tableCell();
567             sink.text( String.valueOf( reactorProjects.size() ) );
568             sink.tableCell_();
569             sink.tableRow_();
570         }
571 
572         sink.tableRow();
573         sink.tableHeaderCell();
574         sink.text( getI18nString( locale, "stats.dependencies" ) );
575         sink.tableHeaderCell_();
576         sink.tableCell();
577         sink.text( String.valueOf( depCount ) );
578         sink.tableCell_();
579         sink.tableRow_();
580 
581         sink.tableRow();
582         sink.tableHeaderCell();
583         sink.text( getI18nString( locale, "stats.artifacts" ) );
584         sink.tableHeaderCell_();
585         sink.tableCell();
586         sink.text( String.valueOf( artifactCount ) );
587         sink.tableCell_();
588         sink.tableRow_();
589 
590         sink.tableRow();
591         sink.tableHeaderCell();
592         sink.text( getI18nString( locale, "stats.conflicting" ) );
593         sink.tableHeaderCell_();
594         sink.tableCell();
595         sink.text( String.valueOf( conflictingCount ) );
596         sink.tableCell_();
597         sink.tableRow_();
598 
599         sink.tableRow();
600         sink.tableHeaderCell();
601         sink.text( getI18nString( locale, "stats.snapshots" ) );
602         sink.tableHeaderCell_();
603         sink.tableCell();
604         sink.text( String.valueOf( snapshotCount ) );
605         sink.tableCell_();
606         sink.tableRow_();
607 
608         sink.tableRow();
609         sink.tableHeaderCell();
610         sink.text( getI18nString( locale, "stats.convergence" ) );
611         sink.tableHeaderCell_();
612         sink.tableCell();
613         if ( convergence < FULL_CONVERGENCE )
614         {
615             iconError( locale, sink );
616         }
617         else
618         {
619             iconSuccess( locale, sink );
620         }
621         sink.nonBreakingSpace();
622         sink.bold();
623         sink.text( String.valueOf( convergence ) + " %" );
624         sink.bold_();
625         sink.tableCell_();
626         sink.tableRow_();
627 
628         sink.tableRow();
629         sink.tableHeaderCell();
630         sink.text( getI18nString( locale, "stats.readyrelease" ) );
631         sink.tableHeaderCell_();
632         sink.tableCell();
633         if ( convergence >= FULL_CONVERGENCE && snapshotCount <= 0 )
634         {
635             iconSuccess( locale, sink );
636             sink.nonBreakingSpace();
637             sink.bold();
638             sink.text( getI18nString( locale, "stats.readyrelease.success" ) );
639             sink.bold_();
640         }
641         else
642         {
643             iconError( locale, sink );
644             sink.nonBreakingSpace();
645             sink.bold();
646             sink.text( getI18nString( locale, "stats.readyrelease.error" ) );
647             sink.bold_();
648             if ( convergence < FULL_CONVERGENCE )
649             {
650                 sink.lineBreak();
651                 sink.text( getI18nString( locale, "stats.readyrelease.error.convergence" ) );
652             }
653             if ( snapshotCount > 0 )
654             {
655                 sink.lineBreak();
656                 sink.text( getI18nString( locale, "stats.readyrelease.error.snapshots" ) );
657             }
658         }
659         sink.tableCell_();
660         sink.tableRow_();
661 
662         sink.table_();
663     }
664 
665     /**
666      * Check to see if the specified dependency is among the reactor projects.
667      *
668      * @param dependency The dependency to check
669      * @return true if and only if the dependency is a reactor project
670      */
671     private boolean isReactorProject( Dependency dependency )
672     {
673         for ( MavenProject mavenProject : reactorProjects )
674         {
675             if ( mavenProject.getGroupId().equals( dependency.getGroupId() )
676                 && mavenProject.getArtifactId().equals( dependency.getArtifactId() ) )
677             {
678                 if ( getLog().isDebugEnabled() )
679                 {
680                     getLog().debug( dependency + " is a reactor project" );
681                 }
682                 return true;
683             }
684         }
685         return false;
686     }
687 
688     private boolean isReactorBuild()
689     {
690         return this.reactorProjects.size() > 1;
691     }
692 
693     private void iconSuccess( Locale locale, Sink sink )
694     {
695         sink.figure();
696         sink.figureCaption();
697         sink.text( getI18nString( locale, "icon.success" ) );
698         sink.figureCaption_();
699         sink.figureGraphics( IMG_SUCCESS_URL );
700         sink.figure_();
701     }
702 
703     private void iconError( Locale locale, Sink sink )
704     {
705         sink.figure();
706         sink.figureCaption();
707         sink.text( getI18nString( locale, "icon.error" ) );
708         sink.figureCaption_();
709         sink.figureGraphics( IMG_ERROR_URL );
710         sink.figure_();
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 dependencyTreeBuilder.buildDependencyTree( buildingRequest.getProject(), localRepository, filter );
906         }
907         catch ( DependencyTreeBuilderException 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         /** {@inheritDoc} */
983         public int compare( DependencyNode p1, DependencyNode p2 )
984         {
985             return p1.getArtifact().getId().compareTo( p2.getArtifact().getId() );
986         }
987     }
988 
989     /**
990      * Internal object
991      */
992     private class DependencyAnalyzeResult
993     {
994         Map<String, List<ReverseDependencyLink>> all;
995 
996         List<ReverseDependencyLink> snapshots;
997 
998         Map<String, List<ReverseDependencyLink>> conflicting;
999 
1000         public void setAll( Map<String, List<ReverseDependencyLink>> all )
1001         {
1002             this.all = all;
1003         }
1004 
1005         public List<ReverseDependencyLink> getSnapshots()
1006         {
1007             return snapshots;
1008         }
1009 
1010         public void setSnapshots( List<ReverseDependencyLink> snapshots )
1011         {
1012             this.snapshots = snapshots;
1013         }
1014 
1015         public Map<String, List<ReverseDependencyLink>> getConflicting()
1016         {
1017             return conflicting;
1018         }
1019 
1020         public void setConflicting( Map<String, List<ReverseDependencyLink>> conflicting )
1021         {
1022             this.conflicting = conflicting;
1023         }
1024 
1025         public int getDependencyCount()
1026         {
1027             return all.size();
1028         }
1029 
1030         public int getSnapshotCount()
1031         {
1032             return this.snapshots.size();
1033         }
1034 
1035         public int getConflictingCount()
1036         {
1037             return this.conflicting.size();
1038         }
1039 
1040         public int getArtifactCount()
1041         {
1042             int artifactCount = 0;
1043             for ( List<ReverseDependencyLink> depList : this.all.values() )
1044             {
1045                 Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
1046                 artifactCount += artifactMap.size();
1047             }
1048 
1049             return artifactCount;
1050         }
1051     }
1052 
1053 }