1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.cvslib;
  18. import java.io.BufferedReader;
  19. import java.io.File;
  20. import java.io.FileOutputStream;
  21. import java.io.FileReader;
  22. import java.io.IOException;
  23. import java.io.OutputStreamWriter;
  24. import java.io.PrintWriter;
  25. import java.io.UnsupportedEncodingException;
  26. import java.util.Vector;
  27. import java.util.StringTokenizer;
  28. import org.apache.tools.ant.BuildException;
  29. import org.apache.tools.ant.Project;
  30. import org.apache.tools.ant.taskdefs.AbstractCvsTask;
  31. import org.apache.tools.ant.util.FileUtils;
  32. /**
  33. * Examines the output of cvs rdiff between two tags.
  34. *
  35. * It produces an XML output representing the list of changes.
  36. * <PRE>
  37. * <!-- Root element -->
  38. * <!ELEMENT tagdiff ( entry+ ) >
  39. * <!-- Start tag of the report -->
  40. * <!ATTLIST tagdiff startTag NMTOKEN #IMPLIED >
  41. * <!-- End tag of the report -->
  42. * <!ATTLIST tagdiff endTag NMTOKEN #IMPLIED >
  43. * <!-- Start date of the report -->
  44. * <!ATTLIST tagdiff startDate NMTOKEN #IMPLIED >
  45. * <!-- End date of the report -->
  46. * <!ATTLIST tagdiff endDate NMTOKEN #IMPLIED >
  47. *
  48. * <!-- CVS tag entry -->
  49. * <!ELEMENT entry ( file ) >
  50. * <!-- File added, changed or removed -->
  51. * <!ELEMENT file ( name, revision?, prevrevision? ) >
  52. * <!-- Name of the file -->
  53. * <!ELEMENT name ( #PCDATA ) >
  54. * <!-- Revision number -->
  55. * <!ELEMENT revision ( #PCDATA ) >
  56. * <!-- Previous revision number -->
  57. * <!ELEMENT prevrevision ( #PCDATA ) >
  58. * </PRE>
  59. *
  60. * @version $Revision: 1.16.2.7 $ $Date: 2004/03/09 17:01:40 $
  61. * @since Ant 1.5
  62. * @ant.task name="cvstagdiff"
  63. */
  64. public class CvsTagDiff extends AbstractCvsTask {
  65. /**
  66. * Token to identify the word file in the rdiff log
  67. */
  68. static final String FILE_STRING = "File ";
  69. /**
  70. * Token to identify the word file in the rdiff log
  71. */
  72. static final String TO_STRING = " to ";
  73. /**
  74. * Token to identify a new file in the rdiff log
  75. */
  76. static final String FILE_IS_NEW = " is new;";
  77. /**
  78. * Token to identify the revision
  79. */
  80. static final String REVISION = "revision ";
  81. /**
  82. * Token to identify a modified file in the rdiff log
  83. */
  84. static final String FILE_HAS_CHANGED = " changed from revision ";
  85. /**
  86. * Token to identify a removed file in the rdiff log
  87. */
  88. static final String FILE_WAS_REMOVED = " is removed";
  89. /**
  90. * The cvs package/module to analyse
  91. */
  92. private String mypackage;
  93. /**
  94. * The earliest tag from which diffs are to be included in the report.
  95. */
  96. private String mystartTag;
  97. /**
  98. * The latest tag from which diffs are to be included in the report.
  99. */
  100. private String myendTag;
  101. /**
  102. * The earliest date from which diffs are to be included in the report.
  103. */
  104. private String mystartDate;
  105. /**
  106. * The latest date from which diffs are to be included in the report.
  107. */
  108. private String myendDate;
  109. /**
  110. * The file in which to write the diff report.
  111. */
  112. private File mydestfile;
  113. /**
  114. * Used to create the temp file for cvs log
  115. */
  116. private FileUtils myfileUtils = FileUtils.newFileUtils();
  117. /**
  118. * The package/module to analyze.
  119. * @param p the name of the package to analyse
  120. */
  121. public void setPackage(String p) {
  122. mypackage = p;
  123. }
  124. /**
  125. * Set the start tag.
  126. *
  127. * @param s the start tag.
  128. */
  129. public void setStartTag(String s) {
  130. mystartTag = s;
  131. }
  132. /**
  133. * Set the start date.
  134. *
  135. * @param s the start date.
  136. */
  137. public void setStartDate(String s) {
  138. mystartDate = s;
  139. }
  140. /**
  141. * Set the end tag.
  142. *
  143. * @param s the end tag.
  144. */
  145. public void setEndTag(String s) {
  146. myendTag = s;
  147. }
  148. /**
  149. * Set the end date.
  150. *
  151. * @param s the end date.
  152. */
  153. public void setEndDate(String s) {
  154. myendDate = s;
  155. }
  156. /**
  157. * Set the output file for the diff.
  158. *
  159. * @param f the output file for the diff.
  160. */
  161. public void setDestFile(File f) {
  162. mydestfile = f;
  163. }
  164. /**
  165. * Execute task.
  166. *
  167. * @exception BuildException if an error occurs
  168. */
  169. public void execute() throws BuildException {
  170. // validate the input parameters
  171. validate();
  172. // build the rdiff command
  173. addCommandArgument("rdiff");
  174. addCommandArgument("-s");
  175. if (mystartTag != null) {
  176. addCommandArgument("-r");
  177. addCommandArgument(mystartTag);
  178. } else {
  179. addCommandArgument("-D");
  180. addCommandArgument(mystartDate);
  181. }
  182. if (myendTag != null) {
  183. addCommandArgument("-r");
  184. addCommandArgument(myendTag);
  185. } else {
  186. addCommandArgument("-D");
  187. addCommandArgument(myendDate);
  188. }
  189. // support multiple packages
  190. StringTokenizer myTokenizer = new StringTokenizer(mypackage);
  191. while (myTokenizer.hasMoreTokens()) {
  192. addCommandArgument(myTokenizer.nextToken());
  193. }
  194. // force command not to be null
  195. setCommand("");
  196. File tmpFile = null;
  197. try {
  198. tmpFile = myfileUtils.createTempFile("cvstagdiff", ".log", null);
  199. tmpFile.deleteOnExit();
  200. setOutput(tmpFile);
  201. // run the cvs command
  202. super.execute();
  203. // parse the rdiff
  204. CvsTagEntry[] entries = parseRDiff(tmpFile);
  205. // write the tag diff
  206. writeTagDiff(entries);
  207. } finally {
  208. if (tmpFile != null) {
  209. tmpFile.delete();
  210. }
  211. }
  212. }
  213. /**
  214. * Parse the tmpFile and return and array of CvsTagEntry to be
  215. * written in the output.
  216. *
  217. * @param tmpFile the File containing the output of the cvs rdiff command
  218. * @return the entries in the output
  219. * @exception BuildException if an error occurs
  220. */
  221. private CvsTagEntry[] parseRDiff(File tmpFile) throws BuildException {
  222. // parse the output of the command
  223. BufferedReader reader = null;
  224. try {
  225. reader = new BufferedReader(new FileReader(tmpFile));
  226. // entries are of the form:
  227. //CVS 1.11
  228. // File module/filename is new; current revision 1.1
  229. //CVS 1.11.9
  230. // File module/filename is new; cvstag_2003_11_03_2 revision 1.1
  231. // or
  232. // File module/filename changed from revision 1.4 to 1.6
  233. // or
  234. // File module/filename is removed; not included in
  235. // release tag SKINLF_12
  236. //CVS 1.11.9
  237. // File testantoine/antoine.bat is removed; TESTANTOINE_1 revision 1.1.1.1
  238. //
  239. // get rid of 'File module/"
  240. String toBeRemoved = FILE_STRING + mypackage + "/";
  241. int headerLength = toBeRemoved.length();
  242. Vector entries = new Vector();
  243. String line = reader.readLine();
  244. int index;
  245. CvsTagEntry entry = null;
  246. while (null != line) {
  247. if (line.length() > headerLength) {
  248. if (line.startsWith(toBeRemoved)) {
  249. line = line.substring(headerLength);
  250. } else {
  251. line = line.substring(FILE_STRING.length());
  252. }
  253. if ((index = line.indexOf(FILE_IS_NEW)) != -1) {
  254. //CVS 1.11
  255. //File apps/websphere/lib/something.jar is new; current revision 1.2
  256. //CVS 1.11.9
  257. //File apps/websphere/lib/something.jar is new; cvstag_2003_11_03_2 revision 1.2
  258. // it is a new file
  259. // set the revision but not the prevrevision
  260. String filename = line.substring(0, index);
  261. String rev = null;
  262. int indexrev = -1;
  263. if ((indexrev = line.indexOf(REVISION, index)) != -1) {
  264. rev = line.substring(indexrev + REVISION.length());
  265. }
  266. entry = new CvsTagEntry(filename, rev);
  267. entries.addElement(entry);
  268. log(entry.toString(), Project.MSG_VERBOSE);
  269. } else if ((index = line.indexOf(FILE_HAS_CHANGED)) != -1) {
  270. // it is a modified file
  271. // set the revision and the prevrevision
  272. String filename = line.substring(0, index);
  273. int revSeparator = line.indexOf(" to ", index);
  274. String prevRevision =
  275. line.substring(index + FILE_HAS_CHANGED.length(),
  276. revSeparator);
  277. String revision = line.substring(revSeparator + TO_STRING.length());
  278. entry = new CvsTagEntry(filename,
  279. revision,
  280. prevRevision);
  281. entries.addElement(entry);
  282. log(entry.toString(), Project.MSG_VERBOSE);
  283. } else if ((index = line.indexOf(FILE_WAS_REMOVED)) != -1) {
  284. // it is a removed file
  285. String filename = line.substring(0, index);
  286. String rev = null;
  287. int indexrev = -1;
  288. if ((indexrev = line.indexOf(REVISION, index)) != -1) {
  289. rev = line.substring(indexrev + REVISION.length());
  290. }
  291. entry = new CvsTagEntry(filename, null, rev);
  292. entries.addElement(entry);
  293. log(entry.toString(), Project.MSG_VERBOSE);
  294. }
  295. }
  296. line = reader.readLine();
  297. }
  298. CvsTagEntry[] array = new CvsTagEntry[entries.size()];
  299. entries.copyInto(array);
  300. return array;
  301. } catch (IOException e) {
  302. throw new BuildException("Error in parsing", e);
  303. } finally {
  304. if (reader != null) {
  305. try {
  306. reader.close();
  307. } catch (IOException e) {
  308. log(e.toString(), Project.MSG_ERR);
  309. }
  310. }
  311. }
  312. }
  313. /**
  314. * Write the rdiff log.
  315. *
  316. * @param entries a <code>CvsTagEntry[]</code> value
  317. * @exception BuildException if an error occurs
  318. */
  319. private void writeTagDiff(CvsTagEntry[] entries) throws BuildException {
  320. FileOutputStream output = null;
  321. try {
  322. output = new FileOutputStream(mydestfile);
  323. PrintWriter writer = new PrintWriter(
  324. new OutputStreamWriter(output, "UTF-8"));
  325. writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  326. writer.print("<tagdiff ");
  327. if (mystartTag != null) {
  328. writer.print("startTag=\"" + mystartTag + "\" ");
  329. } else {
  330. writer.print("startDate=\"" + mystartDate + "\" ");
  331. }
  332. if (myendTag != null) {
  333. writer.print("endTag=\"" + myendTag + "\" ");
  334. } else {
  335. writer.print("endDate=\"" + myendDate + "\" ");
  336. }
  337. writer.print("cvsroot=\"" + getCvsRoot() + "\" ");
  338. writer.print("package=\"" + mypackage + "\" ");
  339. writer.println(">");
  340. for (int i = 0, c = entries.length; i < c; i++) {
  341. writeTagEntry(writer, entries[i]);
  342. }
  343. writer.println("</tagdiff>");
  344. writer.flush();
  345. writer.close();
  346. } catch (UnsupportedEncodingException uee) {
  347. log(uee.toString(), Project.MSG_ERR);
  348. } catch (IOException ioe) {
  349. throw new BuildException(ioe.toString(), ioe);
  350. } finally {
  351. if (null != output) {
  352. try {
  353. output.close();
  354. } catch (IOException ioe) {
  355. log(ioe.toString(), Project.MSG_ERR);
  356. }
  357. }
  358. }
  359. }
  360. /**
  361. * Write a single entry to the given writer.
  362. *
  363. * @param writer a <code>PrintWriter</code> value
  364. * @param entry a <code>CvsTagEntry</code> value
  365. */
  366. private void writeTagEntry(PrintWriter writer, CvsTagEntry entry) {
  367. writer.println("\t<entry>");
  368. writer.println("\t\t<file>");
  369. writer.println("\t\t\t<name>" + entry.getFile() + "</name>");
  370. if (entry.getRevision() != null) {
  371. writer.println("\t\t\t<revision>" + entry.getRevision()
  372. + "</revision>");
  373. }
  374. if (entry.getPreviousRevision() != null) {
  375. writer.println("\t\t\t<prevrevision>"
  376. + entry.getPreviousRevision() + "</prevrevision>");
  377. }
  378. writer.println("\t\t</file>");
  379. writer.println("\t</entry>");
  380. }
  381. /**
  382. * Validate the parameters specified for task.
  383. *
  384. * @exception BuildException if a parameter is not correctly set
  385. */
  386. private void validate() throws BuildException {
  387. if (null == mypackage) {
  388. throw new BuildException("Package/module must be set.");
  389. }
  390. if (null == mydestfile) {
  391. throw new BuildException("Destfile must be set.");
  392. }
  393. if (null == mystartTag && null == mystartDate) {
  394. throw new BuildException("Start tag or start date must be set.");
  395. }
  396. if (null != mystartTag && null != mystartDate) {
  397. throw new BuildException("Only one of start tag and start date "
  398. + "must be set.");
  399. }
  400. if (null == myendTag && null == myendDate) {
  401. throw new BuildException("End tag or end date must be set.");
  402. }
  403. if (null != myendTag && null != myendDate) {
  404. throw new BuildException("Only one of end tag and end date must "
  405. + "be set.");
  406. }
  407. }
  408. }