/*
 *  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
 *
 *      https://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.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

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.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.taskname="cvstagdiff"
/** * Examines the output of cvs rdiff between two tags. * * It produces an XML output representing the list of changes. * <pre> * &lt;!-- Root element --&gt; * &lt;!ELEMENT tagdiff (entry+) &gt; * &lt;!-- Start tag of the report --&gt; * &lt;!ATTLIST tagdiff startTag NMTOKEN #IMPLIED &gt; * &lt;!-- End tag of the report --&gt; * &lt;!ATTLIST tagdiff endTag NMTOKEN #IMPLIED &gt; * &lt;!-- Start date of the report --&gt; * &lt;!ATTLIST tagdiff startDate NMTOKEN #IMPLIED &gt; * &lt;!-- End date of the report --&gt; * &lt;!ATTLIST tagdiff endDate NMTOKEN #IMPLIED &gt; * * &lt;!-- CVS tag entry --&gt; * &lt;!ELEMENT entry (file) &gt; * &lt;!-- File added, changed or removed --&gt; * &lt;!ELEMENT file (name, revision?, prevrevision?) &gt; * &lt;!-- Name of the file --&gt; * &lt;!ELEMENT name (#PCDATA) &gt; * &lt;!-- Revision number --&gt; * &lt;!ELEMENT revision (#PCDATA) &gt; * &lt;!-- Previous revision number --&gt; * &lt;!ELEMENT prevrevision (#PCDATA) &gt; * </pre> * * @since Ant 1.5 * @ant.task name="cvstagdiff" */
public class CvsTagDiff extends AbstractCvsTask {
Used to create the temp file for cvs log
/** * Used to create the temp file for cvs log */
private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
stateless helper for writing the XML document
/** 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
/** * 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
/** * 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
/** * 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
/** * Token to identify a new file in the rdiff log */
static final String FILE_IS_NEW = " is new;";
Token to identify the revision
/** * Token to identify the revision */
static final String REVISION = "revision ";
Token to identify a modified file in the rdiff log
/** * 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
/** * Token to identify a removed file in the rdiff log */
static final String FILE_WAS_REMOVED = " is removed";
The cvs package/module to analyse
/** * The cvs package/module to analyse */
private String mypackage;
The earliest tag from which diffs are to be included in the report.
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * The file in which to write the diff report. */
private File mydestfile;
Used to skip over removed files
/** * Used to skip over removed files */
private boolean ignoreRemoved = false;
temporary list of package names.
/** * temporary list of package names. */
private List<String> packageNames = new ArrayList<>();
temporary list of "File:" + package name + "/" for all packages.
/** * temporary list of "File:" + package name + "/" for all packages. */
private String[] packageNamePrefixes = null;
temporary list of length values for prefixes.
/** * temporary list of length values for prefixes. */
private int[] packageNamePrefixLengths = null;
The package/module to analyze.
Params:
  • p – the name of the package to analyse
/** * 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.
Params:
  • s – the start tag.
/** * Set the start tag. * * @param s the start tag. */
public void setStartTag(String s) { mystartTag = s; }
Set the start date.
Params:
  • s – the start date.
/** * Set the start date. * * @param s the start date. */
public void setStartDate(String s) { mystartDate = s; }
Set the end tag.
Params:
  • s – the end tag.
/** * Set the end tag. * * @param s the end tag. */
public void setEndTag(String s) { myendTag = s; }
Set the end date.
Params:
  • s – the end date.
/** * Set the end date. * * @param s the end date. */
public void setEndDate(String s) { myendDate = s; }
Set the output file for the diff.
Params:
  • f – the output file for the diff.
/** * 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.
Params:
  • b – the ignore removed indicator.
Since:Ant 1.8.0
/** * 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.
Throws:
  • BuildException – if an error occurs
/** * 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.
Params:
  • tmpFile – the File containing the output of the cvs rdiff command
Throws:
Returns:the entries in the output
/** * 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 try (BufferedReader 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/" List<CvsTagEntry> entries = new ArrayList<>(); String line = reader.readLine(); while (null != line) { line = removePackageName(line, packageNamePrefixes, packageNamePrefixLengths); if (line != null) { // use || in a perl like fashion @SuppressWarnings("unused") boolean processed = doFileIsNew(entries, line) || doFileHasChanged(entries, line) || doFileWasRemoved(entries, line); } line = reader.readLine(); } return entries.toArray(new CvsTagEntry[entries.size()]); } catch (IOException e) { throw new BuildException("Error in parsing", e); } } private boolean doFileIsNew(List<CvsTagEntry> 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.add(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; } private boolean doFileHasChanged(List<CvsTagEntry> 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.add(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; } private boolean doFileWasRemoved(List<CvsTagEntry> 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.add(entry); log(entry.toString(), Project.MSG_VERBOSE); return true; }
Write the rdiff log.
Params:
  • entries – a CvsTagEntry[] value
Throws:
/** * Write the rdiff log. * * @param entries a <code>CvsTagEntry[]</code> value * @exception BuildException if an error occurs */
private void writeTagDiff(CvsTagEntry[] entries) throws BuildException { try (PrintWriter writer = new PrintWriter(new OutputStreamWriter( Files.newOutputStream(mydestfile.toPath()), StandardCharsets.UTF_8))) { writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 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", String.join(",", packageNames)); DOM_WRITER.openElement(root, writer, 0, "\t"); writer.println(); for (CvsTagEntry entry : entries) { writeTagEntry(doc, writer, entry); } DOM_WRITER.closeElement(root, writer, 0, "\t", true); writer.flush(); if (writer.checkError()) { throw new IOException("Encountered an error writing tagdiff"); } } catch (UnsupportedEncodingException uee) { log(uee.toString(), Project.MSG_ERR); } catch (IOException ioe) { throw new BuildException(ioe.toString(), ioe); } }
Write a single entry to the given writer.
Params:
  • doc – Document used to create elements.
  • writer – a PrintWriter value
  • entry – a CvsTagEntry value
/** * Write a single entry to the given writer. * * @param doc Document used to create elements. * @param writer a <code>PrintWriter</code> value * @param entry a <code>CvsTagEntry</code> 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.
Throws:
  • BuildException – if a parameter is not correctly set
/** * Validate the parameters specified for task. * * @exception BuildException if a parameter is not correctly set */
private void validate() throws BuildException { if (null == mypackage && getModules().isEmpty()) { 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.
/** * 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 (Module m : getModules()) { 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.
Returns:null if the line was shorter than expected.
/** * 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; } for (int i = 0; i < packagePrefixes.length; i++) { if (line.startsWith(packagePrefixes[i])) { return line.substring(prefixLengths[i]); } } return line.substring(FILE_STRING_LENGTH); } }