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