package org.apache.maven.plugin.changes;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.util.HtmlTools;
import org.apache.maven.plugin.issues.AbstractIssuesReportGenerator;
import org.apache.maven.plugins.changes.model.Action;
import org.apache.maven.plugins.changes.model.DueTo;
import org.apache.maven.plugins.changes.model.FixedIssue;
import org.apache.maven.plugins.changes.model.Release;
/**
* Generates a changes report.
*
* @version $Id: ChangesReportGenerator.java 1098336 2011-05-01 14:35:34Z dennisl $
*/
public class ChangesReportGenerator extends AbstractIssuesReportGenerator
{
/**
* The token in {@link #issueLinksPerSystem} denoting the base URL for the issue management.
*/
private static final String URL_TOKEN = "%URL%";
/**
* The token in {@link #issueLinksPerSystem} denoting the issue ID.
*/
private static final String ISSUE_TOKEN = "%ISSUE%";
static final String DEFAULT_ISSUE_SYSTEM_KEY = "default";
private static final String NO_TEAMLIST = "none";
/**
* The issue management system to use, for actions that do not specify a
* system.
*
* @since 2.4
*/
private String system;
private String teamlist;
private String url;
private Map issueLinksPerSystem;
private boolean addActionDate;
/**
* @since 2.4
*/
private boolean escapeHTML;
/**
* @since 2.4
*/
private List releaseList;
public ChangesReportGenerator()
{
issueLinksPerSystem = new HashMap();
}
public ChangesReportGenerator( List releaseList )
{
this();
this.releaseList = releaseList;
}
/**
* @since 2.4
*/
public boolean isEscapeHTML()
{
return escapeHTML;
}
/**
* @since 2.4
*/
public void setEscapeHTML( boolean escapeHTML )
{
this.escapeHTML = escapeHTML;
}
/**
* @since 2.4
*/
public String getSystem()
{
return system;
}
/**
* @since 2.4
*/
public void setSystem( String system )
{
this.system = system;
}
public void setTeamlist( final String teamlist )
{
this.teamlist = teamlist;
}
public String getTeamlist()
{
return teamlist;
}
public void setUrl( String url )
{
this.url = url;
}
public String getUrl()
{
return url;
}
public Map getIssueLinksPerSystem()
{
return issueLinksPerSystem;
}
public void setIssueLinksPerSystem( Map issueLinksPerSystem )
{
if ( this.issueLinksPerSystem != null && issueLinksPerSystem == null )
{
return;
}
this.issueLinksPerSystem = issueLinksPerSystem;
}
public boolean isAddActionDate()
{
return addActionDate;
}
public void setAddActionDate( boolean addActionDate )
{
this.addActionDate = addActionDate;
}
/**
* Checks whether links to the issues can be generated for the given system.
*
* @param system The issue management system
* @return <code>true</code> if issue links can be generated, <code>false</code> otherwise.
*/
public boolean canGenerateIssueLinks( String system )
{
if ( !this.issueLinksPerSystem.containsKey( system ) )
{
return false;
}
String issueLink = (String) this.issueLinksPerSystem.get( system );
// If the issue link entry is blank then no links are possible
if ( StringUtils.isBlank( issueLink ) )
{
return false;
}
// If the %URL% token is used then the issue management system URL must be set.
if ( issueLink.indexOf( URL_TOKEN ) >= 0 && StringUtils.isBlank( getUrl() ) )
{
return false;
}
return true;
}
public void doGenerateEmptyReport( ResourceBundle bundle, Sink sink, String message )
{
sinkBeginReport( sink, bundle );
sink.text( message );
sinkEndReport( sink );
}
public void doGenerateReport( ResourceBundle bundle, Sink sink )
{
sinkBeginReport( sink, bundle );
constructReleaseHistory( sink, bundle, releaseList );
constructReleases( sink, bundle, releaseList );
sinkEndReport( sink );
}
private void constructActions( Sink sink, List actionList, ResourceBundle bundle )
{
if ( actionList.isEmpty() )
{
sink.paragraph();
sink.text( bundle.getString( "report.changes.text.no.changes" ) );
sink.paragraph_();
}
else
{
sink.table();
sink.tableRow();
sinkHeader( sink, bundle.getString( "report.issues.label.type" ) );
sinkHeader( sink, bundle.getString( "report.issues.label.summary" ) );
sinkHeader( sink, bundle.getString( "report.issues.label.assignee" ) );
if ( this.isAddActionDate() )
{
sinkHeader( sink, bundle.getString( "report.issues.label.updated" ) );
}
sink.tableRow_();
for ( int idx = 0; idx < actionList.size(); idx++ )
{
Action action = (Action) actionList.get( idx );
sink.tableRow();
sinkShowTypeIcon( sink, action.getType() );
sink.tableCell();
if ( escapeHTML )
{
sink.text( action.getAction() );
}
else
{
sink.rawText( action.getAction() );
}
// no null check needed classes from modello return a new ArrayList
if ( StringUtils.isNotEmpty( action.getIssue() ) || ( !action.getFixedIssues().isEmpty() ) )
{
sink.text( " " + bundle.getString( "report.changes.text.fixes" ) + " " );
// Try to get the issue management system specified in the changes.xml file
String system = action.getSystem();
// Try to get the issue management system configured in the POM
if ( StringUtils.isEmpty( system ) )
{
system = this.system;
}
// Use the default issue management system
if ( StringUtils.isEmpty( system ) )
{
system = DEFAULT_ISSUE_SYSTEM_KEY;
}
if ( !canGenerateIssueLinks( system ) )
{
constructIssueText( action.getIssue(), sink, action.getFixedIssues() );
}
else
{
constructIssueLink( action.getIssue(), system, sink, action.getFixedIssues() );
}
sink.text( "." );
}
if ( StringUtils.isNotEmpty( action.getDueTo() ) || ( !action.getDueTos().isEmpty() ) )
{
constructDueTo( sink, action, bundle, action.getDueTos() );
}
sink.tableCell_();
if ( NO_TEAMLIST.equals( teamlist ) )
{
sinkCell( sink, action.getDev() );
}
else
{
sinkCellLink( sink, action.getDev(), teamlist + "#" + action.getDev() );
}
if ( this.isAddActionDate() )
{
sinkCell( sink, action.getDate() );
}
sink.tableRow_();
}
sink.table_();
}
}
/**
* Construct a text or link that mention the people that helped with an action.
*
* @param sink The sink
* @param action The action that was done
* @param bundle A resource bundle for i18n
* @param dueTos Other people that helped with an action
*/
private void constructDueTo( Sink sink, Action action, ResourceBundle bundle, List dueTos )
{
// Create a Map with key : dueTo name, value : dueTo email
Map<String,String> namesEmailMap = new LinkedHashMap<String,String>();
// Only add the dueTo specified as attributes, if it has either a dueTo or a dueToEmail
if ( StringUtils.isNotEmpty( action.getDueTo() ) || StringUtils.isNotEmpty( action.getDueToEmail() ) )
{
namesEmailMap.put( action.getDueTo(), action.getDueToEmail() );
}
for ( Iterator iterator = dueTos.iterator(); iterator.hasNext(); )
{
DueTo dueTo = (DueTo) iterator.next();
namesEmailMap.put( dueTo.getName(), dueTo.getEmail() );
}
if ( namesEmailMap.isEmpty() )
{
return;
}
sink.text( " " + bundle.getString( "report.changes.text.thanx" ) + " " );
int i = 0;
for ( String currentDueTo : namesEmailMap.keySet() )
{
String currentDueToEmail = namesEmailMap.get( currentDueTo );
i++;
if ( StringUtils.isNotEmpty( currentDueToEmail ) )
{
sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
}
else if ( StringUtils.isNotEmpty( currentDueTo ) )
{
sink.text( currentDueTo );
}
if ( i < namesEmailMap.size() )
{
sink.text( ", " );
}
}
sink.text( "." );
}
/**
* Construct links to the issues that were solved by an action.
*
* @param issue The issue specified by attributes
* @param system The issue management system
* @param sink The sink
* @param fixes The List of issues specified as fixes elements
*/
private void constructIssueLink( String issue, String system, Sink sink, List fixes )
{
if ( StringUtils.isNotEmpty( issue ) )
{
sink.link( parseIssueLink( issue, system ) );
sink.text( issue );
sink.link_();
if ( !fixes.isEmpty() )
{
sink.text( ", " );
}
}
for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
{
FixedIssue fixedIssue = (FixedIssue) iterator.next();
String currentIssueId = fixedIssue.getIssue();
if ( StringUtils.isNotEmpty( currentIssueId ) )
{
sink.link( parseIssueLink( currentIssueId, system ) );
sink.text( currentIssueId );
sink.link_();
}
if ( iterator.hasNext() )
{
sink.text( ", " );
}
}
}
/**
* Construct a text that references (but does not link to) the issues that
* were solved by an action.
*
* @param issue The issue specified by attributes
* @param sink The sink
* @param fixes The List of issues specified as fixes elements
*/
private void constructIssueText( String issue, Sink sink, List fixes )
{
if ( StringUtils.isNotEmpty( issue ) )
{
sink.text( issue );
if ( !fixes.isEmpty() )
{
sink.text( ", " );
}
}
for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
{
FixedIssue fixedIssue = (FixedIssue) iterator.next();
String currentIssueId = fixedIssue.getIssue();
if ( StringUtils.isNotEmpty( currentIssueId ) )
{
sink.text( currentIssueId );
}
if ( iterator.hasNext() )
{
sink.text( ", " );
}
}
}
private void constructReleaseHistory( Sink sink, ResourceBundle bundle, List releaseList )
{
sink.section2();
sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.releasehistory" ),
bundle.getString( "report.changes.label.releasehistory" ) );
sink.table();
sink.tableRow();
sinkHeader( sink, bundle.getString( "report.issues.label.fixVersion" ) );
sinkHeader( sink, bundle.getString( "report.changes.label.releaseDate" ) );
sinkHeader( sink, bundle.getString( "report.changes.label.releaseDescription" ) );
sink.tableRow_();
for ( int idx = 0; idx < releaseList.size(); idx++ )
{
Release release = (Release) releaseList.get( idx );
sink.tableRow();
sinkCellLink( sink, release.getVersion(), "#" + HtmlTools.encodeId( release.getVersion() ) );
sinkCell( sink, release.getDateRelease() );
sinkCell( sink, release.getDescription() );
sink.tableRow_();
}
sink.table_();
// @todo Temporarily commented out until MCHANGES-46 is completely solved
// sink.rawText( bundle.getString( "report.changes.text.rssfeed" ) );
// sink.text( " " );
// sink.link( "changes.rss" );
// sinkFigure( "images/rss.png", sink );
// sink.link_();
//
// sink.lineBreak();
sink.section2_();
}
private void constructReleases( Sink sink, ResourceBundle bundle, List releaseList )
{
for ( int idx = 0; idx < releaseList.size(); idx++ )
{
Release release = (Release) releaseList.get( idx );
sink.section2();
final String date = ( release.getDateRelease() == null ) ? "" : " - " + release.getDateRelease();
sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.release" ) + " "
+ release.getVersion() + date, release.getVersion() );
constructActions( sink, release.getActions(), bundle );
sink.section2_();
}
}
/**
* Replace tokens in the issue link template with the real values.
*
* @param issue The issue identifier
* @param system The issue management system
* @return An interpolated issue link
*/
private String parseIssueLink( String issue, String system )
{
String parseLink;
String issueLink = (String) this.issueLinksPerSystem.get( system );
parseLink = issueLink.replaceFirst( ISSUE_TOKEN, issue );
if ( parseLink.indexOf( URL_TOKEN ) >= 0 )
{
String url = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
parseLink = parseLink.replaceFirst( URL_TOKEN, url );
}
return parseLink;
}
}