| /* |
| * 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. |
| * |
| */ |
| package org.apache.tools.ant.taskdefs.cvslib; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.Properties; |
| import java.util.Vector; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.AbstractCvsTask; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Examines the output of cvs log and group related changes together. |
| * |
| * It produces an XML output representing the list of changes. |
| * <pre> |
| * <!-- Root element --> |
| * <!ELEMENT changelog (entry+)> |
| * <!-- CVS Entry --> |
| * <!ELEMENT entry (date,author,file+,msg)> |
| * <!-- Date of cvs entry --> |
| * <!ELEMENT date (#PCDATA)> |
| * <!-- Author of change --> |
| * <!ELEMENT author (#PCDATA)> |
| * <!-- List of files affected --> |
| * <!ELEMENT msg (#PCDATA)> |
| * <!-- File changed --> |
| * <!ELEMENT file (name,revision,prevrevision?)> |
| * <!-- Name of the file --> |
| * <!ELEMENT name (#PCDATA)> |
| * <!-- Revision number --> |
| * <!ELEMENT revision (#PCDATA)> |
| * <!-- Previous revision number --> |
| * <!ELEMENT prevrevision (#PCDATA)> |
| * </pre> |
| * |
| * @since Ant 1.5 |
| * @ant.task name="cvschangelog" category="scm" |
| */ |
| public class ChangeLogTask extends AbstractCvsTask { |
| /** User list */ |
| private File usersFile; |
| |
| /** User list */ |
| private Vector cvsUsers = new Vector(); |
| |
| /** Input dir */ |
| private File inputDir; |
| |
| /** Output file */ |
| private File destFile; |
| |
| /** The earliest date at which to start processing entries. */ |
| private Date startDate; |
| |
| /** The latest date at which to stop processing entries. */ |
| private Date endDate; |
| |
| /** Determines whether log (false) or rlog (true) is used */ |
| private boolean remote = false; |
| |
| /** Start tag when doing tag ranges. */ |
| private String startTag; |
| |
| /** End tag when doing tag ranges. */ |
| private String endTag; |
| |
| /** |
| * Filesets containing list of files against which the cvs log will be |
| * performed. If empty then all files in the working directory will |
| * be checked. |
| */ |
| private final Vector filesets = new Vector(); |
| |
| |
| /** |
| * Set the base dir for cvs. |
| * |
| * @param inputDir The new dir value |
| */ |
| public void setDir(final File inputDir) { |
| this.inputDir = inputDir; |
| } |
| |
| |
| /** |
| * Set the output file for the log. |
| * |
| * @param destFile The new destfile value |
| */ |
| public void setDestfile(final File destFile) { |
| this.destFile = destFile; |
| } |
| |
| |
| /** |
| * Set a lookup list of user names & addresses |
| * |
| * @param usersFile The file containing the users info. |
| */ |
| public void setUsersfile(final File usersFile) { |
| this.usersFile = usersFile; |
| } |
| |
| |
| /** |
| * Add a user to list changelog knows about. |
| * |
| * @param user the user |
| */ |
| public void addUser(final CvsUser user) { |
| cvsUsers.addElement(user); |
| } |
| |
| |
| /** |
| * Set the date at which the changelog should start. |
| * |
| * @param start The date at which the changelog should start. |
| */ |
| public void setStart(final Date start) { |
| this.startDate = start; |
| } |
| |
| |
| /** |
| * Set the date at which the changelog should stop. |
| * |
| * @param endDate The date at which the changelog should stop. |
| */ |
| public void setEnd(final Date endDate) { |
| this.endDate = endDate; |
| } |
| |
| |
| /** |
| * Set the number of days worth of log entries to process. |
| * |
| * @param days the number of days of log to process. |
| */ |
| public void setDaysinpast(final int days) { |
| // CheckStyle:MagicNumber OFF |
| final long time = System.currentTimeMillis() |
| - (long) days * 24 * 60 * 60 * 1000; |
| // CheckStyle:MagicNumber ON |
| |
| setStart(new Date(time)); |
| } |
| |
| /** |
| * Whether to use rlog against a remote repository instead of log |
| * in a working copy's directory. |
| * |
| * @param remote boolean |
| * @since Ant 1.8.0 |
| */ |
| public void setRemote(final boolean remote) { |
| this.remote = remote; |
| } |
| |
| /** |
| * Set the tag at which the changelog should start. |
| * |
| * @param start The date at which the changelog should start. |
| */ |
| public void setStartTag(final String start) { |
| this.startTag = start; |
| } |
| |
| |
| /** |
| * Set the tag at which the changelog should stop. |
| * |
| * @param end The date at which the changelog should stop. |
| */ |
| public void setEndTag(final String end) { |
| this.endTag = end; |
| } |
| |
| /** |
| * Adds a set of files about which cvs logs will be generated. |
| * |
| * @param fileSet a set of files about which cvs logs will be generated. |
| */ |
| public void addFileset(final FileSet fileSet) { |
| filesets.addElement(fileSet); |
| } |
| |
| |
| /** |
| * Execute task |
| * |
| * @exception BuildException if something goes wrong executing the |
| * cvs command |
| */ |
| public void execute() throws BuildException { |
| File savedDir = inputDir; // may be altered in validate |
| |
| try { |
| |
| validate(); |
| final Properties userList = new Properties(); |
| |
| loadUserlist(userList); |
| |
| final int size = cvsUsers.size(); |
| for (int i = 0; i < size; i++) { |
| final CvsUser user = (CvsUser) cvsUsers.get(i); |
| user.validate(); |
| userList.put(user.getUserID(), user.getDisplayname()); |
| } |
| |
| if (!remote) { |
| setCommand("log"); |
| |
| if (getTag() != null) { |
| CvsVersion myCvsVersion = new CvsVersion(); |
| myCvsVersion.setProject(getProject()); |
| myCvsVersion.setTaskName("cvsversion"); |
| myCvsVersion.setCvsRoot(getCvsRoot()); |
| myCvsVersion.setCvsRsh(getCvsRsh()); |
| myCvsVersion.setPassfile(getPassFile()); |
| myCvsVersion.setDest(inputDir); |
| myCvsVersion.execute(); |
| if (myCvsVersion.supportsCvsLogWithSOption()) { |
| addCommandArgument("-S"); |
| } |
| } |
| } else { |
| // supply 'rlog' as argument instead of command |
| setCommand(""); |
| addCommandArgument("rlog"); |
| // Do not print name/header if no revisions |
| // selected. This is quicker: less output to parse. |
| addCommandArgument("-S"); |
| // Do not list tags. This is quicker: less output to |
| // parse. |
| addCommandArgument("-N"); |
| } |
| if (null != startTag || null != endTag) { |
| // man, do I get spoiled by C#'s ?? operator |
| String startValue = startTag == null ? "" : startTag; |
| String endValue = endTag == null ? "" : endTag; |
| addCommandArgument("-r" + startValue + "::" + endValue); |
| } else if (null != startDate) { |
| final SimpleDateFormat outputDate = |
| new SimpleDateFormat("yyyy-MM-dd"); |
| |
| // We want something of the form: -d ">=YYYY-MM-dd" |
| final String dateRange = ">=" + outputDate.format(startDate); |
| |
| // Supply '-d' as a separate argument - Bug# 14397 |
| addCommandArgument("-d"); |
| addCommandArgument(dateRange); |
| } |
| |
| // Check if list of files to check has been specified |
| if (!filesets.isEmpty()) { |
| final Enumeration e = filesets.elements(); |
| |
| while (e.hasMoreElements()) { |
| final FileSet fileSet = (FileSet) e.nextElement(); |
| final DirectoryScanner scanner = |
| fileSet.getDirectoryScanner(getProject()); |
| final String[] files = scanner.getIncludedFiles(); |
| |
| for (int i = 0; i < files.length; i++) { |
| addCommandArgument(files[i]); |
| } |
| } |
| } |
| |
| final ChangeLogParser parser = new ChangeLogParser(remote, |
| getPackage(), |
| getModules()); |
| final RedirectingStreamHandler handler = |
| new RedirectingStreamHandler(parser); |
| |
| log(getCommand(), Project.MSG_VERBOSE); |
| |
| setDest(inputDir); |
| setExecuteStreamHandler(handler); |
| try { |
| super.execute(); |
| } finally { |
| final String errors = handler.getErrors(); |
| |
| if (null != errors) { |
| log(errors, Project.MSG_ERR); |
| } |
| } |
| final CVSEntry[] entrySet = parser.getEntrySetAsArray(); |
| final CVSEntry[] filteredEntrySet = filterEntrySet(entrySet); |
| |
| replaceAuthorIdWithName(userList, filteredEntrySet); |
| |
| writeChangeLog(filteredEntrySet); |
| |
| } finally { |
| inputDir = savedDir; |
| } |
| } |
| |
| /** |
| * Validate the parameters specified for task. |
| * |
| * @throws BuildException if fails validation checks |
| */ |
| private void validate() |
| throws BuildException { |
| if (null == inputDir) { |
| inputDir = getProject().getBaseDir(); |
| } |
| if (null == destFile) { |
| final String message = "Destfile must be set."; |
| |
| throw new BuildException(message); |
| } |
| if (!inputDir.exists()) { |
| final String message = "Cannot find base dir " |
| + inputDir.getAbsolutePath(); |
| |
| throw new BuildException(message); |
| } |
| if (null != usersFile && !usersFile.exists()) { |
| final String message = "Cannot find user lookup list " |
| + usersFile.getAbsolutePath(); |
| |
| throw new BuildException(message); |
| } |
| if ((null != startTag || null != endTag) |
| && (null != startDate || null != endDate)) { |
| final String message = "Specify either a tag or date range," |
| + " not both"; |
| throw new BuildException(message); |
| } |
| } |
| |
| /** |
| * Load the userlist from the userList file (if specified) and add to |
| * list of users. |
| * |
| * @param userList the file of users |
| * @throws BuildException if file can not be loaded for some reason |
| */ |
| private void loadUserlist(final Properties userList) |
| throws BuildException { |
| if (null != usersFile) { |
| try { |
| userList.load(new FileInputStream(usersFile)); |
| } catch (final IOException ioe) { |
| throw new BuildException(ioe.toString(), ioe); |
| } |
| } |
| } |
| |
| /** |
| * Filter the specified entries according to an appropriate rule. |
| * |
| * @param entrySet the entry set to filter |
| * @return the filtered entry set |
| */ |
| private CVSEntry[] filterEntrySet(final CVSEntry[] entrySet) { |
| final Vector results = new Vector(); |
| |
| for (int i = 0; i < entrySet.length; i++) { |
| final CVSEntry cvsEntry = entrySet[i]; |
| final Date date = cvsEntry.getDate(); |
| |
| //bug#30471 |
| //this is caused by Date.after throwing a NullPointerException |
| //for some reason there's no date set in the CVSEntry |
| //Java 1.3.1 API |
| //http://java.sun.com/j2se/1.3/docs/api/java/util/Date.html#after(java.util.Date) |
| //doesn't throw NullPointerException |
| //Java 1.4.2 + 1.5 API |
| //http://java.sun.com/j2se/1.4.2/docs/api/java/util/Date.html#after(java.util.Date) |
| //according to the docs it doesn't throw, according to the bug report it does |
| //http://java.sun.com/j2se/1.5.0/docs/api/java/util/Date.html#after(java.util.Date) |
| //according to the docs it does throw |
| |
| //for now skip entries which are missing a date |
| if (null == date) { |
| continue; |
| } |
| |
| if (null != startDate && startDate.after(date)) { |
| //Skip dates that are too early |
| continue; |
| } |
| if (null != endDate && endDate.before(date)) { |
| //Skip dates that are too late |
| continue; |
| } |
| results.addElement(cvsEntry); |
| } |
| |
| final CVSEntry[] resultArray = new CVSEntry[results.size()]; |
| |
| results.copyInto(resultArray); |
| return resultArray; |
| } |
| |
| /** |
| * replace all known author's id's with their maven specified names |
| */ |
| private void replaceAuthorIdWithName(final Properties userList, |
| final CVSEntry[] entrySet) { |
| for (int i = 0; i < entrySet.length; i++) { |
| |
| final CVSEntry entry = entrySet[ i ]; |
| if (userList.containsKey(entry.getAuthor())) { |
| entry.setAuthor(userList.getProperty(entry.getAuthor())); |
| } |
| } |
| } |
| |
| /** |
| * Print changelog to file specified in task. |
| * |
| * @param entrySet the entry set to write. |
| * @throws BuildException if there is an error writing changelog. |
| */ |
| private void writeChangeLog(final CVSEntry[] entrySet) |
| throws BuildException { |
| FileOutputStream output = null; |
| |
| try { |
| output = new FileOutputStream(destFile); |
| |
| final PrintWriter writer = |
| new PrintWriter(new OutputStreamWriter(output, "UTF-8")); |
| |
| final ChangeLogWriter serializer = new ChangeLogWriter(); |
| |
| serializer.printChangeLog(writer, entrySet); |
| |
| if (writer.checkError()) { |
| throw new IOException("Encountered an error writing changelog"); |
| } |
| } catch (final UnsupportedEncodingException uee) { |
| getProject().log(uee.toString(), Project.MSG_ERR); |
| } catch (final IOException ioe) { |
| throw new BuildException(ioe.toString(), ioe); |
| } finally { |
| FileUtils.close(output); |
| } |
| } |
| } |
| |