001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.cvslib;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.OutputStreamWriter;
025: import java.io.PrintWriter;
026: import java.io.UnsupportedEncodingException;
027: import java.text.SimpleDateFormat;
028: import java.util.Date;
029: import java.util.Enumeration;
030: import java.util.Properties;
031: import java.util.Vector;
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.DirectoryScanner;
034: import org.apache.tools.ant.Project;
035: import org.apache.tools.ant.taskdefs.AbstractCvsTask;
036: import org.apache.tools.ant.types.FileSet;
037: import org.apache.tools.ant.util.FileUtils;
038:
039: /**
040: * Examines the output of cvs log and group related changes together.
041: *
042: * It produces an XML output representing the list of changes.
043: * <pre>
044: * <font color=#0000ff><!-- Root element --></font>
045: * <font color=#6a5acd><!ELEMENT</font> changelog <font color=#ff00ff>
046: * (entry</font><font color=#ff00ff>+</font><font color=#ff00ff>)
047: * </font><font color=#6a5acd>></font>
048: * <font color=#0000ff><!-- CVS Entry --></font>
049: * <font color=#6a5acd><!ELEMENT</font> entry <font color=#ff00ff>
050: * (date,author,file</font><font color=#ff00ff>+</font><font color=#ff00ff>,msg)
051: * </font><font color=#6a5acd>></font>
052: * <font color=#0000ff><!-- Date of cvs entry --></font>
053: * <font color=#6a5acd><!ELEMENT</font> date <font color=#ff00ff>(#PCDATA)
054: * </font><font color=#6a5acd>></font>
055: * <font color=#0000ff><!-- Author of change --></font>
056: * <font color=#6a5acd><!ELEMENT</font> author <font color=#ff00ff>(#PCDATA)
057: * </font><font color=#6a5acd>></font>
058: * <font color=#0000ff><!-- List of files affected --></font>
059: * <font color=#6a5acd><!ELEMENT</font> msg <font color=#ff00ff>(#PCDATA)
060: * </font><font color=#6a5acd>></font>
061: * <font color=#0000ff><!-- File changed --></font>
062: * <font color=#6a5acd><!ELEMENT</font> file <font color=#ff00ff>
063: * (name,revision,prevrevision</font><font color=#ff00ff>?</font>
064: * <font color=#ff00ff>)</font><font color=#6a5acd>></font>
065: * <font color=#0000ff><!-- Name of the file --></font>
066: * <font color=#6a5acd><!ELEMENT</font> name <font color=#ff00ff>(#PCDATA)
067: * </font><font color=#6a5acd>></font>
068: * <font color=#0000ff><!-- Revision number --></font>
069: * <font color=#6a5acd><!ELEMENT</font> revision <font color=#ff00ff>
070: * (#PCDATA)</font><font color=#6a5acd>></font>
071: * <font color=#0000ff><!-- Previous revision number --></font>
072: * <font color=#6a5acd><!ELEMENT</font> prevrevision <font color=#ff00ff>
073: * (#PCDATA)</font><font color=#6a5acd>></font>
074: * </pre>
075: *
076: * @since Ant 1.5
077: * @ant.task name="cvschangelog" category="scm"
078: */
079: public class ChangeLogTask extends AbstractCvsTask {
080: /** User list */
081: private File usersFile;
082:
083: /** User list */
084: private Vector cvsUsers = new Vector();
085:
086: /** Input dir */
087: private File inputDir;
088:
089: /** Output file */
090: private File destFile;
091:
092: /** The earliest date at which to start processing entries. */
093: private Date startDate;
094:
095: /** The latest date at which to stop processing entries. */
096: private Date endDate;
097:
098: /**
099: * Filesets containing list of files against which the cvs log will be
100: * performed. If empty then all files in the working directory will
101: * be checked.
102: */
103: private final Vector filesets = new Vector();
104:
105: /**
106: * Set the base dir for cvs.
107: *
108: * @param inputDir The new dir value
109: */
110: public void setDir(final File inputDir) {
111: this .inputDir = inputDir;
112: }
113:
114: /**
115: * Set the output file for the log.
116: *
117: * @param destFile The new destfile value
118: */
119: public void setDestfile(final File destFile) {
120: this .destFile = destFile;
121: }
122:
123: /**
124: * Set a lookup list of user names & addresses
125: *
126: * @param usersFile The file containing the users info.
127: */
128: public void setUsersfile(final File usersFile) {
129: this .usersFile = usersFile;
130: }
131:
132: /**
133: * Add a user to list changelog knows about.
134: *
135: * @param user the user
136: */
137: public void addUser(final CvsUser user) {
138: cvsUsers.addElement(user);
139: }
140:
141: /**
142: * Set the date at which the changelog should start.
143: *
144: * @param start The date at which the changelog should start.
145: */
146: public void setStart(final Date start) {
147: this .startDate = start;
148: }
149:
150: /**
151: * Set the date at which the changelog should stop.
152: *
153: * @param endDate The date at which the changelog should stop.
154: */
155: public void setEnd(final Date endDate) {
156: this .endDate = endDate;
157: }
158:
159: /**
160: * Set the number of days worth of log entries to process.
161: *
162: * @param days the number of days of log to process.
163: */
164: public void setDaysinpast(final int days) {
165: final long time = System.currentTimeMillis() - (long) days * 24
166: * 60 * 60 * 1000;
167:
168: setStart(new Date(time));
169: }
170:
171: /**
172: * Adds a set of files about which cvs logs will be generated.
173: *
174: * @param fileSet a set of files about which cvs logs will be generated.
175: */
176: public void addFileset(final FileSet fileSet) {
177: filesets.addElement(fileSet);
178: }
179:
180: /**
181: * Execute task
182: *
183: * @exception BuildException if something goes wrong executing the
184: * cvs command
185: */
186: public void execute() throws BuildException {
187: File savedDir = inputDir; // may be altered in validate
188:
189: try {
190:
191: validate();
192: final Properties userList = new Properties();
193:
194: loadUserlist(userList);
195:
196: for (int i = 0, size = cvsUsers.size(); i < size; i++) {
197: final CvsUser user = (CvsUser) cvsUsers.get(i);
198: user.validate();
199: userList.put(user.getUserID(), user.getDisplayname());
200: }
201:
202: setCommand("log");
203:
204: if (getTag() != null) {
205: CvsVersion myCvsVersion = new CvsVersion();
206: myCvsVersion.setProject(getProject());
207: myCvsVersion.setTaskName("cvsversion");
208: myCvsVersion.setCvsRoot(getCvsRoot());
209: myCvsVersion.setCvsRsh(getCvsRsh());
210: myCvsVersion.setPassfile(getPassFile());
211: myCvsVersion.setDest(inputDir);
212: myCvsVersion.execute();
213: if (myCvsVersion.supportsCvsLogWithSOption()) {
214: addCommandArgument("-S");
215: }
216: }
217: if (null != startDate) {
218: final SimpleDateFormat outputDate = new SimpleDateFormat(
219: "yyyy-MM-dd");
220:
221: // We want something of the form: -d ">=YYYY-MM-dd"
222: final String dateRange = ">="
223: + outputDate.format(startDate);
224:
225: // Supply '-d' as a separate argument - Bug# 14397
226: addCommandArgument("-d");
227: addCommandArgument(dateRange);
228: }
229:
230: // Check if list of files to check has been specified
231: if (!filesets.isEmpty()) {
232: final Enumeration e = filesets.elements();
233:
234: while (e.hasMoreElements()) {
235: final FileSet fileSet = (FileSet) e.nextElement();
236: final DirectoryScanner scanner = fileSet
237: .getDirectoryScanner(getProject());
238: final String[] files = scanner.getIncludedFiles();
239:
240: for (int i = 0; i < files.length; i++) {
241: addCommandArgument(files[i]);
242: }
243: }
244: }
245:
246: final ChangeLogParser parser = new ChangeLogParser();
247: final RedirectingStreamHandler handler = new RedirectingStreamHandler(
248: parser);
249:
250: log(getCommand(), Project.MSG_VERBOSE);
251:
252: setDest(inputDir);
253: setExecuteStreamHandler(handler);
254: try {
255: super .execute();
256: } finally {
257: final String errors = handler.getErrors();
258:
259: if (null != errors) {
260: log(errors, Project.MSG_ERR);
261: }
262: }
263: final CVSEntry[] entrySet = parser.getEntrySetAsArray();
264: final CVSEntry[] filteredEntrySet = filterEntrySet(entrySet);
265:
266: replaceAuthorIdWithName(userList, filteredEntrySet);
267:
268: writeChangeLog(filteredEntrySet);
269:
270: } finally {
271: inputDir = savedDir;
272: }
273: }
274:
275: /**
276: * Validate the parameters specified for task.
277: *
278: * @throws BuildException if fails validation checks
279: */
280: private void validate() throws BuildException {
281: if (null == inputDir) {
282: inputDir = getProject().getBaseDir();
283: }
284: if (null == destFile) {
285: final String message = "Destfile must be set.";
286:
287: throw new BuildException(message);
288: }
289: if (!inputDir.exists()) {
290: final String message = "Cannot find base dir "
291: + inputDir.getAbsolutePath();
292:
293: throw new BuildException(message);
294: }
295: if (null != usersFile && !usersFile.exists()) {
296: final String message = "Cannot find user lookup list "
297: + usersFile.getAbsolutePath();
298:
299: throw new BuildException(message);
300: }
301: }
302:
303: /**
304: * Load the userlist from the userList file (if specified) and add to
305: * list of users.
306: *
307: * @param userList the file of users
308: * @throws BuildException if file can not be loaded for some reason
309: */
310: private void loadUserlist(final Properties userList)
311: throws BuildException {
312: if (null != usersFile) {
313: try {
314: userList.load(new FileInputStream(usersFile));
315: } catch (final IOException ioe) {
316: throw new BuildException(ioe.toString(), ioe);
317: }
318: }
319: }
320:
321: /**
322: * Filter the specified entries according to an appropriate rule.
323: *
324: * @param entrySet the entry set to filter
325: * @return the filtered entry set
326: */
327: private CVSEntry[] filterEntrySet(final CVSEntry[] entrySet) {
328: final Vector results = new Vector();
329:
330: for (int i = 0; i < entrySet.length; i++) {
331: final CVSEntry cvsEntry = entrySet[i];
332: final Date date = cvsEntry.getDate();
333:
334: //bug#30471
335: //this is caused by Date.after throwing a NullPointerException
336: //for some reason there's no date set in the CVSEntry
337: //Java 1.3.1 API
338: //http://java.sun.com/j2se/1.3/docs/api/java/util/Date.html#after(java.util.Date)
339: //doesn't throw NullPointerException
340: //Java 1.4.2 + 1.5 API
341: //http://java.sun.com/j2se/1.4.2/docs/api/java/util/Date.html#after(java.util.Date)
342: //according to the docs it doesn't throw, according to the bug report it does
343: //http://java.sun.com/j2se/1.5.0/docs/api/java/util/Date.html#after(java.util.Date)
344: //according to the docs it does throw
345:
346: //for now skip entries which are missing a date
347: if (null == date) {
348: continue;
349: }
350:
351: if (null != startDate && startDate.after(date)) {
352: //Skip dates that are too early
353: continue;
354: }
355: if (null != endDate && endDate.before(date)) {
356: //Skip dates that are too late
357: continue;
358: }
359: results.addElement(cvsEntry);
360: }
361:
362: final CVSEntry[] resultArray = new CVSEntry[results.size()];
363:
364: results.copyInto(resultArray);
365: return resultArray;
366: }
367:
368: /**
369: * replace all known author's id's with their maven specified names
370: */
371: private void replaceAuthorIdWithName(final Properties userList,
372: final CVSEntry[] entrySet) {
373: for (int i = 0; i < entrySet.length; i++) {
374:
375: final CVSEntry entry = entrySet[i];
376: if (userList.containsKey(entry.getAuthor())) {
377: entry
378: .setAuthor(userList.getProperty(entry
379: .getAuthor()));
380: }
381: }
382: }
383:
384: /**
385: * Print changelog to file specified in task.
386: *
387: * @param entrySet the entry set to write.
388: * @throws BuildException if there is an error writing changelog.
389: */
390: private void writeChangeLog(final CVSEntry[] entrySet)
391: throws BuildException {
392: FileOutputStream output = null;
393:
394: try {
395: output = new FileOutputStream(destFile);
396:
397: final PrintWriter writer = new PrintWriter(
398: new OutputStreamWriter(output, "UTF-8"));
399:
400: final ChangeLogWriter serializer = new ChangeLogWriter();
401:
402: serializer.printChangeLog(writer, entrySet);
403: } catch (final UnsupportedEncodingException uee) {
404: getProject().log(uee.toString(), Project.MSG_ERR);
405: } catch (final IOException ioe) {
406: throw new BuildException(ioe.toString(), ioe);
407: } finally {
408: FileUtils.close(output);
409: }
410: }
411: }
|