package org.apache.maven.changelog;
/* ====================================================================
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed 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.
* ====================================================================
*/
// java imports
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.project.Developer;
/**
* Change log task. It uses a ChangeLogGenerator and ChangeLogParser to create
* a Collection of ChangeLogEntry objects, which are used to produce an XML
* output that represents the list of changes.
*
*
* @author Glenn McAllister
* @author Jeff Martin
* @author Jason van Zyl
* @author dIon Gillard
* @author Stefan Bodewig
* @author Peter Donald
* @version $Id: ChangeLog.java 293407 2005-10-03 19:37:51 +0200 (Mo, 03 Okt 2005) ltheussl $
*/
public class ChangeLog
{
/** Used to specify whether to build the log from a range, absolute date, or tag. */
private String type;
/**
* Used to specify the range of log entries to retrieve.
*/
private String range;
/** Used to specify the absolute date (or list of dates) to start log entries from. */
private String date;
/** Used to specify the tag (or list of tags) to start log entries from. */
private String tag;
/** This will contain the date/tag for the start of the current change set. */
private String markerStart;
/** This will contain the date/tag for the end of the current change set. */
private String markerEnd;
/**
* Used to specify the date format of log entries to retrieve.
*/
private String dateFormat;
/**
* Input dir. Working directory for running CVS executable
*/
private File base;
/**
* The classname of our ChangeLogFactory, defaulting to Maven's built in
* CVS factory.
*/
private String clFactoryClass = null;
private static final Map FACTORIES = new HashMap();
/** the connection string used to access the SCM */
private String connection;
/** the list of developers on the project */
private List developers;
/** change log sets parsed (sets of entries) */
private Collection sets;
/** Log */
private static final Log LOG = LogFactory.getLog(ChangeLog.class);
/**
* Output file for xml document
*/
private File output;
/** output encoding for the xml document */
private String outputEncoding;
static
{
FACTORIES.put( "cvs", "org.apache.maven.cvslib.CvsChangeLogFactory" );
FACTORIES.put( "svn", "org.apache.maven.svnlib.SvnChangeLogFactory" );
FACTORIES.put( "clearcase", "org.apache.maven.clearcaselib.ClearcaseChangeLogFactory" );
FACTORIES.put( "perforce", "org.apache.maven.perforcelib.PerforceChangeLogFactory" );
FACTORIES.put( "starteam", "org.apache.maven.starteamlib.StarteamChangeLogFactory" );
FACTORIES.put( "vss", "org.apache.maven.vsslib.VssChangeLogFactory" );
FACTORIES.put( "mks", "org.apache.maven.mkslib.MksChangeLogFactory" );
}
/**
* Comment format string used for interrogating
* the revision control system.
* Currently only used by the ClearcaseChangeLogGenerator.
*/
private String commentFormat;
/**
* Set the ChangeLogFactory class name. If this isn't set, the factory
* defaults to Maven's build in CVS factory.
*
* @param factoryClassName the fully qualified factory class name
*/
public void setFactory(String factoryClassName)
{
clFactoryClass = factoryClassName;
}
/**
* Set the type of log to generate (range, date, or tag).
*
* @param type one of "range", "date", or "tag".
*/
public void setType(String type)
{
this.type = type;
}
/**
* Get the type of log to generate (range, date, or tag).
*
* @return the basis for the log.
*/
public String getType()
{
if ( type == null )
{
type = "range";
}
return type;
}
/**
* Set the range of log entries to process; the interpretation of this
* parameter depends on the generator.
* This is only used if the type is "range".
*
* @param range the range of log entries.
*/
public void setRange(String range)
{
this.range = range;
}
/**
* Get the range of log entries to process; the interpretation of the range
* depends on the generator implementation.
*
* @return the range of log entries.
*/
public String getRange()
{
return range;
}
/**
* Set the date to start the log from.
* This is only used if the type is "date".
* The format is that given by the dateFormat property, if present. Otherwise, the format is "yyyy-MM-dd".
*
* @param date the date to use.
*/
public void setDate(String date)
{
this.date = date;
}
/**
* Get the date to start the log from.
*
* @return the start date.
*/
public String getDate()
{
return date;
}
/**
* Set the tag to start the log from.
* This is only used if the type is "tag".
*
* @param tag the tag to use.
*/
public void setTag(String tag)
{
this.tag = tag;
}
/**
* Get the tag to start the log from.
*
* @return the tag.
*/
public String getTag()
{
return tag;
}
/**
* Sets the marker (date or tag) for the start of the current change set.
* (This is only set internally, but also by test code.)
*
* @param marker start marker to use.
*/
public void setMarkerStart(String marker)
{
markerStart = marker;
}
/**
* Returns the marker (date or tag) for the start of the current change set.
* Whether it's a date or tag depends on {@link #getType}.
*
* @return the marker (date or tag) for the start of the current change set.
*/
public String getMarkerStart()
{
return markerStart;
}
/**
* Sets the marker (date or tag) for the end of the current change set.
* (This is only set internally, but also by test code.)
*
* @param marker end marker to use, or null to specify all changes since the start.
*/
public void setMarkerEnd(String marker)
{
markerEnd = marker;
}
/**
* Returns the marker (date or tag) for the end of the current change set.
* Whether it's a date or tag depends on {@link #getType}.
*
* @return the marker (date or tag) for the end of the current change set, or null if there is no
* end (meaning the change set should show all changes from the start to the present time).
*/
public String getMarkerEnd()
{
return markerEnd;
}
/**
* Set the date format of log entries to process; the
* interpretation of this parameter depends on the generator.
*
* @param dateFormat the dateFormat of log entries.
*/
public void setDateFormat(String dateFormat)
{
this.dateFormat = dateFormat;
}
/**
* Get the date format of log entries to process; the
* interpretation of this parameter depends on the generator.
*
* @return the dateFormat of log entries.
*/
public String getDateFormat()
{
return dateFormat;
}
/**
* Set the base directory for the change log generator.
* @param base the base directory
*/
public void setBasedir(File base)
{
this.base = base;
}
/**
* Get the base directory for the change log generator.
*
* @return the base directory
*/
public File getBasedir()
{
return base;
}
/**
* Set the output file for the log.
* @param output the output file
*/
public void setOutput(File output)
{
this.output = output;
}
/**
* Return connection string declared in project.xml
* @return connection string
*/
public String getRepositoryConnection()
{
return connection;
}
/**
* Change SCM connection string
* @param aString a string containing the project's repository
* connection
*/
public void setRepositoryConnection(String aString)
{
connection = aString;
}
/**
* Execute task.
* @throws FileNotFoundException if the base diretory
* {@link ChangeLog#getBasedir} doesn't exist
* @throws IOException if there are problems running CVS
* @throws UnsupportedEncodingException if the underlying platform doesn't
* support ISO-8859-1 encoding
*/
public void doExecute() throws FileNotFoundException, IOException,
UnsupportedEncodingException
{
if (output == null)
{
throw new NullPointerException("output must be set");
}
generateSets();
replaceAuthorIdWithName();
createDocument();
}
/**
* Create the change log entries.
* @throws IOException if there is a problem creating the change log
* entries.
*/
private void generateSets() throws IOException
{
ChangeLogFactory factory = createFactory();
String markers = "";
if (getType().equalsIgnoreCase("tag"))
{
markers = getTag();
}
else if (getType().equalsIgnoreCase("date"))
{
markers = getDate();
}
else
{
markers = getRange();
}
try
{
StringTokenizer tokens = new StringTokenizer(markers, ",");
sets = new ArrayList(tokens.countTokens());
String end = tokens.nextToken();
do
{
String start = end;
end = (tokens.hasMoreTokens()) ? tokens.nextToken() : null;
setMarkerStart(start);
setMarkerEnd(end);
ChangeLogParser parser = factory.createParser();
if (getDateFormat() != null)
{
parser.setDateFormatInFile(getDateFormat());
}
parser.init(this);
ChangeLogGenerator generator = factory.createGenerator();
generator.init(this);
Collection entries;
String logStart;
String logEnd;
try
{
entries = generator.getEntries(parser);
logStart = generator.getLogStart();
logEnd = generator.getLogEnd();
} catch (IOException e) {
LOG.warn(e.getLocalizedMessage(), e);
throw e;
}
finally
{
generator.cleanup();
parser.cleanup();
}
if ( entries == null )
{
entries = Collections.EMPTY_LIST;
}
sets.add(new ChangeLogSet(entries, logStart, logEnd));
if (LOG.isInfoEnabled()) {
LOG.info("ChangeSet between " + logStart + " and " + logEnd + ": "
+ entries.size() + " entries");
}
} while (end != null);
}
finally
{
}
}
/**
* Create a new instance of the ChangeLogFactory specified by the
* clFactory member.
*
* @return the new ChangeLogFactory instance
* @throws IOException if there is a problem creating the instance.
*/
private ChangeLogFactory createFactory() throws IOException
{
if ( clFactoryClass == null )
{
if ( connection == null || connection.length() < 7 || !connection.startsWith( "scm:" ) )
{
LOG.warn( "Connection does not appear valid" );
}
else
{
clFactoryClass = (String) FACTORIES.get( connection.substring( 4, 7 ) );
}
if ( clFactoryClass == null )
{
LOG.warn( "Could not derive factory from connection: using CVS (valid factories are: " + FACTORIES.keySet() + ")" );
clFactoryClass = "org.apache.maven.cvslib.CvsChangeLogFactory";
}
}
try
{
Class clazz = Class.forName(clFactoryClass);
return (ChangeLogFactory) clazz.newInstance();
}
catch (ClassNotFoundException cnfe)
{
throw new IOException("Cannot find class " + clFactoryClass
+ " " + cnfe.toString());
}
catch (IllegalAccessException iae)
{
throw new IOException("Cannot access class " + clFactoryClass
+ " " + iae.toString());
}
catch (InstantiationException ie)
{
throw new IOException("Cannot instantiate class " + clFactoryClass
+ " " + ie.toString());
}
}
/**
* Set up list of developers mapping id to name.
* @task This should be a facility on the maven project itself
* @return a list of developers ids and names
*/
private Properties getUserList()
{
Properties userList = new Properties();
Developer developer = null;
for (Iterator i = getDevelopers().iterator(); i.hasNext();)
{
developer = (Developer) i.next();
userList.put(developer.getId(), developer.getName());
}
return userList;
}
/**
* replace all known author's id's with their maven specified names
*/
private void replaceAuthorIdWithName()
{
Properties userList = getUserList();
ChangeLogEntry entry = null;
for (final Iterator iSets = getChangeSets().iterator() ; iSets.hasNext() ;)
{
final ChangeLogSet set = (ChangeLogSet)iSets.next();
for (Iterator iEntries = set.getEntries().iterator(); iEntries.hasNext();)
{
entry = (ChangeLogEntry) iEntries.next();
if (userList.containsKey(entry.getAuthor()))
{
entry.setAuthor(userList.getProperty(entry.getAuthor()));
}
}
}
}
/**
* Create the XML document from the currently available details
* @throws FileNotFoundException when the output file previously provided
* does not exist
* @throws UnsupportedEncodingException when the platform doesn't support
* ISO-8859-1 encoding
*/
private void createDocument() throws FileNotFoundException,
UnsupportedEncodingException
{
File dir = output.getParentFile();
if (dir != null) {
dir.mkdirs();
}
PrintWriter out = new PrintWriter(new OutputStreamWriter(
new FileOutputStream(output), getOutputEncoding()));
out.println(toXML());
out.flush();
out.close();
}
/**
* @return an XML document representing this change log and it's entries
*/
private String toXML()
{
StringBuffer buffer = new StringBuffer();
buffer.append("\n")
.append("\n");
for (Iterator i = getChangeSets().iterator(); i.hasNext();)
{
buffer.append(((ChangeLogSet) i.next()).toXML());
}
buffer.append("\n");
return buffer.toString();
}
/**
* Gets the collection of change sets.
*
* @return collection of {@link ChangeLogSet} objects.
*/
public Collection getChangeSets()
{
if (sets == null)
{
sets = Collections.EMPTY_LIST;
}
return sets;
}
/**
* Sets the collection of change sets.
* @param sets New value of property sets.
*/
public void setChangeSets(Collection sets)
{
this.sets = sets;
}
/**
* Returns the developers.
* @return List
*/
public List getDevelopers()
{
return developers;
}
/**
* Sets the developers.
* @param developers The developers to set
*/
public void setDevelopers(List developers)
{
this.developers = developers;
}
/**
* Returns the outputEncoding.
* @return String
*/
public String getOutputEncoding()
{
return outputEncoding;
}
/**
* Sets the outputEncoding.
* @param outputEncoding The outputEncoding to set
*/
public void setOutputEncoding(String outputEncoding)
{
this.outputEncoding = outputEncoding;
}
/**
* Returns the commentFormat used to interrogate the RCS.
* @return String
*/
public String getCommentFormat()
{
return commentFormat;
}
/**
* Sets the commentFormat.
* @param commentFormat The commentFormat to set
*/
public void setCommentFormat(String commentFormat)
{
this.commentFormat = commentFormat;
}
} // end of ChangeLog