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         SinkEventAttributes attributes =
696             new SinkEventAttributeSet( SinkEventAttributes.ALT, getI18nString( locale, "icon.success" ) );
697         sink.figureGraphics( IMG_SUCCESS_URL, attributes );
698     }
699 
700     private void iconError( Locale locale, Sink sink )
701     {
702         SinkEventAttributes attributes =
703             new SinkEventAttributeSet( SinkEventAttributes.ALT, getI18nString( locale, "icon.error" ) );
704         sink.figureGraphics( IMG_ERROR_URL, attributes );
705     }
706 
707     /**
708      * Produce a DependencyAnalyzeResult, it contains conflicting dependencies map, snapshot dependencies map and all
709      * dependencies map. Map structure is the relationships between dependencies (its groupId:artifactId) and reactor
710      * projects. This is the structure of the Map:
711      *
712      * <pre>
713      * +--------------------+----------------------------------+---------------|
714      * | key                | value                                            |
715      * +--------------------+----------------------------------+---------------|
716      * | groupId:artifactId | A List of ReverseDependencyLinks                 |
717      * | of a dependency    | which each look like this:                       |
718      * |                    | +------------+-----------------+-----------------|
719      * |                    | | dependency | reactor project | dependency node |
720      * |                    | +------------+-----------------+-----------------|
721      * +--------------------+--------------------------------------------------|
722      * </pre>
723      *
724      * @return DependencyAnalyzeResult contains conflicting dependencies map, snapshot dependencies map and all
725      *         dependencies map.
726      * @throws MavenReportException
727      */
728     private DependencyAnalyzeResult analyzeDependencyTree()
729         throws MavenReportException
730     {
731         Map<String, List<ReverseDependencyLink>> conflictingDependencyMap = new TreeMap<>();
732         Map<String, List<ReverseDependencyLink>> allDependencies = new TreeMap<>();
733 
734         ProjectBuildingRequest buildingRequest =
735             new DefaultProjectBuildingRequest( getSession().getProjectBuildingRequest() );
736 
737         for ( MavenProject reactorProject : reactorProjects )
738         {
739             buildingRequest.setProject( reactorProject );
740 
741             DependencyNode node = getNode( buildingRequest );
742 
743             this.projectMap.put( reactorProject, node );
744 
745             getConflictingDependencyMap( conflictingDependencyMap, reactorProject, node );
746 
747             getAllDependencyMap( allDependencies, reactorProject, node );
748         }
749 
750         return populateDependencyAnalyzeResult( conflictingDependencyMap, allDependencies );
751     }
752 
753     /**
754      * Produce DependencyAnalyzeResult base on conflicting dependencies map, all dependencies map.
755      *
756      * @param conflictingDependencyMap
757      * @param allDependencies
758      * @return DependencyAnalyzeResult contains conflicting dependencies map, snapshot dependencies map and all
759      *         dependencies map.
760      */
761     private DependencyAnalyzeResult populateDependencyAnalyzeResult(
762             Map<String, List<ReverseDependencyLink>> conflictingDependencyMap,
763             Map<String, List<ReverseDependencyLink>> allDependencies )
764     {
765         DependencyAnalyzeResult dependencyResult = new DependencyAnalyzeResult();
766 
767         dependencyResult.setAll( allDependencies );
768         dependencyResult.setConflicting( conflictingDependencyMap );
769 
770         List<ReverseDependencyLink> snapshots = getSnapshotDependencies( allDependencies );
771         dependencyResult.setSnapshots( snapshots );
772         return dependencyResult;
773     }
774 
775     /**
776      * Get conflicting dependency map base on specified dependency node.
777      *
778      * @param conflictingDependencyMap
779      * @param reactorProject
780      * @param node
781      */
782     private void getConflictingDependencyMap( Map<String, List<ReverseDependencyLink>> conflictingDependencyMap,
783                                               MavenProject reactorProject, DependencyNode node )
784     {
785         DependencyVersionMap visitor = new DependencyVersionMap();
786         visitor.setUniqueVersions( true );
787 
788         node.accept( visitor );
789 
790         for ( List<DependencyNode> nodes : visitor.getConflictedVersionNumbers() )
791         {
792             DependencyNode dependencyNode = nodes.get( 0 );
793 
794             String key = dependencyNode.getArtifact().getGroupId() + ":" + dependencyNode.getArtifact().getArtifactId();
795 
796             List<ReverseDependencyLink> dependencyList = conflictingDependencyMap.get( key );
797             if ( dependencyList == null )
798             {
799                 dependencyList = new ArrayList<>();
800             }
801 
802             dependencyList.add( new ReverseDependencyLink(
803                     toDependency( dependencyNode.getArtifact() ), reactorProject ) );
804 
805             for ( DependencyNode workNode : nodes.subList( 1, nodes.size() ) )
806             {
807                 dependencyList.add( new ReverseDependencyLink(
808                         toDependency( workNode.getArtifact() ), reactorProject ) );
809             }
810 
811             conflictingDependencyMap.put( key, dependencyList );
812         }
813     }
814 
815     /**
816      * Get all dependencies (both directive & transitive dependencies) by specified dependency node.
817      *
818      * @param allDependencies
819      * @param reactorProject
820      * @param node
821      */
822     private void getAllDependencyMap( Map<String, List<ReverseDependencyLink>> allDependencies,
823                                       MavenProject reactorProject, DependencyNode node )
824     {
825         Set<Artifact> artifacts = getAllDescendants( node );
826 
827         for ( Artifact art : artifacts )
828         {
829             String key = art.getGroupId() + ":" + art.getArtifactId();
830 
831             List<ReverseDependencyLink> reverseDepependencies = allDependencies.get( key );
832             if ( reverseDepependencies == null )
833             {
834                 reverseDepependencies = new ArrayList<>();
835             }
836 
837             if ( !containsDependency( reverseDepependencies, art ) )
838             {
839                 reverseDepependencies.add( new ReverseDependencyLink( toDependency( art ), reactorProject ) );
840             }
841 
842             allDependencies.put( key, reverseDepependencies );
843         }
844     }
845 
846     /**
847      * Convert Artifact to Dependency
848      *
849      * @param artifact
850      * @return Dependency object
851      */
852     private Dependency toDependency( Artifact artifact )
853     {
854         Dependency dependency = new Dependency();
855         dependency.setGroupId( artifact.getGroupId() );
856         dependency.setArtifactId( artifact.getArtifactId() );
857         dependency.setVersion( artifact.getVersion() );
858         dependency.setClassifier( artifact.getClassifier() );
859         dependency.setScope( artifact.getScope() );
860 
861         return dependency;
862     }
863 
864     /**
865      * To check whether dependency list contains a given artifact.
866      *
867      * @param reverseDependencies
868      * @param art
869      * @return contains:true; Not contains:false;
870      */
871     private boolean containsDependency( List<ReverseDependencyLink> reverseDependencies, Artifact art )
872     {
873 
874         for ( ReverseDependencyLink revDependency : reverseDependencies )
875         {
876             Dependency dep = revDependency.getDependency();
877             if ( dep.getGroupId().equals( art.getGroupId() ) && dep.getArtifactId().equals( art.getArtifactId() )
878                 && dep.getVersion().equals( art.getVersion() ) )
879             {
880                 return true;
881             }
882         }
883 
884         return false;
885     }
886 
887     /**
888      * Get root node of dependency tree for a given project
889      *
890      * @param buildingRequest
891      * @return root node of dependency tree
892      * @throws MavenReportException
893      */
894     private DependencyNode getNode( ProjectBuildingRequest buildingRequest )
895         throws MavenReportException
896     {
897         try
898         {
899             return dependencyTreeBuilder.buildDependencyTree( buildingRequest.getProject(), localRepository, filter );
900         }
901         catch ( DependencyTreeBuilderException e )
902         {
903             throw new MavenReportException( "Could not build dependency tree: " + e.getMessage(), e );
904         }
905     }
906 
907     /**
908      * Get all descendants nodes for a given dependency node.
909      *
910      * @param node
911      * @return set of descendants artifacts.
912      */
913     private Set<Artifact> getAllDescendants( DependencyNode node )
914     {
915         Set<Artifact> children = null;
916         if ( node.getChildren() != null )
917         {
918             children = new HashSet<>();
919             for ( DependencyNode depNode : node.getChildren() )
920             {
921                 children.add( depNode.getArtifact() );
922                 Set<Artifact> subNodes = getAllDescendants( depNode );
923                 if ( subNodes != null )
924                 {
925                     children.addAll( subNodes );
926                 }
927             }
928         }
929         return children;
930     }
931 
932     private int calculateConvergence( DependencyAnalyzeResult result )
933     {
934         return (int) ( ( (double) result.getDependencyCount()
935                 / (double) result.getArtifactCount() ) * FULL_CONVERGENCE );
936     }
937 
938     /**
939      * Internal object
940      */
941     private static class ReverseDependencyLink
942     {
943         private Dependency dependency;
944 
945         protected MavenProject project;
946 
947         ReverseDependencyLink( Dependency dependency, MavenProject project )
948         {
949             this.dependency = dependency;
950             this.project = project;
951         }
952 
953         public Dependency getDependency()
954         {
955             return dependency;
956         }
957 
958         public MavenProject getProject()
959         {
960             return project;
961         }
962 
963         @Override
964         public String toString()
965         {
966             return project.getId();
967         }
968     }
969 
970     /**
971      * Internal ReverseDependencyLink comparator
972      */
973     static class DependencyNodeComparator
974         implements Comparator<DependencyNode>
975     {
976         /** {@inheritDoc} */
977         public int compare( DependencyNode p1, DependencyNode p2 )
978         {
979             return p1.getArtifact().getId().compareTo( p2.getArtifact().getId() );
980         }
981     }
982 
983     /**
984      * Internal object
985      */
986     private class DependencyAnalyzeResult
987     {
988         Map<String, List<ReverseDependencyLink>> all;
989 
990         List<ReverseDependencyLink> snapshots;
991 
992         Map<String, List<ReverseDependencyLink>> conflicting;
993 
994         public void setAll( Map<String, List<ReverseDependencyLink>> all )
995         {
996             this.all = all;
997         }
998 
999         public List<ReverseDependencyLink> getSnapshots()
1000         {
1001             return snapshots;
1002         }
1003 
1004         public void setSnapshots( List<ReverseDependencyLink> snapshots )
1005         {
1006             this.snapshots = snapshots;
1007         }
1008 
1009         public Map<String, List<ReverseDependencyLink>> getConflicting()
1010         {
1011             return conflicting;
1012         }
1013 
1014         public void setConflicting( Map<String, List<ReverseDependencyLink>> conflicting )
1015         {
1016             this.conflicting = conflicting;
1017         }
1018 
1019         public int getDependencyCount()
1020         {
1021             return all.size();
1022         }
1023 
1024         public int getSnapshotCount()
1025         {
1026             return this.snapshots.size();
1027         }
1028 
1029         public int getConflictingCount()
1030         {
1031             return this.conflicting.size();
1032         }
1033 
1034         public int getArtifactCount()
1035         {
1036             int artifactCount = 0;
1037             for ( List<ReverseDependencyLink> depList : this.all.values() )
1038             {
1039                 Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
1040                 artifactCount += artifactMap.size();
1041             }
1042 
1043             return artifactCount;
1044         }
1045     }
1046 
1047 }