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.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.OutputStreamWriter;
  23. import java.io.PrintWriter;
  24. import java.io.UnsupportedEncodingException;
  25. import java.text.SimpleDateFormat;
  26. import java.util.Date;
  27. import java.util.Enumeration;
  28. import java.util.Properties;
  29. import java.util.Vector;
  30. import org.apache.tools.ant.BuildException;
  31. import org.apache.tools.ant.DirectoryScanner;
  32. import org.apache.tools.ant.Project;
  33. import org.apache.tools.ant.taskdefs.Execute;
  34. import org.apache.tools.ant.taskdefs.AbstractCvsTask;
  35. import org.apache.tools.ant.types.Commandline;
  36. import org.apache.tools.ant.types.FileSet;
  37. /**
  38. * Examines the output of cvs log and group related changes together.
  39. *
  40. * It produces an XML output representing the list of changes.
  41. * <PRE>
  42. * <FONT color=#0000ff><!-- Root element --></FONT>
  43. * <FONT color=#6a5acd><!ELEMENT</FONT> changelog <FONT color=#ff00ff>(entry</FONT><FONT color=#ff00ff>+</FONT><FONT color=#ff00ff>)</FONT><FONT color=#6a5acd>></FONT>
  44. * <FONT color=#0000ff><!-- CVS Entry --></FONT>
  45. * <FONT color=#6a5acd><!ELEMENT</FONT> entry <FONT color=#ff00ff>(date,author,file</FONT><FONT color=#ff00ff>+</FONT><FONT color=#ff00ff>,msg)</FONT><FONT color=#6a5acd>></FONT>
  46. * <FONT color=#0000ff><!-- Date of cvs entry --></FONT>
  47. * <FONT color=#6a5acd><!ELEMENT</FONT> date <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  48. * <FONT color=#0000ff><!-- Author of change --></FONT>
  49. * <FONT color=#6a5acd><!ELEMENT</FONT> author <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  50. * <FONT color=#0000ff><!-- List of files affected --></FONT>
  51. * <FONT color=#6a5acd><!ELEMENT</FONT> msg <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  52. * <FONT color=#0000ff><!-- File changed --></FONT>
  53. * <FONT color=#6a5acd><!ELEMENT</FONT> file <FONT color=#ff00ff>(name,revision,prevrevision</FONT><FONT color=#ff00ff>?</FONT><FONT color=#ff00ff>)</FONT><FONT color=#6a5acd>></FONT>
  54. * <FONT color=#0000ff><!-- Name of the file --></FONT>
  55. * <FONT color=#6a5acd><!ELEMENT</FONT> name <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  56. * <FONT color=#0000ff><!-- Revision number --></FONT>
  57. * <FONT color=#6a5acd><!ELEMENT</FONT> revision <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  58. * <FONT color=#0000ff><!-- Previous revision number --></FONT>
  59. * <FONT color=#6a5acd><!ELEMENT</FONT> prevrevision <FONT color=#ff00ff>(#PCDATA)</FONT><FONT color=#6a5acd>></FONT>
  60. * </PRE>
  61. *
  62. * @version $Revision: 1.25.2.5 $ $Date: 2004/03/09 17:01:40 $
  63. * @since Ant 1.5
  64. * @ant.task name="cvschangelog" category="scm"
  65. */
  66. public class ChangeLogTask extends AbstractCvsTask {
  67. /** User list */
  68. private File m_usersFile;
  69. /** User list */
  70. private Vector m_cvsUsers = new Vector();
  71. /** Input dir */
  72. private File m_dir;
  73. /** Output file */
  74. private File m_destfile;
  75. /** The earliest date at which to start processing entries. */
  76. private Date m_start;
  77. /** The latest date at which to stop processing entries. */
  78. private Date m_stop;
  79. /**
  80. * Filesets containing list of files against which the cvs log will be
  81. * performed. If empty then all files will in the working directory will
  82. * be checked.
  83. */
  84. private final Vector m_filesets = new Vector();
  85. /**
  86. * Set the base dir for cvs.
  87. *
  88. * @param dir The new dir value
  89. */
  90. public void setDir(final File dir) {
  91. m_dir = dir;
  92. }
  93. /**
  94. * Set the output file for the log.
  95. *
  96. * @param destfile The new destfile value
  97. */
  98. public void setDestfile(final File destfile) {
  99. m_destfile = destfile;
  100. }
  101. /**
  102. * Set a lookup list of user names & addresses
  103. *
  104. * @param usersFile The file containing the users info.
  105. */
  106. public void setUsersfile(final File usersFile) {
  107. m_usersFile = usersFile;
  108. }
  109. /**
  110. * Add a user to list changelog knows about.
  111. *
  112. * @param user the user
  113. */
  114. public void addUser(final CvsUser user) {
  115. m_cvsUsers.addElement(user);
  116. }
  117. /**
  118. * Set the date at which the changelog should start.
  119. *
  120. * @param start The date at which the changelog should start.
  121. */
  122. public void setStart(final Date start) {
  123. m_start = start;
  124. }
  125. /**
  126. * Set the date at which the changelog should stop.
  127. *
  128. * @param stop The date at which the changelog should stop.
  129. */
  130. public void setEnd(final Date stop) {
  131. m_stop = stop;
  132. }
  133. /**
  134. * Set the number of days worth of log entries to process.
  135. *
  136. * @param days the number of days of log to process.
  137. */
  138. public void setDaysinpast(final int days) {
  139. final long time = System.currentTimeMillis()
  140. - (long) days * 24 * 60 * 60 * 1000;
  141. setStart(new Date(time));
  142. }
  143. /**
  144. * Adds a set of files about which cvs logs will be generated.
  145. *
  146. * @param fileSet a set of files about which cvs logs will be generated.
  147. */
  148. public void addFileset(final FileSet fileSet) {
  149. m_filesets.addElement(fileSet);
  150. }
  151. /**
  152. * Execute task
  153. *
  154. * @exception BuildException if something goes wrong executing the
  155. * cvs command
  156. */
  157. public void execute() throws BuildException {
  158. File savedDir = m_dir; // may be altered in validate
  159. try {
  160. validate();
  161. final Properties userList = new Properties();
  162. loadUserlist(userList);
  163. for (Enumeration e = m_cvsUsers.elements();
  164. e.hasMoreElements();) {
  165. final CvsUser user = (CvsUser) e.nextElement();
  166. user.validate();
  167. userList.put(user.getUserID(), user.getDisplayname());
  168. }
  169. setCommand("log");
  170. if (getTag() != null) {
  171. CvsVersion myCvsVersion = new CvsVersion();
  172. myCvsVersion.setProject(getProject());
  173. myCvsVersion.setTaskName("cvsversion");
  174. myCvsVersion.setCvsRoot(getCvsRoot());
  175. myCvsVersion.setCvsRsh(getCvsRsh());
  176. myCvsVersion.setPassfile(getPassFile());
  177. myCvsVersion.setDest(m_dir);
  178. myCvsVersion.execute();
  179. if (myCvsVersion.supportsCvsLogWithSOption()) {
  180. addCommandArgument("-S");
  181. }
  182. }
  183. if (null != m_start) {
  184. final SimpleDateFormat outputDate =
  185. new SimpleDateFormat("yyyy-MM-dd");
  186. // We want something of the form: -d ">=YYYY-MM-dd"
  187. final String dateRange = ">=" + outputDate.format(m_start);
  188. // Supply '-d' as a separate argument - Bug# 14397
  189. addCommandArgument("-d");
  190. addCommandArgument(dateRange);
  191. }
  192. // Check if list of files to check has been specified
  193. if (!m_filesets.isEmpty()) {
  194. final Enumeration e = m_filesets.elements();
  195. while (e.hasMoreElements()) {
  196. final FileSet fileSet = (FileSet) e.nextElement();
  197. final DirectoryScanner scanner =
  198. fileSet.getDirectoryScanner(getProject());
  199. final String[] files = scanner.getIncludedFiles();
  200. for (int i = 0; i < files.length; i++) {
  201. addCommandArgument(files[i]);
  202. }
  203. }
  204. }
  205. final ChangeLogParser parser = new ChangeLogParser();
  206. final RedirectingStreamHandler handler =
  207. new RedirectingStreamHandler(parser);
  208. log(getCommand(), Project.MSG_VERBOSE);
  209. setDest(m_dir);
  210. setExecuteStreamHandler(handler);
  211. super.execute();
  212. final String errors = handler.getErrors();
  213. if (null != errors) {
  214. log(errors, Project.MSG_ERR);
  215. }
  216. final CVSEntry[] entrySet = parser.getEntrySetAsArray();
  217. final CVSEntry[] filteredEntrySet = filterEntrySet(entrySet);
  218. replaceAuthorIdWithName(userList, filteredEntrySet);
  219. writeChangeLog(filteredEntrySet);
  220. } finally {
  221. m_dir = savedDir;
  222. }
  223. }
  224. /**
  225. * Validate the parameters specified for task.
  226. *
  227. * @throws BuildException if fails validation checks
  228. */
  229. private void validate()
  230. throws BuildException {
  231. if (null == m_dir) {
  232. m_dir = getProject().getBaseDir();
  233. }
  234. if (null == m_destfile) {
  235. final String message = "Destfile must be set.";
  236. throw new BuildException(message);
  237. }
  238. if (!m_dir.exists()) {
  239. final String message = "Cannot find base dir "
  240. + m_dir.getAbsolutePath();
  241. throw new BuildException(message);
  242. }
  243. if (null != m_usersFile && !m_usersFile.exists()) {
  244. final String message = "Cannot find user lookup list "
  245. + m_usersFile.getAbsolutePath();
  246. throw new BuildException(message);
  247. }
  248. }
  249. /**
  250. * Load the userlist from the userList file (if specified) and add to
  251. * list of users.
  252. *
  253. * @param userList the file of users
  254. * @throws BuildException if file can not be loaded for some reason
  255. */
  256. private void loadUserlist(final Properties userList)
  257. throws BuildException {
  258. if (null != m_usersFile) {
  259. try {
  260. userList.load(new FileInputStream(m_usersFile));
  261. } catch (final IOException ioe) {
  262. throw new BuildException(ioe.toString(), ioe);
  263. }
  264. }
  265. }
  266. /**
  267. * Filter the specified entries according to an appropriate rule.
  268. *
  269. * @param entrySet the entry set to filter
  270. * @return the filtered entry set
  271. */
  272. private CVSEntry[] filterEntrySet(final CVSEntry[] entrySet) {
  273. final Vector results = new Vector();
  274. for (int i = 0; i < entrySet.length; i++) {
  275. final CVSEntry cvsEntry = entrySet[i];
  276. final Date date = cvsEntry.getDate();
  277. if (null != m_start && m_start.after(date)) {
  278. //Skip dates that are too early
  279. continue;
  280. }
  281. if (null != m_stop && m_stop.before(date)) {
  282. //Skip dates that are too late
  283. continue;
  284. }
  285. results.addElement(cvsEntry);
  286. }
  287. final CVSEntry[] resultArray = new CVSEntry[results.size()];
  288. results.copyInto(resultArray);
  289. return resultArray;
  290. }
  291. /**
  292. * replace all known author's id's with their maven specified names
  293. */
  294. private void replaceAuthorIdWithName(final Properties userList,
  295. final CVSEntry[] entrySet) {
  296. for (int i = 0; i < entrySet.length; i++) {
  297. final CVSEntry entry = entrySet[ i ];
  298. if (userList.containsKey(entry.getAuthor())) {
  299. entry.setAuthor(userList.getProperty(entry.getAuthor()));
  300. }
  301. }
  302. }
  303. /**
  304. * Print changelog to file specified in task.
  305. *
  306. * @param entrySet the entry set to write.
  307. * @throws BuildException if there is an error writing changelog.
  308. */
  309. private void writeChangeLog(final CVSEntry[] entrySet)
  310. throws BuildException {
  311. FileOutputStream output = null;
  312. try {
  313. output = new FileOutputStream(m_destfile);
  314. final PrintWriter writer =
  315. new PrintWriter(new OutputStreamWriter(output, "UTF-8"));
  316. final ChangeLogWriter serializer = new ChangeLogWriter();
  317. serializer.printChangeLog(writer, entrySet);
  318. } catch (final UnsupportedEncodingException uee) {
  319. getProject().log(uee.toString(), Project.MSG_ERR);
  320. } catch (final IOException ioe) {
  321. throw new BuildException(ioe.toString(), ioe);
  322. } finally {
  323. if (null != output) {
  324. try {
  325. output.close();
  326. } catch (final IOException ioe) {
  327. }
  328. }
  329. }
  330. }
  331. }