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