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