Coverage Report - org.apache.maven.changelog.ChangeLogReport
 
Classes in this File Line Coverage Branch Coverage Complexity
ChangeLogReport
61% 
61% 
4,281
 
 1  
 package org.apache.maven.changelog;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2006 The Apache Software Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *      http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import org.apache.maven.plugin.MojoExecutionException;
 20  
 import org.apache.maven.project.MavenProject;
 21  
 import org.apache.maven.reporting.AbstractMavenReport;
 22  
 import org.apache.maven.reporting.MavenReportException;
 23  
 import org.apache.maven.scm.ChangeFile;
 24  
 import org.apache.maven.scm.ChangeSet;
 25  
 import org.apache.maven.scm.ScmException;
 26  
 import org.apache.maven.scm.ScmFileSet;
 27  
 import org.apache.maven.scm.ScmResult;
 28  
 import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
 29  
 import org.apache.maven.scm.command.changelog.ChangeLogSet;
 30  
 import org.apache.maven.scm.manager.ScmManager;
 31  
 import org.apache.maven.scm.provider.ScmProvider;
 32  
 import org.apache.maven.scm.provider.ScmProviderRepository;
 33  
 import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
 34  
 import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
 35  
 import org.apache.maven.scm.repository.ScmRepository;
 36  
 import org.apache.maven.settings.Server;
 37  
 import org.apache.maven.settings.Settings;
 38  
 import org.codehaus.doxia.sink.Sink;
 39  
 import org.codehaus.doxia.site.renderer.SiteRenderer;
 40  
 import org.codehaus.plexus.util.StringUtils;
 41  
 
 42  
 import java.io.BufferedOutputStream;
 43  
 import java.io.File;
 44  
 import java.io.FileInputStream;
 45  
 import java.io.FileNotFoundException;
 46  
 import java.io.FileOutputStream;
 47  
 import java.io.PrintWriter;
 48  
 import java.text.ParseException;
 49  
 import java.text.SimpleDateFormat;
 50  
 import java.util.ArrayList;
 51  
 import java.util.Collection;
 52  
 import java.util.Date;
 53  
 import java.util.HashMap;
 54  
 import java.util.Iterator;
 55  
 import java.util.LinkedList;
 56  
 import java.util.List;
 57  
 import java.util.Locale;
 58  
 import java.util.ResourceBundle;
 59  
 import java.util.StringTokenizer;
 60  
 import java.util.Collections;
 61  
 import java.util.Comparator;
 62  
 
 63  
 /**
 64  
  * Generate a changelog report.
 65  
  *
 66  
  * @goal changelog
 67  
  */
 68  85
 public class ChangeLogReport
 69  
     extends AbstractMavenReport
 70  
 {
 71  
     /**
 72  
      * Used to specify whether to build the log using range, tag or date.
 73  
      *
 74  
      * @parameter expression="${changelog.type}" default-value="range"
 75  
      * @required
 76  
      */
 77  
     private String type;
 78  
 
 79  
     /**
 80  
      * Used to specify the number of days of log entries to retrieve.
 81  
      *
 82  
      * @parameter expression="${changelog.range}" default-value="-1"
 83  
      */
 84  
     private int range;
 85  
 
 86  
     /**
 87  
      * Used to specify the absolute date (or list of dates) to start log entries from.
 88  
      *
 89  
      * @parameter
 90  
      */
 91  
     private List dates;
 92  
 
 93  
     /**
 94  
      * Used to specify the tag (or list of tags) to start log entries from.
 95  
      *
 96  
      * @parameter
 97  
      */
 98  
     private List tags;
 99  
 
 100  
     /**
 101  
      * Used to specify the date format of log entries to retrieve. This format will also be reflected in the reports.
 102  
      *
 103  
      * @parameter expression="${changelog.dateFormat}" default-value="yyyy-MM-dd"
 104  
      * @required
 105  
      */
 106  
     private String dateFormat;
 107  
 
 108  
     /**
 109  
      * Input dir.  Directory where the sources are located.
 110  
      *
 111  
      * @parameter expression="${project.build.sourceDirectory}"
 112  
      * @required
 113  
      */
 114  
     private File basedir;
 115  
 
 116  
     /**
 117  
      * Output file for xml document
 118  
      *
 119  
      * @parameter expression="${project.build.directory}/changelog.xml"
 120  
      * @required
 121  
      */
 122  
     private File outputXML;
 123  
 
 124  
     /**
 125  
      * Allows the user to make changelog regenerate the changelog.xml file for the specified time in minutes.
 126  
      *
 127  
      * @parameter expression="${outputXMLExpiration}" default-value="60"
 128  
      * @required
 129  
      */
 130  
     private int outputXMLExpiration;
 131  
 
 132  
     /**
 133  
      * Comment format string used for interrogating
 134  
      * the revision control system.
 135  
      * Currently only used by the ClearcaseChangeLogGenerator.
 136  
      *
 137  
      * @parameter expression="${changelog.commentFormat}"
 138  
      */
 139  
     private String commentFormat;
 140  
 
 141  
     /**
 142  
      * Output encoding for the xml document
 143  
      *
 144  
      * @parameter expression="${changelog.outputEncoding}" default-value="ISO-8859-1"
 145  
      * @required
 146  
      */
 147  
     private String outputEncoding;
 148  
 
 149  
     /**
 150  
      * The user name (used by svn and starteam protocol).
 151  
      *
 152  
      * @parameter expression="${username}"
 153  
      */
 154  
     private String username;
 155  
 
 156  
     /**
 157  
      * The user password (used by svn and starteam protocol).
 158  
      *
 159  
      * @parameter expression="${password}"
 160  
      */
 161  
     private String password;
 162  
 
 163  
     /**
 164  
      * The private key (used by java svn).
 165  
      *
 166  
      * @parameter expression="${privateKey}"
 167  
      */
 168  
     private String privateKey;
 169  
 
 170  
     /**
 171  
      * The passphrase (used by java svn).
 172  
      *
 173  
      * @parameter expression="${passphrase}"
 174  
      */
 175  
     private String passphrase;
 176  
 
 177  
     /**
 178  
      * The url of tags base directory (used by svn protocol).
 179  
      *
 180  
      * @parameter expression="${tagBase}"
 181  
      */
 182  
     private String tagBase;
 183  
 
 184  
     /**
 185  
      * The URL to view the scm. Basis for external links from the generated report.
 186  
      *
 187  
      * @parameter expression="${project.scm.url}"
 188  
      */
 189  
     protected String scmUrl;
 190  
 
 191  
     /**
 192  
      * The Maven Project Object
 193  
      *
 194  
      * @parameter expression="${project}"
 195  
      * @required
 196  
      * @readonly
 197  
      */
 198  
     private MavenProject project;
 199  
 
 200  
     /**
 201  
      * The directory where the report will be generated
 202  
      *
 203  
      * @parameter expression="${project.build.directory}/site"
 204  
      * @required
 205  
      * @readonly
 206  
      */
 207  
     private File outputDirectory;
 208  
 
 209  
     /**
 210  
      * @parameter expression="${component.org.codehaus.doxia.site.renderer.SiteRenderer}"
 211  
      * @required
 212  
      * @readonly
 213  
      */
 214  
     private SiteRenderer siteRenderer;
 215  
 
 216  
     /**
 217  
      * @parameter expression="${settings.offline}"
 218  
      * @required
 219  
      * @readonly
 220  
      */
 221  
     private boolean offline;
 222  
 
 223  
     /**
 224  
      * @parameter expression="${component.org.apache.maven.scm.manager.ScmManager}"
 225  
      * @required
 226  
      * @readonly
 227  
      */
 228  
     private ScmManager manager;
 229  
 
 230  
     /**
 231  
      * @parameter expression="${settings}"
 232  
      * @required
 233  
      * @readonly
 234  
      */
 235  
     private Settings settings;
 236  
 
 237  
     /**
 238  
      * Allows the user to choose which scm connection to use when connecting to the scm.
 239  
      * Can either be "connection" or "developerConnection".
 240  
      *
 241  
      * @parameter default-value="connection"
 242  
      * @required
 243  
      */
 244  
     private String connectionType;
 245  
 
 246  
     /**
 247  
      * Allows the user to specify the file detail URL to use when viewing an scm file.
 248  
      *
 249  
      * @parameter expression="${project.scm.url}"
 250  
      */
 251  
     protected String displayFileDetailUrl;
 252  
 
 253  
     // temporary field holder while generating the report
 254  
     private String rpt_Repository, rpt_OneRepoParam, rpt_MultiRepoParam;
 255  
 
 256  
     // field for SCM Connection URL
 257  
     private String connection;
 258  
 
 259  
     /**
 260  
      * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
 261  
      */
 262  
     public void executeReport( Locale locale )
 263  
         throws MavenReportException
 264  
     {
 265  
         //check if sources exists <-- required for parent poms
 266  85
         if ( !basedir.exists() )
 267  
         {
 268  15
             doGenerateEmptyReport( getBundle( locale ), getSink() );
 269  
 
 270  15
             return;
 271  
         }
 272  
 
 273  70
         verifySCMTypeParams();
 274  
 
 275  65
         doGenerateReport( getChangedSets(), getBundle( locale ), getSink() );
 276  50
     }
 277  
 
 278  
     /**
 279  
      * populates the changedSets field by either connecting to the SCM or using an existing XML generated in a previous
 280  
      * run of the report
 281  
      *
 282  
      * @throws MavenReportException
 283  
      */
 284  
     protected List getChangedSets()
 285  
         throws MavenReportException
 286  
     {
 287  65
         List changelogList = null;
 288  
 
 289  65
         if ( !outputXML.isAbsolute() )
 290  
         {
 291  50
             outputXML = new File( project.getBasedir(), outputXML.getPath() );
 292  
         }
 293  
 
 294  65
         if ( outputXML.exists() )
 295  
         {
 296  50
             if ( outputXMLExpiration > 0 &&
 297  
                  outputXMLExpiration * 60000 > System.currentTimeMillis() - outputXML.lastModified() )
 298  
             {
 299  
                 try
 300  
                 {
 301  50
                     FileInputStream fIn = new FileInputStream( outputXML );
 302  
 
 303  50
                     getLog().info( "Using existing changelog.xml..." );
 304  
 
 305  50
                     changelogList = ChangeLog.loadChangedSets( fIn );
 306  
                 }
 307  0
                 catch ( FileNotFoundException e )
 308  
                 {
 309  
                     //do nothing, just regenerate
 310  
                 }
 311  0
                 catch ( Exception e )
 312  
                 {
 313  0
                     throw new MavenReportException( "An error occurred while parsing " + outputXML.getAbsolutePath(),
 314  
                                                     e );
 315  50
                 }
 316  
             }
 317  
         }
 318  
 
 319  65
         if ( changelogList == null )
 320  
         {
 321  15
             if ( offline )
 322  
             {
 323  0
                 throw new MavenReportException( "This report requires online mode." );
 324  
             }
 325  
 
 326  15
             getLog().info( "Generating changed sets xml to: " + outputXML.getAbsolutePath() );
 327  
 
 328  15
             changelogList = generateChangeSetsFromSCM();
 329  
 
 330  
             try
 331  
             {
 332  0
                 writeChangelogXml( changelogList );
 333  
             }
 334  0
             catch ( FileNotFoundException e )
 335  
             {
 336  0
                 throw new MavenReportException( "Can't create " + outputXML.getAbsolutePath(), e );
 337  0
             }
 338  
         }
 339  
 
 340  50
         return changelogList;
 341  
     }
 342  
 
 343  
     private void writeChangelogXml( List changelogList )
 344  
         throws FileNotFoundException
 345  
     {
 346  0
         StringBuffer changelogXml = new StringBuffer();
 347  
 
 348  0
         changelogXml.append( "<?xml version=\"1.0\" encoding=\"" ).append( outputEncoding ).append( "\"?>\n" );
 349  0
         changelogXml.append( "<changelog>" );
 350  
 
 351  0
         for ( Iterator sets = changelogList.iterator(); sets.hasNext(); )
 352  
         {
 353  0
             changelogXml.append( "\n  " );
 354  
 
 355  0
             ChangeLogSet changelogSet = (ChangeLogSet) sets.next();
 356  0
             String changeset = changelogSet.toXML( outputEncoding );
 357  
 
 358  
             //remove xml header
 359  0
             if ( changeset.startsWith( "<?xml" ) )
 360  
             {
 361  0
                 int idx = changeset.indexOf( "?>" ) + 2;
 362  0
                 changeset = changeset.substring( idx );
 363  
             }
 364  
 
 365  0
             changelogXml.append( changeset );
 366  
         }
 367  
 
 368  0
         changelogXml.append( "\n</changelog>" );
 369  
 
 370  0
         outputXML.getParentFile().mkdirs();
 371  
 
 372  0
         PrintWriter pw = new PrintWriter( new BufferedOutputStream( new FileOutputStream( outputXML ) ) );
 373  0
         pw.write( changelogXml.toString() );
 374  0
         pw.flush();
 375  0
         pw.close();
 376  0
     }
 377  
 
 378  
     /**
 379  
      * creates a ChangeLog object and then connects to the SCM to generate the changed sets
 380  
      *
 381  
      * @return changedlogsets generated from the SCM
 382  
      * @throws MavenReportException
 383  
      */
 384  
     protected List generateChangeSetsFromSCM()
 385  
         throws MavenReportException
 386  
     {
 387  
         try
 388  
         {
 389  15
             List changeSets = new ArrayList();
 390  
 
 391  15
             ScmRepository repository = getScmRepository();
 392  
 
 393  10
             ScmProvider provider = manager.getProviderByRepository( repository );
 394  
 
 395  
             ChangeLogScmResult result;
 396  
 
 397  10
             if ( "range".equals( type ) )
 398  
             {
 399  5
                 result =
 400  
                     provider.changeLog( repository, new ScmFileSet( basedir ), null, null, range, null, dateFormat );
 401  
 
 402  5
                 checkResult( result );
 403  
 
 404  0
                 changeSets.add( result.getChangeLog() );
 405  
             }
 406  5
             else if ( "tag".equals( type ) )
 407  
             {
 408  0
                 Iterator tagsIter = tags.iterator();
 409  
 
 410  0
                 String startTag = (String) tagsIter.next();
 411  0
                 String endTag = null;
 412  
 
 413  0
                 if ( tagsIter.hasNext() )
 414  
                 {
 415  0
                     while ( tagsIter.hasNext() )
 416  
                     {
 417  0
                         endTag = (String) tagsIter.next();
 418  
 
 419  0
                         result = provider.changeLog( repository, new ScmFileSet( basedir ), startTag, endTag );
 420  
 
 421  0
                         checkResult( result );
 422  
 
 423  0
                         changeSets.add( result.getChangeLog() );
 424  
 
 425  0
                         startTag = endTag;
 426  
                     }
 427  
                 }
 428  
                 else
 429  
                 {
 430  0
                     result = provider.changeLog( repository, new ScmFileSet( basedir ), startTag, endTag );
 431  
 
 432  0
                     checkResult( result );
 433  
 
 434  0
                     changeSets.add( result.getChangeLog() );
 435  
                 }
 436  
             }
 437  5
             else if ( "date".equals( type ) )
 438  
             {
 439  5
                 Iterator dateIter = dates.iterator();
 440  
 
 441  5
                 String startDate = (String) dateIter.next();
 442  5
                 String endDate = null;
 443  
 
 444  5
                 if ( dateIter.hasNext() )
 445  
                 {
 446  0
                     while ( dateIter.hasNext() )
 447  
                     {
 448  0
                         endDate = (String) dateIter.next();
 449  
 
 450  0
                         result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ),
 451  
                                                      parseDate( endDate ), 0, null );
 452  
 
 453  0
                         checkResult( result );
 454  
 
 455  0
                         changeSets.add( result.getChangeLog() );
 456  
 
 457  0
                         startDate = endDate;
 458  
                     }
 459  
                 }
 460  
                 else
 461  
                 {
 462  5
                     result = provider.changeLog( repository, new ScmFileSet( basedir ), parseDate( startDate ),
 463  
                                                  parseDate( endDate ), 0, null );
 464  
 
 465  0
                     checkResult( result );
 466  
 
 467  0
                     changeSets.add( result.getChangeLog() );
 468  
                 }
 469  
             }
 470  
             else
 471  
             {
 472  0
                 throw new MavenReportException( "The type '" + type + "' isn't supported." );
 473  
             }
 474  
 
 475  0
             return changeSets;
 476  
         }
 477  5
         catch ( ScmException e )
 478  
         {
 479  5
             throw new MavenReportException( "Cannot run changelog command : ", e );
 480  
         }
 481  10
         catch ( MojoExecutionException e )
 482  
         {
 483  10
             throw new MavenReportException( "An error is occurred during changelog command : ", e );
 484  
         }
 485  
     }
 486  
 
 487  
     /**
 488  
      * Converts the localized date string pattern to date object.
 489  
      *
 490  
      * @return A date
 491  
      */
 492  
     private Date parseDate( String date )
 493  
         throws MojoExecutionException
 494  
     {
 495  5
         if ( date == null || date.trim().length() == 0 )
 496  
         {
 497  0
             return null;
 498  
         }
 499  
 
 500  5
         SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd" );
 501  
 
 502  
         try
 503  
         {
 504  5
             return formatter.parse( date );
 505  
         }
 506  5
         catch ( ParseException e )
 507  
         {
 508  5
             throw new MojoExecutionException( "Please use this date pattern: " + formatter.toLocalizedPattern(), e );
 509  
         }
 510  
     }
 511  
 
 512  
     public ScmRepository getScmRepository()
 513  
         throws ScmException
 514  
     {
 515  
         ScmRepository repository;
 516  
 
 517  
         try
 518  
         {
 519  15
             repository = manager.makeScmRepository( getConnection() );
 520  
 
 521  10
             ScmProviderRepository providerRepo = repository.getProviderRepository();
 522  
 
 523  10
             if ( !StringUtils.isEmpty( username ) )
 524  
             {
 525  0
                 providerRepo.setUser( username );
 526  
             }
 527  
 
 528  10
             if ( !StringUtils.isEmpty( password ) )
 529  
             {
 530  0
                 providerRepo.setPassword( password );
 531  
             }
 532  
 
 533  10
             if ( repository.getProviderRepository() instanceof ScmProviderRepositoryWithHost )
 534  
             {
 535  0
                 ScmProviderRepositoryWithHost repo = (ScmProviderRepositoryWithHost) repository.getProviderRepository();
 536  
 
 537  0
                 loadInfosFromSettings( repo );
 538  
 
 539  0
                 if ( !StringUtils.isEmpty( username ) )
 540  
                 {
 541  0
                     repo.setUser( username );
 542  
                 }
 543  
 
 544  0
                 if ( !StringUtils.isEmpty( password ) )
 545  
                 {
 546  0
                     repo.setPassword( password );
 547  
                 }
 548  
 
 549  0
                 if ( !StringUtils.isEmpty( privateKey ) )
 550  
                 {
 551  0
                     repo.setPrivateKey( privateKey );
 552  
                 }
 553  
 
 554  0
                 if ( !StringUtils.isEmpty( passphrase ) )
 555  
                 {
 556  0
                     repo.setPassphrase( passphrase );
 557  
                 }
 558  
             }
 559  
 
 560  10
             if ( !StringUtils.isEmpty( tagBase ) && repository.getProvider().equals( "svn" ) )
 561  
             {
 562  0
                 SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
 563  
 
 564  0
                 svnRepo.setTagBase( tagBase );
 565  
             }
 566  
         }
 567  5
         catch ( Exception e )
 568  
         {
 569  5
             throw new ScmException( "Can't load the scm provider.", e );
 570  10
         }
 571  
 
 572  10
         return repository;
 573  
     }
 574  
 
 575  
     /**
 576  
      * Load username password from settings if user has not set them in JVM properties
 577  
      *
 578  
      * @param repo
 579  
      */
 580  
     private void loadInfosFromSettings( ScmProviderRepositoryWithHost repo )
 581  
     {
 582  0
         if ( username == null || password == null )
 583  
         {
 584  0
             String host = repo.getHost();
 585  
 
 586  0
             int port = repo.getPort();
 587  
 
 588  0
             if ( port > 0 )
 589  
             {
 590  0
                 host += ":" + port;
 591  
             }
 592  
 
 593  0
             Server server = this.settings.getServer( host );
 594  
 
 595  0
             if ( server != null )
 596  
             {
 597  0
                 if ( username == null )
 598  
                 {
 599  0
                     username = this.settings.getServer( host ).getUsername();
 600  
                 }
 601  
 
 602  0
                 if ( password == null )
 603  
                 {
 604  0
                     password = this.settings.getServer( host ).getPassword();
 605  
                 }
 606  
 
 607  0
                 if ( privateKey == null )
 608  
                 {
 609  0
                     privateKey = this.settings.getServer( host ).getPrivateKey();
 610  
                 }
 611  
 
 612  0
                 if ( passphrase == null )
 613  
                 {
 614  0
                     passphrase = this.settings.getServer( host ).getPassphrase();
 615  
                 }
 616  
             }
 617  
         }
 618  0
     }
 619  
 
 620  
     public void checkResult( ScmResult result )
 621  
         throws MojoExecutionException
 622  
     {
 623  5
         if ( !result.isSuccess() )
 624  
         {
 625  5
             getLog().error( "Provider message:" );
 626  
 
 627  5
             getLog().error( result.getProviderMessage() == null ? "" : result.getProviderMessage() );
 628  
 
 629  5
             getLog().error( "Command output:" );
 630  
 
 631  5
             getLog().error( result.getCommandOutput() == null ? "" : result.getCommandOutput() );
 632  
 
 633  5
             throw new MojoExecutionException( "Command failed." );
 634  
         }
 635  0
     }
 636  
 
 637  
     /**
 638  
      * used to retrieve the SCM connection string
 639  
      *
 640  
      * @return the url string used to connect to the SCM
 641  
      * @throws MavenReportException when there is insufficient information to retrieve the SCM connection string
 642  
      */
 643  
     protected String getConnection()
 644  
         throws MavenReportException
 645  
     {
 646  65
         if ( this.connection != null )
 647  
         {
 648  40
             return connection;
 649  
         }
 650  
 
 651  25
         if ( project.getScm() == null )
 652  
         {
 653  0
             throw new MavenReportException( "SCM Connection is not set." );
 654  
         }
 655  
 
 656  25
         String scmConnection = project.getScm().getConnection();
 657  25
         if ( StringUtils.isNotEmpty( scmConnection ) && "connection".equals( connectionType.toLowerCase() ) )
 658  
         {
 659  20
             connection = scmConnection;
 660  
         }
 661  
 
 662  25
         String scmDeveloper = project.getScm().getDeveloperConnection();
 663  25
         if ( StringUtils.isNotEmpty( scmDeveloper ) && "developerconnection".equals( connectionType.toLowerCase() ) )
 664  
         {
 665  0
             connection = scmDeveloper;
 666  
         }
 667  
 
 668  25
         if ( StringUtils.isEmpty( connection ) )
 669  
         {
 670  5
             throw new MavenReportException( "SCM Connection is not set." );
 671  
         }
 672  
 
 673  20
         return connection;
 674  
     }
 675  
 
 676  
     /**
 677  
      * checks whether there are enough configuration parameters to generate the report
 678  
      *
 679  
      * @throws MavenReportException when there is insufficient paramters to generate the report
 680  
      */
 681  
     private void verifySCMTypeParams()
 682  
         throws MavenReportException
 683  
     {
 684  70
         if ( "range".equals( type ) )
 685  
         {
 686  40
             if ( range == -1 )
 687  
             {
 688  40
                 range = 30;
 689  
             }
 690  
         }
 691  30
         else if ( "date".equals( type ) )
 692  
         {
 693  15
             if ( dates == null )
 694  
             {
 695  0
                 throw new MavenReportException(
 696  
                     "The dates parameter is required when type=\"date\". The value should be the absolute date for the start of the log." );
 697  
             }
 698  
         }
 699  15
         else if ( "tag".equals( type ) )
 700  
         {
 701  10
             if ( tags == null )
 702  
             {
 703  0
                 throw new MavenReportException( "The tags parameter is required when type=\"tag\"." );
 704  
             }
 705  
         }
 706  
         else
 707  
         {
 708  5
             throw new MavenReportException( "The type parameter has an invalid value: " + type +
 709  
                 ".  The value should be \"range\", \"date\", or \"tag\"." );
 710  
         }
 711  65
     }
 712  
 
 713  
     /**
 714  
      * generates an empty report in case there are no sources to generate a report with
 715  
      *
 716  
      * @param bundle the resource bundle to retrieve report phrases from
 717  
      * @param sink   the report formatting tool
 718  
      */
 719  
     protected void doGenerateEmptyReport( ResourceBundle bundle, Sink sink )
 720  
     {
 721  5
         sink.head();
 722  5
         sink.title();
 723  5
         sink.text( bundle.getString( "report.changelog.header" ) );
 724  5
         sink.title_();
 725  5
         sink.head_();
 726  
 
 727  5
         sink.body();
 728  5
         sink.section1();
 729  
 
 730  5
         sink.sectionTitle1();
 731  5
         sink.text( bundle.getString( "report.changelog.mainTitle" ) );
 732  5
         sink.sectionTitle1_();
 733  
 
 734  5
         sink.paragraph();
 735  5
         sink.text( "No sources found to create a report." );
 736  5
         sink.paragraph_();
 737  
 
 738  5
         sink.section1_();
 739  
 
 740  5
         sink.body_();
 741  5
         sink.flush();
 742  5
         sink.close();
 743  5
     }
 744  
 
 745  
     /**
 746  
      * method that generates the report for this mojo. This method is overridden by dev-activity and file-activity mojo
 747  
      *
 748  
      * @param changeLogSets changed sets to generate the report from
 749  
      * @param bundle        the resource bundle to retrieve report phrases from
 750  
      * @param sink          the report formatting tool
 751  
      */
 752  
     protected void doGenerateReport( List changeLogSets, ResourceBundle bundle, Sink sink )
 753  
     {
 754  40
         sink.head();
 755  40
         sink.title();
 756  40
         sink.text( bundle.getString( "report.changelog.header" ) );
 757  40
         sink.title_();
 758  40
         sink.head_();
 759  
 
 760  40
         sink.body();
 761  40
         sink.section1();
 762  
 
 763  40
         sink.sectionTitle1();
 764  40
         sink.text( bundle.getString( "report.changelog.mainTitle" ) );
 765  40
         sink.sectionTitle1_();
 766  
 
 767  
         // Summary section
 768  40
         doSummarySection( changeLogSets, bundle, sink );
 769  
 
 770  40
         for ( Iterator sets = changeLogSets.iterator(); sets.hasNext(); )
 771  
         {
 772  45
             ChangeLogSet changeLogSet = (ChangeLogSet) sets.next();
 773  
 
 774  45
             doChangedSet( changeLogSet, bundle, sink );
 775  
         }
 776  
 
 777  40
         sink.section1_();
 778  40
         sink.body_();
 779  
 
 780  40
         sink.flush();
 781  40
         sink.close();
 782  40
     }
 783  
 
 784  
     /**
 785  
      * generates the report summary section of the report
 786  
      *
 787  
      * @param changeLogSets changed sets to generate the report from
 788  
      * @param bundle        the resource bundle to retrieve report phrases from
 789  
      * @param sink          the report formatting tool
 790  
      */
 791  
     private void doSummarySection( List changeLogSets, ResourceBundle bundle, Sink sink )
 792  
     {
 793  40
         sink.paragraph();
 794  
 
 795  40
         sink.text( bundle.getString( "report.changelog.ChangedSetsTotal" ) );
 796  40
         sink.text( ": " + changeLogSets.size() );
 797  
 
 798  40
         sink.paragraph_();
 799  40
     }
 800  
 
 801  
     /**
 802  
      * generates a section of the report referring to a changeset
 803  
      *
 804  
      * @param set    the current ChangeSet to generate this section of the report
 805  
      * @param bundle the resource bundle to retrieve report phrases from
 806  
      * @param sink   the report formatting tool
 807  
      */
 808  
     private void doChangedSet( ChangeLogSet set, ResourceBundle bundle, Sink sink )
 809  
     {
 810  45
         sink.section1();
 811  
 
 812  45
         sink.sectionTitle2();
 813  45
         if ( set.getStartDate() == null )
 814  
         {
 815  0
             sink.text( bundle.getString( "report.SetRangeUnknown" ) );
 816  
         }
 817  45
         else if ( set.getEndDate() == null )
 818  
         {
 819  0
             sink.text( bundle.getString( "report.SetRangeSince" ) );
 820  0
             sink.text( " " + set.getStartDate() );
 821  
         }
 822  
         else
 823  
         {
 824  45
             sink.text( bundle.getString( "report.SetRangeBetween" ) );
 825  45
             sink.text( " " + set.getStartDate() + " " + bundle.getString( "report.To" ) + " " + set.getEndDate() );
 826  
         }
 827  45
         sink.sectionTitle2_();
 828  
 
 829  45
         sink.paragraph();
 830  45
         sink.text( bundle.getString( "report.TotalCommits" ) );
 831  45
         sink.text( ": " + set.getChangeSets().size() );
 832  45
         sink.lineBreak();
 833  45
         sink.text( bundle.getString( "report.changelog.FilesChanged" ) );
 834  45
         sink.text( ": " + countFilesChanged( set.getChangeSets() ) );
 835  45
         sink.paragraph_();
 836  
 
 837  45
         doChangedSetTable( set.getChangeSets(), bundle, sink );
 838  
 
 839  45
         sink.section1_();
 840  45
     }
 841  
 
 842  
     /**
 843  
      * counts the number of files that were changed in the specified SCM
 844  
      *
 845  
      * @param entries a collection of SCM changes
 846  
      * @return number of files changed for the changedsets
 847  
      */
 848  
     protected long countFilesChanged( Collection entries )
 849  
     {
 850  65
         if ( entries == null )
 851  
         {
 852  0
             return 0;
 853  
         }
 854  
 
 855  65
         if ( entries.isEmpty() )
 856  
         {
 857  35
             return 0;
 858  
         }
 859  
 
 860  30
         HashMap fileList = new HashMap();
 861  
 
 862  30
         for ( Iterator i = entries.iterator(); i.hasNext(); )
 863  
         {
 864  60
             ChangeSet entry = (ChangeSet) i.next();
 865  
 
 866  60
             List files = entry.getFiles();
 867  
 
 868  60
             for ( Iterator fileIterator = files.iterator(); fileIterator.hasNext(); )
 869  
             {
 870  90
                 ChangeFile file = (ChangeFile) fileIterator.next();
 871  
 
 872  90
                 String name = file.getName();
 873  
 
 874  90
                 if ( fileList.containsKey( name ) )
 875  
                 {
 876  30
                     LinkedList list = (LinkedList) fileList.get( name );
 877  
 
 878  30
                     list.add( file );
 879  
                 }
 880  
                 else
 881  
                 {
 882  60
                     LinkedList list = new LinkedList();
 883  
 
 884  60
                     list.add( file );
 885  
 
 886  60
                     fileList.put( name, list );
 887  
                 }
 888  
             }
 889  
         }
 890  
 
 891  30
         return fileList.size();
 892  
     }
 893  
 
 894  
     /**
 895  
      * generates the report table showing the SCM log entries
 896  
      *
 897  
      * @param entries a list of change log entries to generate the report from
 898  
      * @param bundle  the resource bundle to retrieve report phrases from
 899  
      * @param sink    the report formatting tool
 900  
      */
 901  
     private void doChangedSetTable( Collection entries, ResourceBundle bundle, Sink sink )
 902  
     {
 903  45
         sink.table();
 904  
 
 905  45
         sink.tableRow();
 906  45
         sink.tableHeaderCell();
 907  45
         sink.text( bundle.getString( "report.changelog.timestamp" ) );
 908  45
         sink.tableHeaderCell_();
 909  45
         sink.tableHeaderCell();
 910  45
         sink.text( bundle.getString( "report.changelog.author" ) );
 911  45
         sink.tableHeaderCell_();
 912  45
         sink.tableHeaderCell();
 913  45
         sink.text( bundle.getString( "report.changelog.details" ) );
 914  45
         sink.tableHeaderCell_();
 915  45
         sink.tableRow_();
 916  
 
 917  45
         initReportUrls();
 918  
 
 919  45
         Collections.sort( new ArrayList( entries ), new Comparator()
 920  
         {
 921  45
             public int compare( Object arg0, Object arg1 )
 922  
             {
 923  10
                 ChangeSet changeSet0 = (ChangeSet) arg0;
 924  10
                 ChangeSet changeSet1 = (ChangeSet) arg1;
 925  10
                 return changeSet1.getDate().compareTo( changeSet0.getDate() );
 926  
             }
 927  
         } );
 928  
 
 929  45
         for ( Iterator i = entries.iterator(); i.hasNext(); )
 930  
         {
 931  20
             ChangeSet entry = (ChangeSet) i.next();
 932  
 
 933  20
             doChangedSetDetail( entry, bundle, sink );
 934  
         }
 935  
 
 936  45
         sink.table_();
 937  45
     }
 938  
 
 939  
     /**
 940  
      * reports on the details of an SCM entry log
 941  
      *
 942  
      * @param entry  an SCM entry to generate the report from
 943  
      * @param bundle the resource bundle to retrieve report phrases from
 944  
      * @param sink   the report formatting tool
 945  
      */
 946  
     private void doChangedSetDetail( ChangeSet entry, ResourceBundle bundle, Sink sink )
 947  
     {
 948  20
         sink.tableRow();
 949  
 
 950  20
         sink.tableCell();
 951  20
         sink.text( entry.getDateFormatted() + " " + entry.getTimeFormatted() );
 952  20
         sink.tableCell_();
 953  
 
 954  20
         sink.tableCell();
 955  20
         sink.text( entry.getAuthor() );
 956  20
         sink.tableCell_();
 957  
 
 958  20
         sink.tableCell();
 959  
         //doRevision( entry.getFiles(), bundle, sink );
 960  20
         doChangedFiles( entry.getFiles(), sink );
 961  20
         sink.lineBreak();
 962  20
         sink.text( entry.getComment() );
 963  20
         sink.tableCell_();
 964  
 
 965  20
         sink.tableRow_();
 966  20
     }
 967  
 
 968  
     /**
 969  
      * populates the report url used to create links from certain elements of the report
 970  
      */
 971  
     protected void initReportUrls()
 972  
     {
 973  55
         if ( scmUrl != null )
 974  
         {
 975  55
             int idx = scmUrl.indexOf( '?' );
 976  
 
 977  55
             if ( idx > 0 )
 978  
             {
 979  5
                 rpt_Repository = scmUrl.substring( 0, idx );
 980  
 
 981  5
                 if ( scmUrl.equals( displayFileDetailUrl ) )
 982  
                 {
 983  5
                     String rpt_TmpMultiRepoParam = scmUrl.substring( rpt_Repository.length() );
 984  
 
 985  5
                     rpt_OneRepoParam = "?" + rpt_TmpMultiRepoParam.substring( 1 );
 986  
 
 987  5
                     rpt_MultiRepoParam = "&" + rpt_TmpMultiRepoParam.substring( 1 );
 988  
                 }
 989  
                 else
 990  
                 {
 991  0
                     idx = displayFileDetailUrl.indexOf( "?" );
 992  
 
 993  0
                     if ( idx > 0 )
 994  
                     {
 995  0
                         String fileDetailUrl = displayFileDetailUrl.substring( 0, idx );
 996  
 
 997  0
                         String rpt_TmpMultiRepoParam = displayFileDetailUrl.substring( idx + 1 );
 998  
 
 999  0
                         displayFileDetailUrl = fileDetailUrl;
 1000  
 
 1001  0
                         rpt_OneRepoParam = "?" + rpt_TmpMultiRepoParam;
 1002  
 
 1003  0
                         rpt_MultiRepoParam = "&" + rpt_TmpMultiRepoParam;
 1004  
                     }
 1005  
                     else
 1006  
                     {
 1007  0
                         rpt_OneRepoParam = "";
 1008  
 
 1009  0
                         rpt_MultiRepoParam = "";
 1010  
                     }
 1011  
                 }
 1012  
             }
 1013  
             else
 1014  
             {
 1015  50
                 rpt_Repository = scmUrl;
 1016  
 
 1017  50
                 rpt_OneRepoParam = "";
 1018  
 
 1019  50
                 rpt_MultiRepoParam = "";
 1020  
             }
 1021  
         }
 1022  55
     }
 1023  
 
 1024  
     /**
 1025  
      * generates the section of the report listing all the files revisions
 1026  
      *
 1027  
      * @param files list of files to generate the reports from
 1028  
      */
 1029  
     private void doChangedFiles( List files, Sink sink )
 1030  
     {
 1031  20
         for ( Iterator i = files.iterator(); i.hasNext(); )
 1032  
         {
 1033  30
             ChangeFile file = (ChangeFile) i.next();
 1034  30
             sinkLogFile( sink, file.getName(), file.getRevision() );
 1035  
         }
 1036  20
     }
 1037  
 
 1038  
     /**
 1039  
      * generates the section of the report detailing the revisions made and the files changed
 1040  
      *
 1041  
      * @param sink     the report formatting tool
 1042  
      * @param name     filename of the changed file
 1043  
      * @param revision the revision code for this file
 1044  
      */
 1045  
     private void sinkLogFile( Sink sink, String name, String revision )
 1046  
     {
 1047  30
         sink.paragraph();
 1048  
 
 1049  
         try
 1050  
         {
 1051  30
             String connection = getConnection();
 1052  
 
 1053  30
             generateLinks( connection, name, revision, sink );
 1054  
         }
 1055  0
         catch ( Exception e )
 1056  
         {
 1057  0
             getLog().debug( e );
 1058  
 
 1059  0
             sink.text( name + " v " + revision );
 1060  30
         }
 1061  
 
 1062  30
         sink.paragraph_();
 1063  30
     }
 1064  
 
 1065  
     /**
 1066  
      * attaches the http links from the changed files
 1067  
      *
 1068  
      * @param connection the string used to connect to the SCM
 1069  
      * @param name       filename of the file that was changed
 1070  
      * @param sink       the report formatting tool
 1071  
      */
 1072  
     protected void generateLinks( String connection, String name, Sink sink )
 1073  
     {
 1074  20
         generateLinks( connection, name, null, sink );
 1075  20
     }
 1076  
 
 1077  
     /**
 1078  
      * attaches the http links from the changed files
 1079  
      *
 1080  
      * @param connection the string used to connect to the SCM
 1081  
      * @param name       filename of the file that was changed
 1082  
      * @param revision   the revision code
 1083  
      * @param sink       the report formatting tool
 1084  
      */
 1085  
     protected void generateLinks( String connection, String name, String revision, Sink sink )
 1086  
     {
 1087  50
         String linkFile = null;
 1088  50
         String linkRev = null;
 1089  
 
 1090  50
         if ( displayFileDetailUrl != null )
 1091  
         {
 1092  0
             if ( connection.startsWith( "scm:perforce" ) )
 1093  
             {
 1094  0
                 String path = getAbsolutePath( displayFileDetailUrl, name );
 1095  0
                 linkFile = path + "?ac=22";
 1096  0
                 if ( revision != null )
 1097  
                 {
 1098  0
                     linkRev = path + "?ac=64&rev=" + revision;
 1099  
                 }
 1100  
             }
 1101  0
             else if ( connection.startsWith( "scm:clearcase" ) )
 1102  
             {
 1103  0
                 String path = getAbsolutePath( displayFileDetailUrl, name );
 1104  0
                 linkFile = path + rpt_OneRepoParam;
 1105  
             }
 1106  0
             else if ( connection.indexOf( "cvsmonitor.pl" ) > 0 )
 1107  
             {
 1108  0
                 String module = rpt_OneRepoParam.replaceAll( "^.*(&amp;module=.*?(?:&amp;|$)).*$", "$1" );
 1109  0
                 linkFile = displayFileDetailUrl + "?cmd=viewBrowseFile" + module + "&file=" + name;
 1110  0
                 if ( revision != null )
 1111  
                 {
 1112  0
                     linkRev =
 1113  
                         rpt_Repository + "?cmd=viewBrowseVersion" + module + "&file=" + name + "&version=" + revision;
 1114  
                 }
 1115  
             }
 1116  0
             else if ( connection.startsWith( "scm:svn" ))
 1117  
             {
 1118  
                 // idea will be to look for placeholders in URL, then replace them with appropriate values
 1119  0
                 String url = displayFileDetailUrl + rpt_OneRepoParam;
 1120  
 
 1121  0
                 url = url.replaceFirst( "([&?]path=)([^&]+|$)", "$1" + name );
 1122  0
                 linkFile = url;
 1123  
 
 1124  0
                 if ( revision != null )
 1125  
                 {
 1126  0
                     url = url.replaceFirst( "([&?]rev=)([^&]+|$)", "$1" + revision );
 1127  0
                     linkRev = url;
 1128  
                 }
 1129  
 
 1130  
             }
 1131  
             else
 1132  
             {
 1133  0
                 String path = getAbsolutePath( displayFileDetailUrl, name );
 1134  0
                 linkFile = path + rpt_OneRepoParam;
 1135  0
                 if ( revision != null )
 1136  
                 {
 1137  0
                     linkRev = path + "?rev=" + revision + "&content-type=text/vnd.viewcvs-markup" + rpt_MultiRepoParam;
 1138  
                 }
 1139  
             }
 1140  
         }
 1141  
 
 1142  50
         if ( linkFile != null )
 1143  
         {
 1144  0
             sink.link( linkFile );
 1145  0
             sink.text( name );
 1146  0
             sink.link_();
 1147  
         }
 1148  
         else
 1149  
         {
 1150  50
             sink.text( name );
 1151  
         }
 1152  
 
 1153  50
         sink.text( " " );
 1154  
 
 1155  50
         if ( linkRev != null )
 1156  
         {
 1157  0
             sink.link( linkRev );
 1158  0
             sink.text( "v " + revision );
 1159  0
             sink.link_();
 1160  
         }
 1161  50
         else if ( revision != null )
 1162  
         {
 1163  30
             sink.text( "v " + revision );
 1164  
         }
 1165  50
     }
 1166  
 
 1167  
     /**
 1168  
      * calculates the path from a base directory to a target file
 1169  
      *
 1170  
      * @param base   base directory to create the absolute path from
 1171  
      * @param target target file to create the absolute path to
 1172  
      */
 1173  
     private String getAbsolutePath( final String base, final String target )
 1174  
     {
 1175  0
         String absPath = "";
 1176  
 
 1177  0
         StringTokenizer baseTokens = new StringTokenizer( base.replaceAll( "\\\\", "/" ), "/", true );
 1178  
 
 1179  0
         StringTokenizer targetTokens = new StringTokenizer( target.replaceAll( "\\\\", "/" ), "/" );
 1180  
 
 1181  0
         String targetRoot = targetTokens.nextToken();
 1182  
 
 1183  0
         while ( baseTokens.hasMoreTokens() )
 1184  
         {
 1185  0
             String baseToken = baseTokens.nextToken();
 1186  
 
 1187  0
             if ( baseToken.equals( targetRoot ) )
 1188  
             {
 1189  0
                 break;
 1190  
             }
 1191  
 
 1192  0
             absPath += baseToken;
 1193  
         }
 1194  
 
 1195  0
         if ( !absPath.endsWith( "/" ) )
 1196  
         {
 1197  0
             absPath += "/";
 1198  
         }
 1199  
 
 1200  0
         String newTarget = target;
 1201  0
         if ( newTarget.startsWith( "/" ) )
 1202  
         {
 1203  0
             newTarget = newTarget.substring( 1 );
 1204  
         }
 1205  
 
 1206  0
         return absPath + newTarget;
 1207  
     }
 1208  
 
 1209  
     /**
 1210  
      * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
 1211  
      */
 1212  
     protected MavenProject getProject()
 1213  
     {
 1214  170
         return project;
 1215  
     }
 1216  
 
 1217  
     /**
 1218  
      * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
 1219  
      */
 1220  
     protected String getOutputDirectory()
 1221  
     {
 1222  85
         if ( !outputDirectory.isAbsolute() )
 1223  
         {
 1224  85
             outputDirectory = new File( project.getBasedir(), outputDirectory.getPath() );
 1225  
         }
 1226  
 
 1227  85
         return outputDirectory.getAbsolutePath();
 1228  
     }
 1229  
 
 1230  
     /**
 1231  
      * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
 1232  
      */
 1233  
     protected SiteRenderer getSiteRenderer()
 1234  
     {
 1235  150
         return siteRenderer;
 1236  
     }
 1237  
 
 1238  
     /**
 1239  
      * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
 1240  
      */
 1241  
     public String getDescription( Locale locale )
 1242  
     {
 1243  0
         return "Generated change log report from SCM.";
 1244  
     }
 1245  
 
 1246  
     /**
 1247  
      * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
 1248  
      */
 1249  
     public String getName( Locale locale )
 1250  
     {
 1251  85
         return "Change Log";
 1252  
     }
 1253  
 
 1254  
     /**
 1255  
      * @see org.apache.maven.reporting.MavenReport#getOutputName()
 1256  
      */
 1257  
     public String getOutputName()
 1258  
     {
 1259  130
         return "changelog";
 1260  
     }
 1261  
 
 1262  
     /**
 1263  
      * @see org.apache.maven.reporting.MavenReport#getOutputName()
 1264  
      */
 1265  
     protected ResourceBundle getBundle( Locale locale )
 1266  
     {
 1267  65
         return ResourceBundle.getBundle( "scm-activity", locale, this.getClass().getClassLoader() );
 1268  
     }
 1269  
 
 1270  
     public boolean canGenerateReport()
 1271  
     {
 1272  0
         if ( offline && !outputXML.exists() )
 1273  
         {
 1274  0
             return false;
 1275  
         }
 1276  
         else
 1277  
         {
 1278  0
             return true;
 1279  
         }
 1280  
     }
 1281  
 }