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