vmassol 01/04/28 05:04:27 Added: cactus/src/ant/org/apache/commons/cactus/ant ChangeLogNewsTask.java Log: new Ant task to extract cvs log information and generate an XML file with the result Revision Changes Path 1.1 jakarta-commons/cactus/src/ant/org/apache/commons/cactus/ant/ChangeLogNewsTask.java Index: ChangeLogNewsTask.java =================================================================== /* * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.commons.cactus.ant; import java.io.*; import java.util.*; import java.text.*; import org.apache.tools.ant.*; import org.apache.tools.ant.taskdefs.*; import org.apache.tools.ant.types.*; /** * A CVS log task that extract information from the execution of the * '<code>cvs log</code>' command and put them in a generated output XML file. * * Note: I have rewritten this task based on the ChangeLog cvslog task from the * Jakarta Alexandria project (initially written by Jeff Martin) * * @version @version@ * @author Jeff Martin <a href="mailto:[EMAIL PROTECTED]">Jeff Martin</a> * @author Vincent Massol <a href="mailto:[EMAIL PROTECTED]">[EMAIL PROTECTED]</a> */ public class ChangeLogNewsTask extends Task implements ExecuteStreamHandler { /** * Name of properties file containing the user list. This list is used to * match a user id retrieved from the '<code>cvs log</code>' command with a * display name. The format of the file is : * '<code>[user id] = [display name]</code>'. */ private File m_UserConfigFile; /** * In memory data structure to store the user list of matching pairs of * (user id, user display name). */ private Properties m_UserList = new Properties(); /** * CVS working directory where the '<code>cvs log</code>' operation is * performed. */ private File m_CVSWorkingDirectory; /** * XML Output file containing the results. */ private File m_OutputFile; /** * Date before which the cvs logs are ignored */ private Date m_ThresholdDate; /** * Input stream read in from CVS log command */ private BufferedReader m_Input; /** * Output file stream where results will be written to */ private PrintWriter m_Output; /** * Filesets containting list of files against which the cvs log will be * performed. If empty then all files will in the working directory will * be checked. */ private Vector m_Filesets = new Vector(); // state machine states private final static int GET_ENTRY = 0; private final static int GET_FILE = 1; private final static int GET_DATE = 2; private final static int GET_COMMENT = 3; private final static int GET_REVISION = 4; private final static int GET_PREVIOUS_REV = 5; /** * Input format for dates read in from cvs log */ private static final SimpleDateFormat INPUT_DATE = new SimpleDateFormat("yyyy/MM/dd"); /** * Output format for dates written to the XML file */ private static final SimpleDateFormat OUTPUT_DATE = new SimpleDateFormat("yyyy-MM-dd"); /** * Output format for times written to the XML file */ private static final SimpleDateFormat OUTPUT_TIME = new SimpleDateFormat("hh:mm"); /** * Set the properties file name containing the matching list of (user id, * user display name). This method is automatically called by the Ant * runtime engine when the <code>users</code> attribute is encountered * in the Ant XML build file. This attribute is optional. * * @param theUserConfigFileName the properties file name relative to the * Ant project base directory (basedir attribute in build file). */ public void setUsers(File theUserConfigFileName) { m_UserConfigFile = theUserConfigFileName; } /** * Set the CVS working directory where the cvs log operation will be * performed. This method is automatically called by the Ant * runtime engine when the <code>work</code> attribute is encountered * in the Ant XML build file. This attribute is mandatory. * * @param theWorkDir the CVS working directory relative to the Ant * project base directory (basedir attribute in build file). */ public void setWork(File theWorkDir) { m_CVSWorkingDirectory = theWorkDir; } /** * Set the output file for the log. This method is automatically called by * the Ant runtime engine when the <code>output</code> attribute is * encountered in the Ant XML build file. This attribute is mandatory. * * @param theOutputFile the XML output file relative to the Ant project * base directory (i.e. basedir attrifbute in build * file). */ public void setOutput(File theOutputFile) { m_OutputFile = theOutputFile; } /** * Set the threshold cvs log date. This method is automatically called by * the Ant runtime engine when the <code>date</code> attribute is * encountered in the Ant XML build file. This attribute is optional. The * format is "yyyy/MM/dd hh:mm". * * @param theThresholdDate the threshold date before which cvs log are * ignored. */ public void setDate(String theThresholdDate) { try { m_ThresholdDate = INPUT_DATE.parse(theThresholdDate); } catch(ParseException e) { throw new BuildException("Bad date format [" + theThresholdDate + "]."); } } /** * Set the threshold cvs log date by calculating it : "today - elapsed". * This method is automatically called by * the Ant runtime engine when the <code>elapsed</code> attribute is * encountered in the Ant XML build file. This attribute is optional. The * elasped time must be expressed in days. * * @param theElapsedDays the elapsed time from now in days. All cvs logs * that are this old will be shown. */ public void setElapsed(Long theElapsedDays) { long now = System.currentTimeMillis(); m_ThresholdDate = new Date( now - theElapsedDays.longValue() * 24 * 60 * 60 * 1000); } /** * Adds a set of files (nested fileset attribute). * This method is automatically called by * the Ant runtime engine when the <code>fileset</code> nested tag is * encountered in the Ant XML build file. This attribute is optional. * * @param theSet the fileset that contains the list of files for which * cvs logs will be checked. */ public void addFileset(FileSet theSet) { m_Filesets.addElement(theSet); } /** * Read the user list from the properties file and store the matches in * memory. If no properties file has been set, do not do anything. */ private void readUserList() { if (m_UserConfigFile != null) { if (!m_UserConfigFile.exists()) { throw new BuildException("User list configuration file [" + m_UserConfigFile.getAbsolutePath() + "] was not found. Please check location."); } try { m_UserList.load(new FileInputStream(m_UserConfigFile)); } catch(IOException e) { throw new BuildException(e); } } } /** * Execute task */ public void execute() throws BuildException { if (m_CVSWorkingDirectory == null) { throw new BuildException("The [workDir] attribute must be set"); } if (!m_CVSWorkingDirectory.exists()) { throw new BuildException("Cannot find CVS working directory [" + m_CVSWorkingDirectory.getAbsolutePath() + "]"); } if (m_OutputFile == null) { throw new BuildException("The [output] attribute must be set"); } readUserList(); Commandline toExecute = new Commandline(); toExecute.setExecutable("cvs"); toExecute.createArgument().setValue("log"); // Check if a threshold date has been specified if (m_ThresholdDate != null) { toExecute.createArgument().setValue("-d\">=" + OUTPUT_DATE.format(m_ThresholdDate) + "\""); } // Check if list of files to check has been specified if (!m_Filesets.isEmpty()) { Enumeration e = m_Filesets.elements(); while(e.hasMoreElements()) { FileSet fs = (FileSet)e.nextElement(); DirectoryScanner ds = fs.getDirectoryScanner(project); String[] srcFiles = ds.getIncludedFiles(); for (int i = 0; i < srcFiles.length; i++) { toExecute.createArgument().setValue(srcFiles[i]); } } } Execute exe = new Execute(this); exe.setCommandline(toExecute.getCommandline()); exe.setAntRun(project); exe.setWorkingDirectory(m_CVSWorkingDirectory); try { exe.execute(); } catch(IOException e) { throw new BuildException(e); } } /** * Set the input stream for the CVS process. As CVS requires no input, this * is not used. * * @param theOs the output stream to write to the standard input stream of * the subprocess (i.e. the CVS process) */ public void setProcessInputStream(OutputStream theOs) throws IOException { } /** * Set the error stream for reading from CVS log. Not used in the current * version (should be handled in future versions). * * @param theIs the input stream to read from the error stream from the * subprocess (i.e. the CVS process) */ public void setProcessErrorStream(InputStream theIs) throws IOException { } /** * Set the input stream used to read from CVS log * * @param theIs the input stream to read from the output stream of the * subprocess (i.e. the CVS process) */ public void setProcessOutputStream(InputStream theIs) throws IOException { m_Input = new BufferedReader(new InputStreamReader(theIs)); } /** * Stop handling of the streams (i.e. the cvs process). */ public void stop() { } /** * Start reading from the cvs log stream. */ public void start() throws IOException { m_Output = new PrintWriter(new OutputStreamWriter( new FileOutputStream(m_OutputFile),"UTF-8")); String file = null; String line = null; String date = null; String author = null; String comment = null; String revision = null; String previousRev = null; // Current state in the state machine used to parse the CVS log stream int status = GET_FILE; // RCS entries Hashtable entries = new Hashtable(); while ((line = m_Input.readLine()) != null) { switch(status){ case GET_FILE: if (line.startsWith("Working file:")) { file = line.substring(14, line.length()); status = GET_REVISION; } break; case GET_REVISION: if (line.startsWith("revision")) { revision = line.substring(9); status = GET_DATE; } // If we encounter a "=====" line, it means there was no // description and thus the entry must be forgotten else if (line.startsWith("======")) { status = GET_FILE; } break; case GET_DATE: if (line.startsWith("date:")) { date = line.substring(6, 16); line = line.substring(line.indexOf(";") + 1); author = line.substring(10, line.indexOf(";")); if ((m_UserList != null) && m_UserList.containsKey(author)) { author = "<![CDATA[" + m_UserList.getProperty(author) + "]]>"; } status = GET_COMMENT; } break; case GET_COMMENT: comment = ""; while (line != null && !line.startsWith("======") && !line.startsWith("------")) { comment += line + "\n"; line = m_Input.readLine(); } comment = "<![CDATA[" + comment.substring(0,comment.length() - 1) + "]]>"; status = GET_PREVIOUS_REV; break; case GET_PREVIOUS_REV: if (line.startsWith("revision")) { previousRev = line.substring(9); status = GET_FILE; Entry entry; if (!entries.containsKey(date + author + comment)) { entry = new Entry(date, author, comment); entries.put(date + author + comment, entry); } else { entry = (Entry)entries.get(date + author + comment); } entry.addFile(file, revision, previousRev); } if (line.startsWith("======")) { status = GET_FILE; Entry entry; if (!entries.containsKey(date + author + comment)) { entry = new Entry(date, author, comment); entries.put(date + author + comment, entry); }else { entry = (Entry)entries.get(date + author + comment); } entry.addFile(file, revision); } } } m_Output.println("<changelog>"); Enumeration en = entries.elements(); while (en.hasMoreElements()) { ((Entry)en.nextElement()).print(); } m_Output.println("</changelog>"); m_Output.flush(); m_Output.close(); } /** * CVS entry class */ private class Entry { /** * The entry date */ private Date m_Date; /** * The entry author id */ private final String m_Author; /** * The comment entry */ private final String m_Comment; /** * The list of files that were CVS committed at the same time */ private final Vector m_Files = new Vector(); /** * Create an entry. * * @param theDate the entry's date * @param theAuthor the entry's author * @param theComment the entry's comment */ public Entry(String theDate, String theAuthor, String theComment) { try { m_Date = INPUT_DATE.parse(theDate); } catch(ParseException e) { log("Bad date format [" + theDate + "]."); } m_Author = theAuthor; m_Comment = theComment; } public void addFile(String theFile, String theRevision) { m_Files.addElement(new RCSFile(theFile, theRevision)); } public void addFile(String theFile, String theRevision, String thePreviousRev) { m_Files.addElement(new RCSFile(theFile, theRevision, thePreviousRev)); } public String toString() { return m_Author + "\n" + m_Date + "\n" + m_Files + "\n" + m_Comment; } public void print() { m_Output.println("\t<entry>"); m_Output.println("\t\t<date>" + OUTPUT_DATE.format(m_Date) + "</date>"); m_Output.println("\t\t<time>" +OUTPUT_TIME.format(m_Date) + "</time>"); m_Output.println("\t\t<author>" + m_Author + "</author>"); Enumeration e = m_Files.elements(); while (e.hasMoreElements()) { RCSFile file = (RCSFile)e.nextElement(); m_Output.println("\t\t<file>"); m_Output.println("\t\t\t<name>" + file.getName() + "</name>"); m_Output.println("\t\t\t<revision>" + file.getRevision() + "</revision>"); if (file.getPreviousRev() != null) { m_Output.println("\t\t\t<prevrevision>" + file.getPreviousRev() + "</prevrevision>"); } m_Output.println("\t\t</file>"); } m_Output.println("\t\t<msg>" + m_Comment + "</msg>"); m_Output.println("\t</entry>"); } private class RCSFile { private String m_Name; private String m_Rev; private String m_PreviousRev; private RCSFile(String theName, String theRev) { this(theName, theRev, null); } private RCSFile(String theName, String theRev, String thePreviousRev) { m_Name = theName; m_Rev = theRev; if (!m_Rev.equals(m_PreviousRev)) { m_PreviousRev = thePreviousRev; } } public String getName() { return m_Name; } public String getRevision() { return m_Rev; } public String getPreviousRev() { return m_PreviousRev; } } } }