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 org.apache.maven.doxia.sink.Sink;
23  import org.apache.maven.doxia.sink.SinkEventAttributeSet;
24  import org.apache.maven.doxia.sink.SinkEventAttributes;
25  import org.apache.maven.model.Dependency;
26  import org.apache.maven.project.MavenProject;
27  import org.apache.maven.reporting.MavenReportException;
28  import org.codehaus.plexus.util.StringUtils;
29  
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.Comparator;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.TreeMap;
38  
39  /**
40   * Generates the Dependency Convergence report for reactor builds.
41   *
42   * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
43   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton </a>
44   * @version $Id: DependencyConvergenceReport.java 1038048 2010-11-23 10:52:14Z vsiveton $
45   * @since 2.0
46   * @goal dependency-convergence
47   * @aggregator
48   */
49  public class DependencyConvergenceReport
50      extends AbstractProjectInfoReport
51  {
52      private static final int PERCENTAGE = 100;
53  
54      // ----------------------------------------------------------------------
55      // Mojo parameters
56      // ----------------------------------------------------------------------
57  
58      /**
59       * The projects in the current build. The effective-POM for
60       * each of these projects will written.
61       *
62       * @parameter expression="${reactorProjects}"
63       * @required
64       * @readonly
65       */
66      private List<MavenProject> reactorProjects;
67  
68      // ----------------------------------------------------------------------
69      // Public methods
70      // ----------------------------------------------------------------------
71  
72      /** {@inheritDoc} */
73      public String getOutputName()
74      {
75          return "dependency-convergence";
76      }
77  
78      @Override
79      protected String getI18Nsection()
80      {
81          return "dependency-convergence";
82      }
83  
84      @Override
85      public boolean canGenerateReport()
86      {
87          // only generate the convergency report if we are running a reactor build
88          return reactorProjects.size() > 1;
89      }
90  
91      // ----------------------------------------------------------------------
92      // Protected methods
93      // ----------------------------------------------------------------------
94  
95      @Override
96      protected void executeReport( Locale locale )
97          throws MavenReportException
98      {
99          Sink sink = getSink();
100 
101         sink.head();
102         sink.title();
103         sink.text( getI18nString( locale, "title" ) );
104         sink.title_();
105         sink.head_();
106 
107         sink.body();
108 
109         sink.section1();
110 
111         sink.sectionTitle1();
112         sink.text( getI18nString( locale, "title" ) );
113         sink.sectionTitle1_();
114 
115         Map<String, List<ReverseDependencyLink>> dependencyMap = getDependencyMap();
116 
117         // legend
118         generateLegend( locale, sink );
119 
120         sink.lineBreak();
121 
122         // stats
123         generateStats( locale, sink, dependencyMap );
124 
125         sink.section1_();
126 
127         // convergence
128         generateConvergence( locale, sink, dependencyMap );
129 
130         sink.body_();
131         sink.flush();
132         sink.close();
133     }
134 
135     // ----------------------------------------------------------------------
136     // Private methods
137     // ----------------------------------------------------------------------
138 
139     /**
140      * Generate the convergence table for all dependencies
141      *
142      * @param locale
143      * @param sink
144      * @param dependencyMap
145      */
146     private void generateConvergence( Locale locale, Sink sink, Map<String, List<ReverseDependencyLink>> dependencyMap )
147     {
148         sink.section2();
149 
150         sink.sectionTitle2();
151         sink.text( getI18nString( locale, "convergence.caption" ) );
152         sink.sectionTitle2_();
153 
154         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : dependencyMap.entrySet() )
155         {
156             String key = entry.getKey();
157             List<ReverseDependencyLink> depList = entry.getValue();
158 
159             sink.section3();
160             sink.sectionTitle3();
161             sink.text( key );
162             sink.sectionTitle3_();
163 
164             generateDependencyDetails( sink, depList );
165 
166             sink.section3_();
167         }
168 
169         sink.section2_();
170     }
171 
172     /**
173      * Generate the detail table for a given dependency
174      *
175      * @param sink
176      * @param depList
177      */
178     private void generateDependencyDetails( Sink sink, List<ReverseDependencyLink> depList )
179     {
180         sink.table();
181 
182         Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
183 
184         sink.tableRow();
185 
186         sink.tableCell( );
187         if ( artifactMap.size() > 1 )
188         {
189             iconError( sink );
190         }
191         else
192         {
193             iconSuccess( sink );
194         }
195         sink.tableCell_();
196 
197         sink.tableCell();
198 
199         sink.table();
200 
201         for ( String version : artifactMap.keySet() )
202         {
203             sink.tableRow();
204             sink.tableCell( new SinkEventAttributeSet( new String[] {SinkEventAttributes.WIDTH, "25%"} ) );
205             sink.text( version );
206             sink.tableCell_();
207 
208             sink.tableCell();
209             generateVersionDetails( sink, artifactMap, version );
210             sink.tableCell_();
211 
212             sink.tableRow_();
213         }
214         sink.table_();
215         sink.tableCell_();
216 
217         sink.tableRow_();
218 
219         sink.table_();
220     }
221 
222     private void generateVersionDetails( Sink sink, Map<String, List<ReverseDependencyLink>> artifactMap, String version )
223     {
224         sink.numberedList( 1 ); // Use lower alpha numbering
225         List<ReverseDependencyLink> depList = artifactMap.get( version );
226         Collections.sort( depList, new ReverseDependencyLinkComparator() );
227 
228         for ( ReverseDependencyLink rdl : depList )
229         {
230             sink.numberedListItem();
231             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
232             {
233                 sink.link( rdl.project.getUrl() );
234             }
235             sink.text( rdl.project.getGroupId() + ":" + rdl.project.getArtifactId() );
236             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
237             {
238                 sink.link_();
239             }
240             sink.numberedListItem_();
241         }
242         sink.numberedList_();
243     }
244 
245     /**
246      * Produce a Map of relationships between dependencies (its version) and
247      * reactor projects.
248      *
249      * This is the structure of the Map:
250      * <pre>
251      * +--------------------+----------------------------------+
252      * | key                | value                            |
253      * +--------------------+----------------------------------+
254      * | version of a       | A List of ReverseDependencyLinks |
255      * | dependency         | which each look like this:       |
256      * |                    | +------------+-----------------+ |
257      * |                    | | dependency | reactor project | |
258      * |                    | +------------+-----------------+ |
259      * +--------------------+----------------------------------+
260      * </pre>
261      *
262      * @return A Map of sorted unique artifacts
263      */
264     private Map<String, List<ReverseDependencyLink>> getSortedUniqueArtifactMap( List<ReverseDependencyLink> depList )
265     {
266         Map<String, List<ReverseDependencyLink>> uniqueArtifactMap = new TreeMap<String, List<ReverseDependencyLink>>();
267 
268         for ( ReverseDependencyLink rdl : depList )
269         {
270             String key = rdl.getDependency().getVersion();
271             List<ReverseDependencyLink> projectList = uniqueArtifactMap.get( key );
272             if ( projectList == null )
273             {
274                 projectList = new ArrayList<ReverseDependencyLink>();
275             }
276             projectList.add( rdl );
277             uniqueArtifactMap.put( key, projectList );
278         }
279 
280         return uniqueArtifactMap;
281     }
282 
283     /**
284      * Generate the legend table
285      *
286      * @param locale
287      * @param sink
288      */
289     private void generateLegend( Locale locale, Sink sink )
290     {
291         sink.table();
292         sink.tableCaption();
293         sink.bold();
294         sink.text( getI18nString( locale, "legend" ) );
295         sink.bold_();
296         sink.tableCaption_();
297 
298         sink.tableRow();
299 
300         sink.tableCell( );
301         iconSuccess( sink );
302         sink.tableCell_();
303         sink.tableCell();
304         sink.text( getI18nString( locale, "legend.shared" ) );
305         sink.tableCell_();
306 
307         sink.tableRow_();
308 
309         sink.tableRow();
310 
311         sink.tableCell( );
312         iconError( sink );
313         sink.tableCell_();
314         sink.tableCell();
315         sink.text( getI18nString( locale, "legend.different" ) );
316         sink.tableCell_();
317 
318         sink.tableRow_();
319 
320         sink.table_();
321     }
322 
323     /**
324      * Generate the statistic table
325      *
326      * @param locale
327      * @param sink
328      * @param dependencyMap
329      */
330     private void generateStats( Locale locale, Sink sink, Map<String, List<ReverseDependencyLink>> dependencyMap )
331     {
332         int depCount = dependencyMap.size();
333         int artifactCount = 0;
334         int snapshotCount = 0;
335 
336         for ( List<ReverseDependencyLink> depList : dependencyMap.values() )
337         {
338             Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
339             snapshotCount += countSnapshots( artifactMap );
340             artifactCount += artifactMap.size();
341         }
342 
343         int convergence = (int) ( ( (double) depCount / (double) artifactCount ) * PERCENTAGE );
344 
345         // Create report
346         sink.table();
347         sink.tableCaption();
348         sink.bold();
349         sink.text( getI18nString( locale, "stats.caption" ) );
350         sink.bold_();
351         sink.tableCaption_();
352 
353         sink.tableRow();
354         sink.tableHeaderCell( );
355         sink.text( getI18nString( locale, "stats.subprojects" ) );
356         sink.tableHeaderCell_();
357         sink.tableCell();
358         sink.text( String.valueOf( reactorProjects.size() ) );
359         sink.tableCell_();
360         sink.tableRow_();
361 
362         sink.tableRow();
363         sink.tableHeaderCell( );
364         sink.text( getI18nString( locale, "stats.dependencies" ) );
365         sink.tableHeaderCell_();
366         sink.tableCell();
367         sink.text( String.valueOf( depCount ) );
368         sink.tableCell_();
369         sink.tableRow_();
370 
371         sink.tableRow();
372         sink.tableHeaderCell( );
373         sink.text( getI18nString( locale, "stats.artifacts" ) );
374         sink.tableHeaderCell_();
375         sink.tableCell();
376         sink.text( String.valueOf( artifactCount ) );
377         sink.tableCell_();
378         sink.tableRow_();
379 
380         sink.tableRow();
381         sink.tableHeaderCell( );
382         sink.text( getI18nString( locale, "stats.snapshots" ) );
383         sink.tableHeaderCell_();
384         sink.tableCell();
385         sink.text( String.valueOf( snapshotCount ) );
386         sink.tableCell_();
387         sink.tableRow_();
388 
389         sink.tableRow();
390         sink.tableHeaderCell( );
391         sink.text( getI18nString( locale, "stats.convergence" ) );
392         sink.tableHeaderCell_();
393         sink.tableCell();
394         if ( convergence < PERCENTAGE )
395         {
396             iconError( sink );
397         }
398         else
399         {
400             iconSuccess( sink );
401         }
402         sink.nonBreakingSpace();
403         sink.bold();
404         sink.text( String.valueOf( convergence ) + "%" );
405         sink.bold_();
406         sink.tableCell_();
407         sink.tableRow_();
408 
409         sink.tableRow();
410         sink.tableHeaderCell( );
411         sink.text( getI18nString( locale, "stats.readyrelease" ) );
412         sink.tableHeaderCell_();
413         sink.tableCell();
414         if ( convergence >= PERCENTAGE && snapshotCount <= 0 )
415         {
416             iconSuccess( sink );
417             sink.nonBreakingSpace();
418             sink.bold();
419             sink.text( getI18nString( locale, "stats.readyrelease.success" ) );
420             sink.bold_();
421         }
422         else
423         {
424             iconError( sink );
425             sink.nonBreakingSpace();
426             sink.bold();
427             sink.text( getI18nString( locale, "stats.readyrelease.error" ) );
428             sink.bold_();
429             if ( convergence < PERCENTAGE )
430             {
431                 sink.lineBreak();
432                 sink.text( getI18nString( locale, "stats.readyrelease.error.convergence" ) );
433             }
434             if ( snapshotCount > 0 )
435             {
436                 sink.lineBreak();
437                 sink.text( getI18nString( locale, "stats.readyrelease.error.snapshots" ) );
438             }
439         }
440         sink.tableCell_();
441         sink.tableRow_();
442 
443         sink.table_();
444     }
445 
446     private int countSnapshots( Map<String, List<ReverseDependencyLink>> artifactMap )
447     {
448         int count = 0;
449         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : artifactMap.entrySet() )
450         {
451             String version = entry.getKey();
452             boolean isReactorProject = false;
453 
454             Iterator<ReverseDependencyLink> iterator = entry.getValue().iterator();
455             // It if enough to check just the first dependency here, because
456             // the dependency is the same in all the RDLs in the List. It's the
457             // reactorProjects that are different.
458             if ( iterator.hasNext() )
459             {
460                 ReverseDependencyLink rdl = iterator.next();
461                 if ( isReactorProject( rdl.getDependency() ) )
462                 {
463                     isReactorProject = true;
464                 }
465             }
466 
467             if ( version.endsWith( "-SNAPSHOT" ) && !isReactorProject )
468             {
469                 count++;
470             }
471         }
472         return count;
473     }
474 
475     /**
476      * Check to see if the specified dependency is among the reactor projects.
477      *
478      * @param dependency The dependency to check
479      * @return true if and only if the dependency is a reactor project
480      */
481     private boolean isReactorProject( Dependency dependency )
482     {
483         for ( MavenProject mavenProject : reactorProjects )
484         {
485             if ( mavenProject.getGroupId().equals( dependency.getGroupId() )
486                 && mavenProject.getArtifactId().equals( dependency.getArtifactId() ) )
487             {
488                 if ( getLog().isDebugEnabled() )
489                 {
490                     getLog().debug( dependency + " is a reactor project" );
491                 }
492                 return true;
493             }
494         }
495         return false;
496     }
497 
498     private void iconSuccess( Sink sink )
499     {
500         sink.figure();
501         sink.figureCaption();
502         sink.text( "success" );
503         sink.figureCaption_();
504         sink.figureGraphics( "images/icon_success_sml.gif" );
505         sink.figure_();
506     }
507 
508     private void iconError( Sink sink )
509     {
510         sink.figure();
511         sink.figureCaption();
512         sink.text( "error" );
513         sink.figureCaption_();
514         sink.figureGraphics( "images/icon_error_sml.gif" );
515         sink.figure_();
516     }
517 
518     /**
519      * Produce a Map of relationships between dependencies
520      * (its groupId:artifactId) and reactor projects.
521      *
522      * This is the structure of the Map:
523      * <pre>
524      * +--------------------+----------------------------------+
525      * | key                | value                            |
526      * +--------------------+----------------------------------+
527      * | groupId:artifactId | A List of ReverseDependencyLinks |
528      * | of a dependency    | which each look like this:       |
529      * |                    | +------------+-----------------+ |
530      * |                    | | dependency | reactor project | |
531      * |                    | +------------+-----------------+ |
532      * +--------------------+----------------------------------+
533      * </pre>
534      *
535      * @return A Map of relationships between dependencies and reactor projects
536      */
537     private Map<String, List<ReverseDependencyLink>> getDependencyMap()
538     {
539         Map<String, List<ReverseDependencyLink>> dependencyMap = new TreeMap<String, List<ReverseDependencyLink>>();
540 
541         for ( MavenProject reactorProject : reactorProjects )
542         {
543             @SuppressWarnings( "unchecked" )
544             Iterator<Dependency> itdep = reactorProject.getDependencies().iterator();
545             while ( itdep.hasNext() )
546             {
547                 Dependency dep = itdep.next();
548                 String key = dep.getGroupId() + ":" + dep.getArtifactId();
549                 List<ReverseDependencyLink> depList = dependencyMap.get( key );
550                 if ( depList == null )
551                 {
552                     depList = new ArrayList<ReverseDependencyLink>();
553                 }
554                 depList.add( new ReverseDependencyLink( dep, reactorProject ) );
555                 dependencyMap.put( key, depList );
556             }
557         }
558 
559         return dependencyMap;
560     }
561 
562     /**
563      * Internal object
564      */
565     private static class ReverseDependencyLink
566     {
567         private Dependency dependency;
568 
569         protected MavenProject project;
570 
571         ReverseDependencyLink( Dependency dependency, MavenProject project )
572         {
573             this.dependency = dependency;
574             this.project = project;
575         }
576 
577         public Dependency getDependency()
578         {
579             return dependency;
580         }
581 
582         public MavenProject getProject()
583         {
584             return project;
585         }
586 
587         @Override
588         public String toString()
589         {
590             return project.getId();
591         }
592     }
593 
594     /**
595      * Internal ReverseDependencyLink comparator
596      */
597     static class ReverseDependencyLinkComparator
598         implements Comparator<ReverseDependencyLink>
599     {
600         /** {@inheritDoc} */
601         public int compare( ReverseDependencyLink p1, ReverseDependencyLink p2 )
602         {
603             return p1.getProject().getId().compareTo( p2.getProject().getId() );
604         }
605     }
606 }