/* * 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.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.AbstractCvsTask; import org.apache.tools.ant.util.CollectionUtils; import org.apache.tools.ant.util.DOMElementWriter; import org.apache.tools.ant.util.DOMUtils; import org.apache.tools.ant.util.FileUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Examines the output of cvs rdiff between two tags. * * It produces an XML output representing the list of changes. *
* <!-- Root element --> * <!ELEMENT tagdiff ( entry+ ) > * <!-- Start tag of the report --> * <!ATTLIST tagdiff startTag NMTOKEN #IMPLIED > * <!-- End tag of the report --> * <!ATTLIST tagdiff endTag NMTOKEN #IMPLIED > * <!-- Start date of the report --> * <!ATTLIST tagdiff startDate NMTOKEN #IMPLIED > * <!-- End date of the report --> * <!ATTLIST tagdiff endDate NMTOKEN #IMPLIED > * * <!-- CVS tag entry --> * <!ELEMENT entry ( file ) > * <!-- File added, changed or removed --> * <!ELEMENT file ( name, revision?, prevrevision? ) > * <!-- Name of the file --> * <!ELEMENT name ( #PCDATA ) > * <!-- Revision number --> * <!ELEMENT revision ( #PCDATA ) > * <!-- Previous revision number --> * <!ELEMENT prevrevision ( #PCDATA ) > ** * @since Ant 1.5 * @ant.task name="cvstagdiff" */ public class CvsTagDiff extends AbstractCvsTask { /** * Used to create the temp file for cvs log */ private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); /** stateless helper for writing the XML document */ private static final DOMElementWriter DOM_WRITER = new DOMElementWriter(); /** * Token to identify the word file in the rdiff log */ static final String FILE_STRING = "File "; /** * Length of token to identify the word file in the rdiff log */ static final int FILE_STRING_LENGTH = FILE_STRING.length(); /** * Token to identify the word file in the rdiff log */ static final String TO_STRING = " to "; /** * Token to identify a new file in the rdiff log */ static final String FILE_IS_NEW = " is new;"; /** * Token to identify the revision */ static final String REVISION = "revision "; /** * Token to identify a modified file in the rdiff log */ static final String FILE_HAS_CHANGED = " changed from revision "; /** * Token to identify a removed file in the rdiff log */ static final String FILE_WAS_REMOVED = " is removed"; /** * The cvs package/module to analyse */ private String mypackage; /** * The earliest tag from which diffs are to be included in the report. */ private String mystartTag; /** * The latest tag from which diffs are to be included in the report. */ private String myendTag; /** * The earliest date from which diffs are to be included in the report. */ private String mystartDate; /** * The latest date from which diffs are to be included in the report. */ private String myendDate; /** * The file in which to write the diff report. */ private File mydestfile; /** * Used to skip over removed files */ private boolean ignoreRemoved = false; /** * temporary list of package names. */ private List packageNames = new ArrayList(); /** * temporary list of "File:" + package name + "/" for all packages. */ private String[] packageNamePrefixes = null; /** * temporary list of length values for prefixes. */ private int[] packageNamePrefixLengths = null; /** * The package/module to analyze. * @param p the name of the package to analyse */ @Override public void setPackage(String p) { mypackage = p; } /** * Set the start tag. * * @param s the start tag. */ public void setStartTag(String s) { mystartTag = s; } /** * Set the start date. * * @param s the start date. */ public void setStartDate(String s) { mystartDate = s; } /** * Set the end tag. * * @param s the end tag. */ public void setEndTag(String s) { myendTag = s; } /** * Set the end date. * * @param s the end date. */ public void setEndDate(String s) { myendDate = s; } /** * Set the output file for the diff. * * @param f the output file for the diff. */ public void setDestFile(File f) { mydestfile = f; } /** * Set the ignore removed indicator. * * @param b the ignore removed indicator. * * @since Ant 1.8.0 */ public void setIgnoreRemoved(boolean b) { ignoreRemoved = b; } /** * Execute task. * * @exception BuildException if an error occurs */ @Override public void execute() throws BuildException { // validate the input parameters validate(); // build the rdiff command addCommandArgument("rdiff"); addCommandArgument("-s"); if (mystartTag != null) { addCommandArgument("-r"); addCommandArgument(mystartTag); } else { addCommandArgument("-D"); addCommandArgument(mystartDate); } if (myendTag != null) { addCommandArgument("-r"); addCommandArgument(myendTag); } else { addCommandArgument("-D"); addCommandArgument(myendDate); } // force command not to be null setCommand(""); File tmpFile = null; try { handlePackageNames(); tmpFile = FILE_UTILS.createTempFile("cvstagdiff", ".log", null, true, true); setOutput(tmpFile); // run the cvs command super.execute(); // parse the rdiff CvsTagEntry[] entries = parseRDiff(tmpFile); // write the tag diff writeTagDiff(entries); } finally { packageNamePrefixes = null; packageNamePrefixLengths = null; packageNames.clear(); if (tmpFile != null) { tmpFile.delete(); } } } /** * Parse the tmpFile and return and array of CvsTagEntry to be * written in the output. * * @param tmpFile the File containing the output of the cvs rdiff command * @return the entries in the output * @exception BuildException if an error occurs */ private CvsTagEntry[] parseRDiff(File tmpFile) throws BuildException { // parse the output of the command BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(tmpFile)); // entries are of the form: //CVS 1.11 // File module/filename is new; current revision 1.1 //CVS 1.11.9 // File module/filename is new; cvstag_2003_11_03_2 revision 1.1 // or // File module/filename changed from revision 1.4 to 1.6 // or // File module/filename is removed; not included in // release tag SKINLF_12 //CVS 1.11.9 // File testantoine/antoine.bat is removed; TESTANTOINE_1 revision 1.1.1.1 // // get rid of 'File module/" Vector entries = new Vector(); String line = reader.readLine(); while (null != line) { line = removePackageName(line, packageNamePrefixes, packageNamePrefixLengths); if (line != null) { // use || in a perl like fashion boolean processed = doFileIsNew(entries, line) || doFileHasChanged(entries, line) || doFileWasRemoved(entries, line); } line = reader.readLine(); } CvsTagEntry[] array = new CvsTagEntry[entries.size()]; entries.copyInto(array); return array; } catch (IOException e) { throw new BuildException("Error in parsing", e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log(e.toString(), Project.MSG_ERR); } } } } private boolean doFileIsNew(Vector entries, String line) { int index = line.indexOf(FILE_IS_NEW); if (index == -1) { return false; } // it is a new file // set the revision but not the prevrevision String filename = line.substring(0, index); String rev = null; int indexrev = line.indexOf(REVISION, index); if (indexrev != -1) { rev = line.substring(indexrev + REVISION.length()); } CvsTagEntry entry = new CvsTagEntry(filename, rev); entries.addElement(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; } private boolean doFileHasChanged(Vector entries, String line) { int index = line.indexOf(FILE_HAS_CHANGED); if (index == -1) { return false; } // it is a modified file // set the revision and the prevrevision String filename = line.substring(0, index); int revSeparator = line.indexOf(" to ", index); String prevRevision = line.substring(index + FILE_HAS_CHANGED.length(), revSeparator); String revision = line.substring(revSeparator + TO_STRING.length()); CvsTagEntry entry = new CvsTagEntry(filename, revision, prevRevision); entries.addElement(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; } private boolean doFileWasRemoved(Vector entries, String line) { if (ignoreRemoved) { return false; } int index = line.indexOf(FILE_WAS_REMOVED); if (index == -1) { return false; } // it is a removed file String filename = line.substring(0, index); String rev = null; int indexrev = line.indexOf(REVISION, index); if (indexrev != -1) { rev = line.substring(indexrev + REVISION.length()); } CvsTagEntry entry = new CvsTagEntry(filename, null, rev); entries.addElement(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; } /** * Write the rdiff log. * * @param entries a
CvsTagEntry[]
value
* @exception BuildException if an error occurs
*/
private void writeTagDiff(CvsTagEntry[] entries) throws BuildException {
FileOutputStream output = null;
try {
output = new FileOutputStream(mydestfile);
PrintWriter writer = new PrintWriter(
new OutputStreamWriter(output, "UTF-8"));
writer.println("");
Document doc = DOMUtils.newDocument();
Element root = doc.createElement("tagdiff");
if (mystartTag != null) {
root.setAttribute("startTag", mystartTag);
} else {
root.setAttribute("startDate", mystartDate);
}
if (myendTag != null) {
root.setAttribute("endTag", myendTag);
} else {
root.setAttribute("endDate", myendDate);
}
root.setAttribute("cvsroot", getCvsRoot());
root.setAttribute("package",
CollectionUtils.flattenToString(packageNames));
DOM_WRITER.openElement(root, writer, 0, "\t");
writer.println();
for (int i = 0, c = entries.length; i < c; i++) {
writeTagEntry(doc, writer, entries[i]);
}
DOM_WRITER.closeElement(root, writer, 0, "\t", true);
writer.flush();
if (writer.checkError()) {
throw new IOException("Encountered an error writing tagdiff");
}
writer.close();
} catch (UnsupportedEncodingException uee) {
log(uee.toString(), Project.MSG_ERR);
} catch (IOException ioe) {
throw new BuildException(ioe.toString(), ioe);
} finally {
if (null != output) {
try {
output.close();
} catch (IOException ioe) {
log(ioe.toString(), Project.MSG_ERR);
}
}
}
}
/**
* Write a single entry to the given writer.
*
* @param doc Document used to create elements.
* @param writer a PrintWriter
value
* @param entry a CvsTagEntry
value
*/
private void writeTagEntry(Document doc, PrintWriter writer,
CvsTagEntry entry)
throws IOException {
Element ent = doc.createElement("entry");
Element f = DOMUtils.createChildElement(ent, "file");
DOMUtils.appendCDATAElement(f, "name", entry.getFile());
if (entry.getRevision() != null) {
DOMUtils.appendTextElement(f, "revision", entry.getRevision());
}
if (entry.getPreviousRevision() != null) {
DOMUtils.appendTextElement(f, "prevrevision",
entry.getPreviousRevision());
}
DOM_WRITER.write(ent, writer, 1, "\t");
}
/**
* Validate the parameters specified for task.
*
* @exception BuildException if a parameter is not correctly set
*/
private void validate() throws BuildException {
if (null == mypackage && getModules().size() == 0) {
throw new BuildException("Package/module must be set.");
}
if (null == mydestfile) {
throw new BuildException("Destfile must be set.");
}
if (null == mystartTag && null == mystartDate) {
throw new BuildException("Start tag or start date must be set.");
}
if (null != mystartTag && null != mystartDate) {
throw new BuildException("Only one of start tag and start date "
+ "must be set.");
}
if (null == myendTag && null == myendDate) {
throw new BuildException("End tag or end date must be set.");
}
if (null != myendTag && null != myendDate) {
throw new BuildException("Only one of end tag and end date must "
+ "be set.");
}
}
/**
* collects package names from the package attribute and nested
* module elements.
*/
private void handlePackageNames() {
if (mypackage != null) {
// support multiple packages
StringTokenizer myTokenizer = new StringTokenizer(mypackage);
while (myTokenizer.hasMoreTokens()) {
String pack = myTokenizer.nextToken();
packageNames.add(pack);
addCommandArgument(pack);
}
}
for (Iterator iter = getModules().iterator(); iter.hasNext();) {
AbstractCvsTask.Module m = (AbstractCvsTask.Module) iter.next();
packageNames.add(m.getName());
// will be added to command line in super.execute()
}
packageNamePrefixes = new String[packageNames.size()];
packageNamePrefixLengths = new int[packageNames.size()];
for (int i = 0; i < packageNamePrefixes.length; i++) {
packageNamePrefixes[i] = FILE_STRING + packageNames.get(i) + "/";
packageNamePrefixLengths[i] = packageNamePrefixes[i].length();
}
}
/**
* removes a "File: module/" prefix if present.
*
* @return null if the line was shorter than expected.
*/
private static String removePackageName(String line,
String[] packagePrefixes,
int[] prefixLengths) {
if (line.length() < FILE_STRING_LENGTH) {
return null;
}
boolean matched = false;
for (int i = 0; i < packagePrefixes.length; i++) {
if (line.startsWith(packagePrefixes[i])) {
matched = true;
line = line.substring(prefixLengths[i]);
break;
}
}
if (!matched) {
line = line.substring(FILE_STRING_LENGTH);
}
return line;
}
}