Coverage Report - org.apache.maven.report.projectinfo.DependencyConvergenceReport
 
Classes in this File Line Coverage Branch Coverage Complexity
DependencyConvergenceReport
92%
244/264
61%
33/54
2.3
DependencyConvergenceReport$ReverseDependencyLink
86%
6/7
N/A
2.3
DependencyConvergenceReport$ReverseDependencyLinkComparator
83%
5/6
50%
2/4
2.3
 
 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  1
 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  2
         return "dependency-convergence";
 75  
     }
 76  
 
 77  
     protected String getI18Nsection()
 78  
     {
 79  15
         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  2
         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  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 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 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  1
         sink.section2();
 147  
 
 148  1
         sink.sectionTitle2();
 149  1
         sink.text( getI18nString( locale, "convergence.caption" ) );
 150  1
         sink.sectionTitle2_();
 151  
 
 152  1
         Iterator it = dependencyMap.keySet().iterator();
 153  2
         while ( it.hasNext() )
 154  
         {
 155  1
             String key = (String) it.next();
 156  1
             List depList = (List) dependencyMap.get( key );
 157  
 
 158  1
             sink.section3();
 159  1
             sink.sectionTitle3();
 160  1
             sink.text( key );
 161  1
             sink.sectionTitle3_();
 162  
 
 163  1
             generateDependencyDetails( sink, depList );
 164  
 
 165  1
             sink.section3_();
 166  1
         }
 167  
 
 168  1
         sink.section2_();
 169  1
     }
 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  1
         sink.table();
 180  
 
 181  1
         Map artifactMap = getSortedUniqueArtifactMap( depList );
 182  
 
 183  1
         sink.tableRow();
 184  
 
 185  1
         sink.tableCell( );
 186  1
         if ( artifactMap.size() > 1 )
 187  
         {
 188  0
             iconError( sink );
 189  
         }
 190  
         else
 191  
         {
 192  1
             iconSuccess( sink );
 193  
         }
 194  1
         sink.tableCell_();
 195  
 
 196  1
         sink.tableCell();
 197  
 
 198  1
         sink.table();
 199  
 
 200  1
         Iterator it = artifactMap.keySet().iterator();
 201  2
         while ( it.hasNext() )
 202  
         {
 203  1
             String version = (String) it.next();
 204  1
             sink.tableRow();
 205  1
             sink.tableCell( new SinkEventAttributeSet( new String[] {SinkEventAttributeSet.WIDTH, "25%"} ) );
 206  1
             sink.text( version );
 207  1
             sink.tableCell_();
 208  
 
 209  1
             sink.tableCell();
 210  1
             generateVersionDetails( sink, artifactMap, version );
 211  1
             sink.tableCell_();
 212  
 
 213  1
             sink.tableRow_();
 214  1
         }
 215  1
         sink.table_();
 216  1
         sink.tableCell_();
 217  
 
 218  1
         sink.tableRow_();
 219  
 
 220  1
         sink.table_();
 221  1
     }
 222  
 
 223  
     private void generateVersionDetails( Sink sink, Map artifactMap, String version )
 224  
     {
 225  1
         sink.numberedList( 1 ); // Use lower alpha numbering
 226  1
         List depList = (List) artifactMap.get( version );
 227  1
         Collections.sort( depList, new ReverseDependencyLinkComparator() );
 228  1
         Iterator it = depList.iterator();
 229  3
         while ( it.hasNext() )
 230  
         {
 231  2
             ReverseDependencyLink rdl = (ReverseDependencyLink) it.next();
 232  2
             sink.numberedListItem();
 233  2
             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
 234  
             {
 235  0
                 sink.link( rdl.project.getUrl() );
 236  
             }
 237  2
             sink.text( rdl.project.getGroupId() + ":" + rdl.project.getArtifactId() );
 238  2
             if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
 239  
             {
 240  0
                 sink.link_();
 241  
             }
 242  2
             sink.numberedListItem_();
 243  2
         }
 244  1
         sink.numberedList_();
 245  1
     }
 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  2
         Map uniqueArtifactMap = new TreeMap();
 269  
 
 270  2
         Iterator it = depList.iterator();
 271  6
         while ( it.hasNext() )
 272  
         {
 273  4
             ReverseDependencyLink rdl = (ReverseDependencyLink) it.next();
 274  4
             String key = rdl.getDependency().getVersion();
 275  4
             List projectList = (List) uniqueArtifactMap.get( key );
 276  4
             if ( projectList == null )
 277  
             {
 278  2
                 projectList = new ArrayList();
 279  
             }
 280  4
             projectList.add( rdl );
 281  4
             uniqueArtifactMap.put( key, projectList );
 282  4
         }
 283  
 
 284  2
         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  1
         sink.table();
 296  1
         sink.tableCaption();
 297  1
         sink.bold();
 298  1
         sink.text( getI18nString( locale, "legend" ) );
 299  1
         sink.bold_();
 300  1
         sink.tableCaption_();
 301  
 
 302  1
         sink.tableRow();
 303  
 
 304  1
         sink.tableCell( );
 305  1
         iconSuccess( sink );
 306  1
         sink.tableCell_();
 307  1
         sink.tableCell();
 308  1
         sink.text( getI18nString( locale, "legend.shared" ) );
 309  1
         sink.tableCell_();
 310  
 
 311  1
         sink.tableRow_();
 312  
 
 313  1
         sink.tableRow();
 314  
 
 315  1
         sink.tableCell( );
 316  1
         iconError( sink );
 317  1
         sink.tableCell_();
 318  1
         sink.tableCell();
 319  1
         sink.text( getI18nString( locale, "legend.different" ) );
 320  1
         sink.tableCell_();
 321  
 
 322  1
         sink.tableRow_();
 323  
 
 324  1
         sink.table_();
 325  1
     }
 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  1
         int depCount = dependencyMap.size();
 337  1
         int artifactCount = 0;
 338  1
         int snapshotCount = 0;
 339  
 
 340  1
         Iterator it = dependencyMap.values().iterator();
 341  2
         while ( it.hasNext() )
 342  
         {
 343  1
             List depList = (List) it.next();
 344  1
             Map artifactMap = getSortedUniqueArtifactMap( depList );
 345  1
             snapshotCount += countSnapshots( artifactMap );
 346  1
             artifactCount += artifactMap.size();
 347  1
         }
 348  
 
 349  1
         int convergence = (int) ( ( (double) depCount / (double) artifactCount ) * PERCENTAGE );
 350  
 
 351  
         // Create report
 352  1
         sink.table();
 353  1
         sink.tableCaption();
 354  1
         sink.bold();
 355  1
         sink.text( getI18nString( locale, "stats.caption" ) );
 356  1
         sink.bold_();
 357  1
         sink.tableCaption_();
 358  
 
 359  1
         sink.tableRow();
 360  1
         sink.tableHeaderCell( );
 361  1
         sink.text( getI18nString( locale, "stats.subprojects" ) );
 362  1
         sink.tableHeaderCell_();
 363  1
         sink.tableCell();
 364  1
         sink.text( String.valueOf( reactorProjects.size() ) );
 365  1
         sink.tableCell_();
 366  1
         sink.tableRow_();
 367  
 
 368  1
         sink.tableRow();
 369  1
         sink.tableHeaderCell( );
 370  1
         sink.text( getI18nString( locale, "stats.dependencies" ) );
 371  1
         sink.tableHeaderCell_();
 372  1
         sink.tableCell();
 373  1
         sink.text( String.valueOf( depCount ) );
 374  1
         sink.tableCell_();
 375  1
         sink.tableRow_();
 376  
 
 377  1
         sink.tableRow();
 378  1
         sink.tableHeaderCell( );
 379  1
         sink.text( getI18nString( locale, "stats.artifacts" ) );
 380  1
         sink.tableHeaderCell_();
 381  1
         sink.tableCell();
 382  1
         sink.text( String.valueOf( artifactCount ) );
 383  1
         sink.tableCell_();
 384  1
         sink.tableRow_();
 385  
 
 386  1
         sink.tableRow();
 387  1
         sink.tableHeaderCell( );
 388  1
         sink.text( getI18nString( locale, "stats.snapshots" ) );
 389  1
         sink.tableHeaderCell_();
 390  1
         sink.tableCell();
 391  1
         sink.text( String.valueOf( snapshotCount ) );
 392  1
         sink.tableCell_();
 393  1
         sink.tableRow_();
 394  
 
 395  1
         sink.tableRow();
 396  1
         sink.tableHeaderCell( );
 397  1
         sink.text( getI18nString( locale, "stats.convergence" ) );
 398  1
         sink.tableHeaderCell_();
 399  1
         sink.tableCell();
 400  1
         if ( convergence < PERCENTAGE )
 401  
         {
 402  0
             iconError( sink );
 403  
         }
 404  
         else
 405  
         {
 406  1
             iconSuccess( sink );
 407  
         }
 408  1
         sink.nonBreakingSpace();
 409  1
         sink.bold();
 410  1
         sink.text( String.valueOf( convergence ) + "%" );
 411  1
         sink.bold_();
 412  1
         sink.tableCell_();
 413  1
         sink.tableRow_();
 414  
 
 415  1
         sink.tableRow();
 416  1
         sink.tableHeaderCell( );
 417  1
         sink.text( getI18nString( locale, "stats.readyrelease" ) );
 418  1
         sink.tableHeaderCell_();
 419  1
         sink.tableCell();
 420  1
         if ( convergence >= PERCENTAGE && snapshotCount <= 0 )
 421  
         {
 422  1
             iconSuccess( sink );
 423  1
             sink.nonBreakingSpace();
 424  1
             sink.bold();
 425  1
             sink.text( getI18nString( locale, "stats.readyrelease.success" ) );
 426  1
             sink.bold_();
 427  
         }
 428  
         else
 429  
         {
 430  0
             iconError( sink );
 431  0
             sink.nonBreakingSpace();
 432  0
             sink.bold();
 433  0
             sink.text( getI18nString( locale, "stats.readyrelease.error" ) );
 434  0
             sink.bold_();
 435  0
             if ( convergence < PERCENTAGE )
 436  
             {
 437  0
                 sink.lineBreak();
 438  0
                 sink.text( getI18nString( locale, "stats.readyrelease.error.convergence" ) );
 439  
             }
 440  0
             if ( snapshotCount > 0 )
 441  
             {
 442  0
                 sink.lineBreak();
 443  0
                 sink.text( getI18nString( locale, "stats.readyrelease.error.snapshots" ) );
 444  
             }
 445  
         }
 446  1
         sink.tableCell_();
 447  1
         sink.tableRow_();
 448  
 
 449  1
         sink.table_();
 450  1
     }
 451  
 
 452  
     private int countSnapshots( Map artifactMap )
 453  
     {
 454  1
         int count = 0;
 455  1
         Iterator it = artifactMap.keySet().iterator();
 456  2
         while ( it.hasNext() )
 457  
         {
 458  1
             String version = (String) it.next();
 459  1
             boolean isReactorProject = false;
 460  
 
 461  1
             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  1
             if ( iterator.hasNext() )
 466  
             {
 467  1
                 ReverseDependencyLink rdl = (ReverseDependencyLink) iterator.next();
 468  1
                 if ( isReactorProject( rdl.getDependency() ) )
 469  
                 {
 470  0
                     isReactorProject = true;
 471  
                 }
 472  
             }
 473  
 
 474  1
             if ( version.endsWith( "-SNAPSHOT" ) && !isReactorProject )
 475  
             {
 476  0
                 count++;
 477  
             }
 478  1
         }
 479  1
         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  1
         Iterator iterator = reactorProjects.iterator();
 491  3
         while ( iterator.hasNext() )
 492  
         {
 493  2
             MavenProject mavenProject = (MavenProject) iterator.next();
 494  2
             if ( mavenProject.getGroupId().equals( dependency.getGroupId() )
 495  
                 && mavenProject.getArtifactId().equals( dependency.getArtifactId() ) )
 496  
             {
 497  0
                 if ( getLog().isDebugEnabled() )
 498  
                 {
 499  0
                     getLog().debug( dependency + " is a reactor project" );
 500  
                 }
 501  0
                 return true;
 502  
             }
 503  2
         }
 504  1
         return false;
 505  
     }
 506  
 
 507  
     private void iconSuccess( Sink sink )
 508  
     {
 509  4
         sink.figure();
 510  4
         sink.figureCaption();
 511  4
         sink.text( "success" );
 512  4
         sink.figureCaption_();
 513  4
         sink.figureGraphics( "images/icon_success_sml.gif" );
 514  4
         sink.figure_();
 515  4
     }
 516  
 
 517  
     private void iconError( Sink sink )
 518  
     {
 519  1
         sink.figure();
 520  1
         sink.figureCaption();
 521  1
         sink.text( "error" );
 522  1
         sink.figureCaption_();
 523  1
         sink.figureGraphics( "images/icon_error_sml.gif" );
 524  1
         sink.figure_();
 525  1
     }
 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  1
         Iterator it = reactorProjects.iterator();
 549  
 
 550  1
         Map dependencyMap = new TreeMap();
 551  
 
 552  3
         while ( it.hasNext() )
 553  
         {
 554  2
             MavenProject reactorProject = (MavenProject) it.next();
 555  
 
 556  2
             Iterator itdep = reactorProject.getDependencies().iterator();
 557  4
             while ( itdep.hasNext() )
 558  
             {
 559  2
                 Dependency dep = (Dependency) itdep.next();
 560  2
                 String key = dep.getGroupId() + ":" + dep.getArtifactId();
 561  2
                 List depList = (List) dependencyMap.get( key );
 562  2
                 if ( depList == null )
 563  
                 {
 564  1
                     depList = new ArrayList();
 565  
                 }
 566  2
                 depList.add( new ReverseDependencyLink( dep, reactorProject ) );
 567  2
                 dependencyMap.put( key, depList );
 568  2
             }
 569  2
         }
 570  
 
 571  1
         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  2
         {
 585  2
             this.dependency = dependency;
 586  2
             this.project = project;
 587  2
         }
 588  
 
 589  
         public Dependency getDependency()
 590  
         {
 591  5
             return dependency;
 592  
         }
 593  
 
 594  
         public MavenProject getProject()
 595  
         {
 596  2
             return project;
 597  
         }
 598  
 
 599  
         /** {@inheritDoc} */
 600  
         public String toString()
 601  
         {
 602  0
             return project.getId();
 603  
         }
 604  
     }
 605  
 
 606  
     /**
 607  
      * Internal ReverseDependencyLink comparator
 608  
      */
 609  1
     static class ReverseDependencyLinkComparator
 610  
         implements Comparator
 611  
     {
 612  
         /** {@inheritDoc} */
 613  
         public int compare( Object o1, Object o2 )
 614  
         {
 615  1
             if ( o1 instanceof ReverseDependencyLink && o2 instanceof ReverseDependencyLink )
 616  
             {
 617  1
                 ReverseDependencyLink p1 = (ReverseDependencyLink) o1;
 618  1
                 ReverseDependencyLink p2 = (ReverseDependencyLink) o2;
 619  1
                 return p1.getProject().getId().compareTo( p2.getProject().getId() );
 620  
             }
 621  
 
 622  0
             return 0;
 623  
         }
 624  
     }
 625  
 }