Coverage Report - org.apache.maven.report.projectinfo.DependencyConvergenceReport
 
Classes in this File Line Coverage Branch Coverage Complexity
DependencyConvergenceReport
92 %
227/247
61 %
33/54
0
DependencyConvergenceReport$ReverseDependencyLink
86 %
6/7
N/A
0
DependencyConvergenceReport$ReverseDependencyLinkComparator
100 %
2/2
N/A
0
 
 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  1
 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  2
         return "dependency-convergence";
 76  
     }
 77  
 
 78  
     @Override
 79  
     protected String getI18Nsection()
 80  
     {
 81  15
         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  2
         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  1
         Sink sink = getSink();
 100  
 
 101  1
         sink.head();
 102  1
         sink.title();
 103  1
         sink.text( getI18nString( locale, "title" ) );
 104  1
         sink.title_();
 105  1
         sink.head_();
 106  
 
 107  1
         sink.body();
 108  
 
 109  1
         sink.section1();
 110  
 
 111  1
         sink.sectionTitle1();
 112  1
         sink.text( getI18nString( locale, "title" ) );
 113  1
         sink.sectionTitle1_();
 114  
 
 115  1
         Map<String, List<ReverseDependencyLink>> dependencyMap = getDependencyMap();
 116  
 
 117  
         // legend
 118  1
         generateLegend( locale, sink );
 119  
 
 120  1
         sink.lineBreak();
 121  
 
 122  
         // stats
 123  1
         generateStats( locale, sink, dependencyMap );
 124  
 
 125  1
         sink.section1_();
 126  
 
 127  
         // convergence
 128  1
         generateConvergence( locale, sink, dependencyMap );
 129  
 
 130  1
         sink.body_();
 131  1
         sink.flush();
 132  1
         sink.close();
 133  1
     }
 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  1
         sink.section2();
 149  
 
 150  1
         sink.sectionTitle2();
 151  1
         sink.text( getI18nString( locale, "convergence.caption" ) );
 152  1
         sink.sectionTitle2_();
 153  
 
 154  1
         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : dependencyMap.entrySet() )
 155  
         {
 156  1
             String key = entry.getKey();
 157  1
             List<ReverseDependencyLink> depList = entry.getValue();
 158  
 
 159  1
             sink.section3();
 160  1
             sink.sectionTitle3();
 161  1
             sink.text( key );
 162  1
             sink.sectionTitle3_();
 163  
 
 164  1
             generateDependencyDetails( sink, depList );
 165  
 
 166  1
             sink.section3_();
 167  1
         }
 168  
 
 169  1
         sink.section2_();
 170  1
     }
 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  1
         sink.table();
 181  
 
 182  1
         Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
 183  
 
 184  1
         sink.tableRow();
 185  
 
 186  1
         sink.tableCell( );
 187  1
         if ( artifactMap.size() > 1 )
 188  
         {
 189  0
             iconError( sink );
 190  
         }
 191  
         else
 192  
         {
 193  1
             iconSuccess( sink );
 194  
         }
 195  1
         sink.tableCell_();
 196  
 
 197  1
         sink.tableCell();
 198  
 
 199  1
         sink.table();
 200  
 
 201  1
         for ( String version : artifactMap.keySet() )
 202  
         {
 203  1
             sink.tableRow();
 204  1
             sink.tableCell( new SinkEventAttributeSet( new String[] {SinkEventAttributes.WIDTH, "25%"} ) );
 205  1
             sink.text( version );
 206  1
             sink.tableCell_();
 207  
 
 208  1
             sink.tableCell();
 209  1
             generateVersionDetails( sink, artifactMap, version );
 210  1
             sink.tableCell_();
 211  
 
 212  1
             sink.tableRow_();
 213  
         }
 214  1
         sink.table_();
 215  1
         sink.tableCell_();
 216  
 
 217  1
         sink.tableRow_();
 218  
 
 219  1
         sink.table_();
 220  1
     }
 221  
 
 222  
     private void generateVersionDetails( Sink sink, Map<String, List<ReverseDependencyLink>> artifactMap, String version )
 223  
     {
 224  1
         sink.numberedList( 1 ); // Use lower alpha numbering
 225  1
         List<ReverseDependencyLink> depList = artifactMap.get( version );
 226  1
         Collections.sort( depList, new ReverseDependencyLinkComparator() );
 227  
 
 228  1
         for ( ReverseDependencyLink rdl : depList )
 229  
         {
 230  2
             sink.numberedListItem();
 231  2
             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
 232  
             {
 233  0
                 sink.link( rdl.project.getUrl() );
 234  
             }
 235  2
             sink.text( rdl.project.getGroupId() + ":" + rdl.project.getArtifactId() );
 236  2
             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
 237  
             {
 238  0
                 sink.link_();
 239  
             }
 240  2
             sink.numberedListItem_();
 241  
         }
 242  1
         sink.numberedList_();
 243  1
     }
 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  2
         Map<String, List<ReverseDependencyLink>> uniqueArtifactMap = new TreeMap<String, List<ReverseDependencyLink>>();
 267  
 
 268  2
         for ( ReverseDependencyLink rdl : depList )
 269  
         {
 270  4
             String key = rdl.getDependency().getVersion();
 271  4
             List<ReverseDependencyLink> projectList = uniqueArtifactMap.get( key );
 272  4
             if ( projectList == null )
 273  
             {
 274  2
                 projectList = new ArrayList<ReverseDependencyLink>();
 275  
             }
 276  4
             projectList.add( rdl );
 277  4
             uniqueArtifactMap.put( key, projectList );
 278  4
         }
 279  
 
 280  2
         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  1
         sink.table();
 292  1
         sink.tableCaption();
 293  1
         sink.bold();
 294  1
         sink.text( getI18nString( locale, "legend" ) );
 295  1
         sink.bold_();
 296  1
         sink.tableCaption_();
 297  
 
 298  1
         sink.tableRow();
 299  
 
 300  1
         sink.tableCell( );
 301  1
         iconSuccess( sink );
 302  1
         sink.tableCell_();
 303  1
         sink.tableCell();
 304  1
         sink.text( getI18nString( locale, "legend.shared" ) );
 305  1
         sink.tableCell_();
 306  
 
 307  1
         sink.tableRow_();
 308  
 
 309  1
         sink.tableRow();
 310  
 
 311  1
         sink.tableCell( );
 312  1
         iconError( sink );
 313  1
         sink.tableCell_();
 314  1
         sink.tableCell();
 315  1
         sink.text( getI18nString( locale, "legend.different" ) );
 316  1
         sink.tableCell_();
 317  
 
 318  1
         sink.tableRow_();
 319  
 
 320  1
         sink.table_();
 321  1
     }
 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  1
         int depCount = dependencyMap.size();
 333  1
         int artifactCount = 0;
 334  1
         int snapshotCount = 0;
 335  
 
 336  1
         for ( List<ReverseDependencyLink> depList : dependencyMap.values() )
 337  
         {
 338  1
             Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
 339  1
             snapshotCount += countSnapshots( artifactMap );
 340  1
             artifactCount += artifactMap.size();
 341  1
         }
 342  
 
 343  1
         int convergence = (int) ( ( (double) depCount / (double) artifactCount ) * PERCENTAGE );
 344  
 
 345  
         // Create report
 346  1
         sink.table();
 347  1
         sink.tableCaption();
 348  1
         sink.bold();
 349  1
         sink.text( getI18nString( locale, "stats.caption" ) );
 350  1
         sink.bold_();
 351  1
         sink.tableCaption_();
 352  
 
 353  1
         sink.tableRow();
 354  1
         sink.tableHeaderCell( );
 355  1
         sink.text( getI18nString( locale, "stats.subprojects" ) );
 356  1
         sink.tableHeaderCell_();
 357  1
         sink.tableCell();
 358  1
         sink.text( String.valueOf( reactorProjects.size() ) );
 359  1
         sink.tableCell_();
 360  1
         sink.tableRow_();
 361  
 
 362  1
         sink.tableRow();
 363  1
         sink.tableHeaderCell( );
 364  1
         sink.text( getI18nString( locale, "stats.dependencies" ) );
 365  1
         sink.tableHeaderCell_();
 366  1
         sink.tableCell();
 367  1
         sink.text( String.valueOf( depCount ) );
 368  1
         sink.tableCell_();
 369  1
         sink.tableRow_();
 370  
 
 371  1
         sink.tableRow();
 372  1
         sink.tableHeaderCell( );
 373  1
         sink.text( getI18nString( locale, "stats.artifacts" ) );
 374  1
         sink.tableHeaderCell_();
 375  1
         sink.tableCell();
 376  1
         sink.text( String.valueOf( artifactCount ) );
 377  1
         sink.tableCell_();
 378  1
         sink.tableRow_();
 379  
 
 380  1
         sink.tableRow();
 381  1
         sink.tableHeaderCell( );
 382  1
         sink.text( getI18nString( locale, "stats.snapshots" ) );
 383  1
         sink.tableHeaderCell_();
 384  1
         sink.tableCell();
 385  1
         sink.text( String.valueOf( snapshotCount ) );
 386  1
         sink.tableCell_();
 387  1
         sink.tableRow_();
 388  
 
 389  1
         sink.tableRow();
 390  1
         sink.tableHeaderCell( );
 391  1
         sink.text( getI18nString( locale, "stats.convergence" ) );
 392  1
         sink.tableHeaderCell_();
 393  1
         sink.tableCell();
 394  1
         if ( convergence < PERCENTAGE )
 395  
         {
 396  0
             iconError( sink );
 397  
         }
 398  
         else
 399  
         {
 400  1
             iconSuccess( sink );
 401  
         }
 402  1
         sink.nonBreakingSpace();
 403  1
         sink.bold();
 404  1
         sink.text( String.valueOf( convergence ) + "%" );
 405  1
         sink.bold_();
 406  1
         sink.tableCell_();
 407  1
         sink.tableRow_();
 408  
 
 409  1
         sink.tableRow();
 410  1
         sink.tableHeaderCell( );
 411  1
         sink.text( getI18nString( locale, "stats.readyrelease" ) );
 412  1
         sink.tableHeaderCell_();
 413  1
         sink.tableCell();
 414  1
         if ( convergence >= PERCENTAGE && snapshotCount <= 0 )
 415  
         {
 416  1
             iconSuccess( sink );
 417  1
             sink.nonBreakingSpace();
 418  1
             sink.bold();
 419  1
             sink.text( getI18nString( locale, "stats.readyrelease.success" ) );
 420  1
             sink.bold_();
 421  
         }
 422  
         else
 423  
         {
 424  0
             iconError( sink );
 425  0
             sink.nonBreakingSpace();
 426  0
             sink.bold();
 427  0
             sink.text( getI18nString( locale, "stats.readyrelease.error" ) );
 428  0
             sink.bold_();
 429  0
             if ( convergence < PERCENTAGE )
 430  
             {
 431  0
                 sink.lineBreak();
 432  0
                 sink.text( getI18nString( locale, "stats.readyrelease.error.convergence" ) );
 433  
             }
 434  0
             if ( snapshotCount > 0 )
 435  
             {
 436  0
                 sink.lineBreak();
 437  0
                 sink.text( getI18nString( locale, "stats.readyrelease.error.snapshots" ) );
 438  
             }
 439  
         }
 440  1
         sink.tableCell_();
 441  1
         sink.tableRow_();
 442  
 
 443  1
         sink.table_();
 444  1
     }
 445  
 
 446  
     private int countSnapshots( Map<String, List<ReverseDependencyLink>> artifactMap )
 447  
     {
 448  1
         int count = 0;
 449  1
         for ( Map.Entry<String, List<ReverseDependencyLink>> entry : artifactMap.entrySet() )
 450  
         {
 451  1
             String version = entry.getKey();
 452  1
             boolean isReactorProject = false;
 453  
 
 454  1
             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  1
             if ( iterator.hasNext() )
 459  
             {
 460  1
                 ReverseDependencyLink rdl = iterator.next();
 461  1
                 if ( isReactorProject( rdl.getDependency() ) )
 462  
                 {
 463  0
                     isReactorProject = true;
 464  
                 }
 465  
             }
 466  
 
 467  1
             if ( version.endsWith( "-SNAPSHOT" ) && !isReactorProject )
 468  
             {
 469  0
                 count++;
 470  
             }
 471  1
         }
 472  1
         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  1
         for ( MavenProject mavenProject : reactorProjects )
 484  
         {
 485  2
             if ( mavenProject.getGroupId().equals( dependency.getGroupId() )
 486  
                 && mavenProject.getArtifactId().equals( dependency.getArtifactId() ) )
 487  
             {
 488  0
                 if ( getLog().isDebugEnabled() )
 489  
                 {
 490  0
                     getLog().debug( dependency + " is a reactor project" );
 491  
                 }
 492  0
                 return true;
 493  
             }
 494  
         }
 495  1
         return false;
 496  
     }
 497  
 
 498  
     private void iconSuccess( Sink sink )
 499  
     {
 500  4
         sink.figure();
 501  4
         sink.figureCaption();
 502  4
         sink.text( "success" );
 503  4
         sink.figureCaption_();
 504  4
         sink.figureGraphics( "images/icon_success_sml.gif" );
 505  4
         sink.figure_();
 506  4
     }
 507  
 
 508  
     private void iconError( Sink sink )
 509  
     {
 510  1
         sink.figure();
 511  1
         sink.figureCaption();
 512  1
         sink.text( "error" );
 513  1
         sink.figureCaption_();
 514  1
         sink.figureGraphics( "images/icon_error_sml.gif" );
 515  1
         sink.figure_();
 516  1
     }
 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  1
         Map<String, List<ReverseDependencyLink>> dependencyMap = new TreeMap<String, List<ReverseDependencyLink>>();
 540  
 
 541  1
         for ( MavenProject reactorProject : reactorProjects )
 542  
         {
 543  
             @SuppressWarnings( "unchecked" )
 544  2
             Iterator<Dependency> itdep = reactorProject.getDependencies().iterator();
 545  4
             while ( itdep.hasNext() )
 546  
             {
 547  2
                 Dependency dep = itdep.next();
 548  2
                 String key = dep.getGroupId() + ":" + dep.getArtifactId();
 549  2
                 List<ReverseDependencyLink> depList = dependencyMap.get( key );
 550  2
                 if ( depList == null )
 551  
                 {
 552  1
                     depList = new ArrayList<ReverseDependencyLink>();
 553  
                 }
 554  2
                 depList.add( new ReverseDependencyLink( dep, reactorProject ) );
 555  2
                 dependencyMap.put( key, depList );
 556  2
             }
 557  2
         }
 558  
 
 559  1
         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  2
         {
 573  2
             this.dependency = dependency;
 574  2
             this.project = project;
 575  2
         }
 576  
 
 577  
         public Dependency getDependency()
 578  
         {
 579  5
             return dependency;
 580  
         }
 581  
 
 582  
         public MavenProject getProject()
 583  
         {
 584  2
             return project;
 585  
         }
 586  
 
 587  
         @Override
 588  
         public String toString()
 589  
         {
 590  0
             return project.getId();
 591  
         }
 592  
     }
 593  
 
 594  
     /**
 595  
      * Internal ReverseDependencyLink comparator
 596  
      */
 597  2
     static class ReverseDependencyLinkComparator
 598  
         implements Comparator<ReverseDependencyLink>
 599  
     {
 600  
         /** {@inheritDoc} */
 601  
         public int compare( ReverseDependencyLink p1, ReverseDependencyLink p2 )
 602  
         {
 603  1
             return p1.getProject().getId().compareTo( p2.getProject().getId() );
 604  
         }
 605  
     }
 606  
 }