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