Coverage Report - org.apache.maven.plugin.changes.ChangesReportGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
ChangesReportGenerator
0%
0/203
0%
0/96
3.037
 
 1  
 package org.apache.maven.plugin.changes;
 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 java.util.HashMap;
 23  
 import java.util.Iterator;
 24  
 import java.util.LinkedHashMap;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.ResourceBundle;
 28  
 
 29  
 import org.apache.commons.lang.StringUtils;
 30  
 import org.apache.maven.doxia.sink.Sink;
 31  
 import org.apache.maven.doxia.util.HtmlTools;
 32  
 import org.apache.maven.plugin.issues.AbstractIssuesReportGenerator;
 33  
 import org.apache.maven.plugins.changes.model.Action;
 34  
 import org.apache.maven.plugins.changes.model.Component;
 35  
 import org.apache.maven.plugins.changes.model.DueTo;
 36  
 import org.apache.maven.plugins.changes.model.FixedIssue;
 37  
 import org.apache.maven.plugins.changes.model.Release;
 38  
 
 39  
 /**
 40  
  * Generates a changes report.
 41  
  *
 42  
  * @version $Id: ChangesReportGenerator.java 1328886 2012-04-22 14:09:45Z bimargulies $
 43  
  */
 44  
 public class ChangesReportGenerator extends AbstractIssuesReportGenerator
 45  
 {
 46  
 
 47  
     /**
 48  
      * The token in {@link #issueLinksPerSystem} denoting the base URL for the issue management.
 49  
      */
 50  
     private static final String URL_TOKEN = "%URL%";
 51  
 
 52  
     /**
 53  
      * The token in {@link #issueLinksPerSystem} denoting the issue ID.
 54  
      */
 55  
     private static final String ISSUE_TOKEN = "%ISSUE%";
 56  
 
 57  
     static final String DEFAULT_ISSUE_SYSTEM_KEY = "default";
 58  
 
 59  
     private static final String NO_TEAMLIST = "none";
 60  
 
 61  
     /**
 62  
      * The issue management system to use, for actions that do not specify a
 63  
      * system.
 64  
      *
 65  
      * @since 2.4
 66  
      */
 67  
     private String system;
 68  
 
 69  
     private String teamlist;
 70  
 
 71  
     private String url;
 72  
 
 73  
     private Map issueLinksPerSystem;
 74  
 
 75  
     private boolean addActionDate;
 76  
 
 77  
     /**
 78  
      * @since 2.4
 79  
      */
 80  
     private boolean escapeHTML;
 81  
 
 82  
     /**
 83  
      * @since 2.4
 84  
      */
 85  
     private List releaseList;
 86  
 
 87  
     public ChangesReportGenerator()
 88  0
     {
 89  0
         issueLinksPerSystem = new HashMap();
 90  0
     }
 91  
 
 92  
     public ChangesReportGenerator( List releaseList )
 93  
     {
 94  0
         this();
 95  0
         this.releaseList = releaseList;
 96  0
     }
 97  
 
 98  
     /**
 99  
      * @since 2.4
 100  
      */
 101  
     public boolean isEscapeHTML()
 102  
     {
 103  0
         return escapeHTML;
 104  
     }
 105  
 
 106  
     /**
 107  
      * @since 2.4
 108  
      */
 109  
     public void setEscapeHTML( boolean escapeHTML )
 110  
     {
 111  0
         this.escapeHTML = escapeHTML;
 112  0
     }
 113  
 
 114  
     /**
 115  
      * @since 2.4
 116  
      */
 117  
     public String getSystem()
 118  
     {
 119  0
         return system;
 120  
     }
 121  
 
 122  
     /**
 123  
      * @since 2.4
 124  
      */
 125  
     public void setSystem( String system )
 126  
     {
 127  0
         this.system = system;
 128  0
     }
 129  
 
 130  
     public void setTeamlist( final String teamlist )
 131  
     {
 132  0
         this.teamlist = teamlist;
 133  0
     }
 134  
 
 135  
     public String getTeamlist()
 136  
     {
 137  0
         return teamlist;
 138  
     }
 139  
 
 140  
     public void setUrl( String url )
 141  
     {
 142  0
         this.url = url;
 143  0
     }
 144  
 
 145  
     public String getUrl()
 146  
     {
 147  0
         return url;
 148  
     }
 149  
 
 150  
     public Map getIssueLinksPerSystem()
 151  
     {
 152  0
         return issueLinksPerSystem;
 153  
     }
 154  
 
 155  
     public void setIssueLinksPerSystem( Map issueLinksPerSystem )
 156  
     {
 157  0
         if ( this.issueLinksPerSystem != null && issueLinksPerSystem == null )
 158  
         {
 159  0
             return;
 160  
         }
 161  0
         this.issueLinksPerSystem = issueLinksPerSystem;
 162  0
     }
 163  
 
 164  
     public boolean isAddActionDate()
 165  
     {
 166  0
         return addActionDate;
 167  
     }
 168  
 
 169  
     public void setAddActionDate( boolean addActionDate )
 170  
     {
 171  0
         this.addActionDate = addActionDate;
 172  0
     }
 173  
 
 174  
     /**
 175  
      * Checks whether links to the issues can be generated for the given system.
 176  
      *
 177  
      * @param system The issue management system
 178  
      * @return <code>true</code> if issue links can be generated, <code>false</code> otherwise.
 179  
      */
 180  
     public boolean canGenerateIssueLinks( String system )
 181  
     {
 182  0
         if ( !this.issueLinksPerSystem.containsKey( system ) )
 183  
         {
 184  0
             return false;
 185  
         }
 186  0
         String issueLink = (String) this.issueLinksPerSystem.get( system );
 187  
 
 188  
         // If the issue link entry is blank then no links are possible
 189  0
         if ( StringUtils.isBlank( issueLink ) )
 190  
         {
 191  0
             return false;
 192  
         }
 193  
 
 194  
         // If the %URL% token is used then the issue management system URL must be set.
 195  0
         if ( issueLink.indexOf( URL_TOKEN ) >= 0 && StringUtils.isBlank( getUrl() ) )
 196  
         {
 197  0
             return false;
 198  
         }
 199  0
         return true;
 200  
     }
 201  
 
 202  
     public void doGenerateEmptyReport( ResourceBundle bundle, Sink sink, String message )
 203  
     {
 204  0
         sinkBeginReport( sink, bundle );
 205  
 
 206  0
         sink.text( message );
 207  
 
 208  0
         sinkEndReport( sink );
 209  0
     }
 210  
 
 211  
     public void doGenerateReport( ResourceBundle bundle, Sink sink )
 212  
     {
 213  0
         sinkBeginReport( sink, bundle );
 214  
 
 215  0
         constructReleaseHistory(sink, bundle, releaseList);
 216  
 
 217  0
         constructReleases(sink, bundle, releaseList);
 218  
 
 219  0
         sinkEndReport( sink );
 220  0
     }
 221  
 
 222  
     /**
 223  
      * Constructs table row for specified action with all calculated content (e.g. issue link).
 224  
      *
 225  
      * @param sink Sink
 226  
      * @param bundle Resource bundle
 227  
      * @param action Action to generate content for
 228  
      */
 229  
     private void constructAction( Sink sink, ResourceBundle bundle, Action action )
 230  
     {
 231  0
         sink.tableRow();
 232  
 
 233  0
         sinkShowTypeIcon(sink, action.getType());
 234  
 
 235  0
         sink.tableCell();
 236  
 
 237  0
         if ( escapeHTML )
 238  
         {
 239  0
             sink.text( action.getAction() );
 240  
         }
 241  
         else
 242  
         {
 243  0
             sink.rawText( action.getAction() );
 244  
         }
 245  
 
 246  
         // no null check needed classes from modello return a new ArrayList
 247  0
         if ( StringUtils.isNotEmpty( action.getIssue() ) || ( !action.getFixedIssues().isEmpty() ) )
 248  
         {
 249  0
             sink.text( " " + bundle.getString( "report.changes.text.fixes" ) + " " );
 250  
 
 251  
             // Try to get the issue management system specified in the changes.xml file
 252  0
             String system = action.getSystem();
 253  
             // Try to get the issue management system configured in the POM
 254  0
             if ( StringUtils.isEmpty( system ) )
 255  
             {
 256  0
                 system = this.system;
 257  
             }
 258  
             // Use the default issue management system
 259  0
             if ( StringUtils.isEmpty( system ) )
 260  
             {
 261  0
                 system = DEFAULT_ISSUE_SYSTEM_KEY;
 262  
             }
 263  0
             if ( !canGenerateIssueLinks( system ) )
 264  
             {
 265  0
                 constructIssueText( action.getIssue(), sink, action.getFixedIssues() );
 266  
             }
 267  
             else
 268  
             {
 269  0
                 constructIssueLink( action.getIssue(), system, sink, action.getFixedIssues() );
 270  
             }
 271  0
             sink.text( "." );
 272  
         }
 273  
 
 274  0
         if ( StringUtils.isNotEmpty( action.getDueTo() ) || ( !action.getDueTos().isEmpty() ) )
 275  
         {
 276  0
             constructDueTo( sink, action, bundle, action.getDueTos() );
 277  
         }
 278  
 
 279  0
         sink.tableCell_();
 280  
 
 281  0
         if ( NO_TEAMLIST.equals( teamlist ) )
 282  
         {
 283  0
             sinkCell( sink, action.getDev() );
 284  
         }
 285  
         else
 286  
         {
 287  0
             sinkCellLink( sink, action.getDev(), teamlist + "#" + action.getDev() );
 288  
         }
 289  
 
 290  0
         if ( this.isAddActionDate() )
 291  
         {
 292  0
             sinkCell( sink, action.getDate() );
 293  
         }
 294  
 
 295  0
         sink.tableRow_();
 296  0
     }
 297  
 
 298  
     /**
 299  
      * Construct a text or link that mention the people that helped with an action.
 300  
      *
 301  
      * @param sink The sink
 302  
      * @param action The action that was done
 303  
      * @param bundle A resource bundle for i18n
 304  
      * @param dueTos Other people that helped with an action
 305  
      */
 306  
     private void constructDueTo( Sink sink, Action action, ResourceBundle bundle, List dueTos )
 307  
     {
 308  
 
 309  
         // Create a Map with key : dueTo name, value : dueTo email
 310  0
         Map<String,String> namesEmailMap = new LinkedHashMap<String,String>();
 311  
 
 312  
         // Only add the dueTo specified as attributes, if it has either a dueTo or a dueToEmail
 313  0
         if ( StringUtils.isNotEmpty( action.getDueTo() ) || StringUtils.isNotEmpty( action.getDueToEmail() ) )
 314  
         {
 315  0
             namesEmailMap.put(action.getDueTo(), action.getDueToEmail());
 316  
         }
 317  
 
 318  0
         for ( Iterator iterator = dueTos.iterator(); iterator.hasNext(); )
 319  
         {
 320  0
             DueTo dueTo = (DueTo) iterator.next();
 321  0
             namesEmailMap.put( dueTo.getName(), dueTo.getEmail() );
 322  0
         }
 323  
 
 324  0
         if ( namesEmailMap.isEmpty() )
 325  
         {
 326  0
             return;
 327  
         }
 328  
 
 329  0
         sink.text( " " + bundle.getString( "report.changes.text.thanx" ) + " " );
 330  0
         int i = 0;
 331  0
         for ( String currentDueTo : namesEmailMap.keySet() )
 332  
         {
 333  0
             String currentDueToEmail = namesEmailMap.get( currentDueTo );
 334  0
             i++;
 335  
 
 336  0
             if ( StringUtils.isNotEmpty( currentDueToEmail ) )
 337  
             {
 338  0
                 sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
 339  
             }
 340  0
             else if ( StringUtils.isNotEmpty( currentDueTo ) )
 341  
             {
 342  0
                 sink.text( currentDueTo );
 343  
             }
 344  
 
 345  0
             if ( i < namesEmailMap.size() )
 346  
             {
 347  0
                 sink.text( ", " );
 348  
             }
 349  0
         }
 350  
 
 351  0
         sink.text(".");
 352  0
     }
 353  
 
 354  
     /**
 355  
      * Construct links to the issues that were solved by an action.
 356  
      *
 357  
      * @param issue The issue specified by attributes
 358  
      * @param system The issue management system
 359  
      * @param sink The sink
 360  
      * @param fixes The List of issues specified as fixes elements
 361  
      */
 362  
     private void constructIssueLink( String issue, String system, Sink sink, List fixes )
 363  
     {
 364  0
         if ( StringUtils.isNotEmpty( issue ) )
 365  
         {
 366  0
             sink.link( parseIssueLink( issue, system ) );
 367  
 
 368  0
             sink.text( issue );
 369  
 
 370  0
             sink.link_();
 371  
 
 372  0
             if ( !fixes.isEmpty() )
 373  
             {
 374  0
                 sink.text( ", " );
 375  
             }
 376  
         }
 377  
 
 378  0
         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
 379  
         {
 380  0
             FixedIssue fixedIssue = (FixedIssue) iterator.next();
 381  0
             String currentIssueId = fixedIssue.getIssue();
 382  0
             if ( StringUtils.isNotEmpty( currentIssueId ) )
 383  
             {
 384  0
                 sink.link( parseIssueLink( currentIssueId, system ) );
 385  
 
 386  0
                 sink.text( currentIssueId );
 387  
 
 388  0
                 sink.link_();
 389  
             }
 390  
 
 391  0
             if ( iterator.hasNext() )
 392  
             {
 393  0
                 sink.text( ", " );
 394  
             }
 395  0
         }
 396  0
     }
 397  
 
 398  
     /**
 399  
      * Construct a text that references (but does not link to) the issues that
 400  
      * were solved by an action.
 401  
      *
 402  
      * @param issue The issue specified by attributes
 403  
      * @param sink The sink
 404  
      * @param fixes The List of issues specified as fixes elements
 405  
      */
 406  
     private void constructIssueText( String issue, Sink sink, List fixes )
 407  
     {
 408  0
         if ( StringUtils.isNotEmpty( issue ) )
 409  
         {
 410  0
             sink.text(issue);
 411  
 
 412  0
             if ( !fixes.isEmpty() )
 413  
             {
 414  0
                 sink.text( ", " );
 415  
             }
 416  
         }
 417  
 
 418  0
         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
 419  
         {
 420  0
             FixedIssue fixedIssue = (FixedIssue) iterator.next();
 421  
 
 422  0
             String currentIssueId = fixedIssue.getIssue();
 423  0
             if ( StringUtils.isNotEmpty( currentIssueId ) )
 424  
             {
 425  0
                 sink.text(currentIssueId);
 426  
             }
 427  
 
 428  0
             if ( iterator.hasNext() )
 429  
             {
 430  0
                 sink.text( ", " );
 431  
             }
 432  0
         }
 433  0
     }
 434  
 
 435  
     private void constructReleaseHistory( Sink sink, ResourceBundle bundle, List releaseList )
 436  
     {
 437  0
         sink.section2();
 438  
 
 439  0
         sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.releasehistory" ),
 440  
                                  bundle.getString( "report.changes.label.releasehistory" ) );
 441  
 
 442  0
         sink.table();
 443  
 
 444  0
         sink.tableRow();
 445  
 
 446  0
         sinkHeader( sink, bundle.getString( "report.issues.label.fixVersion" ) );
 447  
 
 448  0
         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDate" ) );
 449  
 
 450  0
         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDescription" ) );
 451  
 
 452  0
         sink.tableRow_();
 453  
 
 454  0
         for ( int idx = 0; idx < releaseList.size(); idx++ )
 455  
         {
 456  0
             Release release = (Release) releaseList.get( idx );
 457  
 
 458  0
             sink.tableRow();
 459  
 
 460  0
             sinkCellLink( sink, release.getVersion(), "#" + HtmlTools.encodeId( release.getVersion() ) );
 461  
 
 462  0
             sinkCell( sink, release.getDateRelease() );
 463  
 
 464  0
             sinkCell( sink, release.getDescription() );
 465  
 
 466  0
             sink.tableRow_();
 467  
         }
 468  
 
 469  0
         sink.table_();
 470  
 
 471  
         // @todo Temporarily commented out until MCHANGES-46 is completely solved
 472  
         // sink.rawText( bundle.getString( "report.changes.text.rssfeed" ) );
 473  
         // sink.text( " " );
 474  
         // sink.link( "changes.rss" );
 475  
         // sinkFigure( "images/rss.png", sink );
 476  
         // sink.link_();
 477  
         //
 478  
         // sink.lineBreak();
 479  
 
 480  0
         sink.section2_();
 481  0
     }
 482  
 
 483  
     /**
 484  
      * Constructs document sections for each of specified releases.
 485  
      *
 486  
      * @param sink Sink
 487  
      * @param bundle Resource bundle
 488  
      * @param releaseList Releases to create content for
 489  
      */
 490  
     private void constructReleases( Sink sink, ResourceBundle bundle, List releaseList )
 491  
     {
 492  0
         for ( int idx = 0; idx < releaseList.size(); idx++ )
 493  
         {
 494  0
             Release release = (Release) releaseList.get(idx);
 495  0
             constructRelease( sink, bundle, release );
 496  
         }
 497  0
     }
 498  
 
 499  
     /**
 500  
      * Constructs document section for specified release.
 501  
      *
 502  
      * @param sink Sink
 503  
      * @param bundle Resource bundle
 504  
      * @param release Release to create document section for
 505  
      */
 506  
     private void constructRelease( Sink sink, ResourceBundle bundle, Release release )
 507  
     {
 508  0
         sink.section2();
 509  
 
 510  0
         final String date = ( release.getDateRelease() == null ) ? "" : " - " + release.getDateRelease();
 511  
 
 512  0
         sinkSectionTitle2Anchor(sink, bundle.getString("report.changes.label.release") + " "
 513  
                 + release.getVersion() + date, release.getVersion());
 514  
 
 515  0
         if ( isReleaseEmpty( release ) )
 516  
         {
 517  0
             sink.paragraph();
 518  0
             sink.text( bundle.getString("report.changes.text.no.changes") );
 519  0
             sink.paragraph_();
 520  
         }
 521  
         else
 522  
         {
 523  0
             sink.table();
 524  
 
 525  0
             sink.tableRow();
 526  0
             sinkHeader( sink, bundle.getString( "report.issues.label.type" ) );
 527  0
             sinkHeader( sink, bundle.getString( "report.issues.label.summary" ) );
 528  0
             sinkHeader(sink, bundle.getString("report.issues.label.assignee"));
 529  0
             if ( this.isAddActionDate() )
 530  
             {
 531  0
                 sinkHeader( sink, bundle.getString( "report.issues.label.updated" ) );
 532  
             }
 533  0
             sink.tableRow_();
 534  
 
 535  0
             for (Iterator iterator = release.getActions().iterator(); iterator.hasNext();)
 536  
             {
 537  0
                 Action action = (Action) iterator.next();
 538  0
                 constructAction(sink, bundle, action);
 539  0
             }
 540  
 
 541  0
             for (Iterator iterator = release.getComponents().iterator(); iterator.hasNext();)
 542  
             {
 543  0
                 Component component = (Component) iterator.next();
 544  0
                 constructComponent( sink, bundle, component );
 545  0
             }
 546  
 
 547  0
             sink.table_();
 548  
 
 549  0
             sink.section2_();
 550  
         }
 551  0
     }
 552  
 
 553  
     /**
 554  
      * Constructs table rows for specified release component. It will create header row for
 555  
      * component name and action rows for all component issues.
 556  
      *
 557  
      * @param sink Sink
 558  
      * @param bundle Resource bundle
 559  
      * @param component Release component to generate content for.
 560  
      */
 561  
     private void constructComponent( Sink sink, ResourceBundle bundle, Component component )
 562  
     {
 563  0
         if ( !component.getActions().isEmpty() )
 564  
         {
 565  0
             sink.tableRow();
 566  
 
 567  0
             sink.tableHeaderCell();
 568  0
             sink.tableHeaderCell_();
 569  
 
 570  0
             sink.tableHeaderCell();
 571  0
             sink.text(component.getName());
 572  0
             sink.tableHeaderCell_();
 573  
 
 574  0
             sink.tableHeaderCell();
 575  0
             sink.tableHeaderCell_();
 576  
 
 577  0
             if ( isAddActionDate() )
 578  
             {
 579  0
                 sink.tableHeaderCell();
 580  0
                 sink.tableHeaderCell_();
 581  
             }
 582  
 
 583  0
             sink.tableRow_();
 584  
 
 585  0
             for ( Iterator iterator = component.getActions().iterator(); iterator.hasNext(); )
 586  
             {
 587  0
                 Action action = (Action) iterator.next();
 588  0
                 constructAction( sink, bundle, action );
 589  0
             }
 590  
         }
 591  0
     }
 592  
 
 593  
     /**
 594  
      * Checks if specified release contains own issues or issues inside the child components.
 595  
      *
 596  
      * @param release Release to check
 597  
      * @return <code>true</code> if release doesn't contain any issues, <code>false</code> otherwise
 598  
      */
 599  
     private boolean isReleaseEmpty( Release release ) {
 600  0
         if ( !release.getActions().isEmpty() )
 601  
         {
 602  0
             return false;
 603  
         }
 604  
 
 605  0
         for (Iterator iterator = release.getComponents().iterator(); iterator.hasNext();)
 606  
         {
 607  0
             Component component = (Component) iterator.next();
 608  0
             if ( !component.getActions().isEmpty() )
 609  
             {
 610  0
                 return false;
 611  
             }
 612  0
         }
 613  
 
 614  0
         return true;
 615  
     }
 616  
 
 617  
     /**
 618  
      * Replace tokens in the issue link template with the real values.
 619  
      *
 620  
      * @param issue The issue identifier
 621  
      * @param system The issue management system
 622  
      * @return An interpolated issue link
 623  
      */
 624  
     private String parseIssueLink( String issue, String system )
 625  
     {
 626  
         String parseLink;
 627  0
         String issueLink = (String) this.issueLinksPerSystem.get( system );
 628  0
         parseLink = issueLink.replaceFirst( ISSUE_TOKEN, issue );
 629  0
         if ( parseLink.indexOf( URL_TOKEN ) >= 0 )
 630  
         {
 631  0
             String url = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
 632  0
             parseLink = parseLink.replaceFirst( URL_TOKEN, url );
 633  
         }
 634  
 
 635  0
         return parseLink;
 636  
     }
 637  
 
 638  
 }