KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > cruisecontrol > sourcecontrols > VssJournal


1 /********************************************************************************
2  * CruiseControl, a Continuous Integration Toolkit
3  * Copyright (c) 2001, ThoughtWorks, Inc.
4  * 651 W Washington Ave. Suite 600
5  * Chicago, IL 60661 USA
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * + Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * + Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  *
20  * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
21  * names of its contributors may be used to endorse or promote
22  * products derived from this software without specific prior
23  * written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
29  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  ********************************************************************************/

37 package net.sourceforge.cruisecontrol.sourcecontrols;
38
39 import java.io.BufferedReader JavaDoc;
40 import java.io.FileReader JavaDoc;
41 import java.text.ParseException JavaDoc;
42 import java.text.SimpleDateFormat JavaDoc;
43 import java.util.ArrayList JavaDoc;
44 import java.util.Date JavaDoc;
45 import java.util.Hashtable JavaDoc;
46 import java.util.List JavaDoc;
47 import java.util.Locale JavaDoc;
48 import java.util.Map JavaDoc;
49
50 import net.sourceforge.cruisecontrol.CruiseControlException;
51 import net.sourceforge.cruisecontrol.Modification;
52 import net.sourceforge.cruisecontrol.SourceControl;
53 import net.sourceforge.cruisecontrol.util.ValidationHelper;
54
55 import org.apache.log4j.Logger;
56
57 /**
58  * This class handles all VSS-related aspects of determining the modifications since the last good build.
59  *
60  * This class uses Source Safe Journal files. Unlike the history files that are generated by executing
61  * <code>ss.exe history</code>, journal files must be setup by the Source Safe administrator before the point that
62  * logging of modifications is to occur.
63  *
64  * This code has been tested against Visual Source Safe v6.0 build 8383.
65  *
66  * @author Eli Tucker
67  * @author <a HREF="mailto:alden@thoughtworks.com">alden almagro</a>
68  * @author <a HREF="mailto:jcyip@thoughtworks.com">Jason Yip</a>
69  * @author Arun Aggarwal
70  * @author Jonny Boman
71  * @author <a HREF="mailto:simon.brandhof@hortis.ch">Simon Brandhof</a>
72  */

73 public class VssJournal implements SourceControl {
74
75     private static final Logger LOG = Logger.getLogger(VssJournal.class);
76
77     private String JavaDoc dateFormat;
78     private String JavaDoc timeFormat;
79     private SimpleDateFormat JavaDoc vssDateTimeFormat;
80     private boolean overridenDateFormat = false;
81
82     private String JavaDoc ssDir = "$/";
83     private String JavaDoc journalFile;
84
85     private Hashtable JavaDoc properties = new Hashtable JavaDoc();
86     private String JavaDoc property;
87     private String JavaDoc propertyOnDelete;
88
89     private Date JavaDoc lastBuild;
90
91     private ArrayList JavaDoc modifications = new ArrayList JavaDoc();
92
93     public VssJournal() {
94         dateFormat = "MM/dd/yy";
95         timeFormat = "hh:mma";
96         constructVssDateTimeFormat();
97     }
98     /**
99      * Set the project to get history from
100      *
101      */

102     public void setSsDir(String JavaDoc s) {
103         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
104         if (!s.startsWith("$")) {
105             sb.append("$");
106         }
107         if (s.endsWith("/")) {
108             sb.append(s.substring(0, s.length() - 1));
109         } else {
110             sb.append(s);
111         }
112         this.ssDir = sb.toString();
113     }
114
115     /**
116      * Full path to journal file. Example: <code>c:/vssdata/journal/journal.txt</code>
117      *
118      * @param journalFile
119      */

120     public void setJournalFile(String JavaDoc journalFile) {
121         this.journalFile = journalFile;
122     }
123
124     /**
125      * Choose a property to be set if the project has modifications if we have a change that only requires repackaging,
126      * i.e. jsp, we don't need to recompile everything, just rejar.
127      *
128      * @param property
129      */

130     public void setProperty(String JavaDoc property) {
131         this.property = property;
132     }
133
134     /**
135      * Set the name of the property to be set if some files were deleted or renamed from VSS on this project.
136      *
137      * @param propertyOnDelete the name of the property to set
138      */

139     public void setPropertyOnDelete(String JavaDoc propertyOnDelete) {
140         this.propertyOnDelete = propertyOnDelete;
141     }
142     
143     /**
144      * Sets the date format to use for parsing VSS journal.
145      *
146      * The default date format is <code>MM/dd/yy</code>. If your VSS server is set to a different region, you may wish
147      * to use a format such as <code>dd/MM/yy</code>.
148      *
149      * @see java.text.SimpleDateFormat
150      */

151     public void setDateFormat(String JavaDoc format) {
152         dateFormat = format;
153         overridenDateFormat = true;
154         constructVssDateTimeFormat();
155     }
156
157     /**
158      * Sets the time format to use for parsing VSS journal.
159      *
160      * The default time format is <code>hh:mma</code> . If your VSS server is set to a different region, you may wish to
161      * use a format such as <code>HH:mm</code> .
162      *
163      * @see java.text.SimpleDateFormat
164      */

165     public void setTimeFormat(String JavaDoc format) {
166         timeFormat = format;
167         constructVssDateTimeFormat();
168     }
169     
170     private void constructVssDateTimeFormat() {
171         vssDateTimeFormat = new SimpleDateFormat JavaDoc(dateFormat + " " + timeFormat, Locale.US);
172     }
173
174
175     /**
176      * Sets the _lastBuild date. Protected so it can be used by tests.
177      */

178     protected void setLastBuildDate(Date JavaDoc lastBuild) {
179         this.lastBuild = lastBuild;
180     }
181
182     public Map JavaDoc getProperties() {
183         return properties;
184     }
185
186     public void validate() throws CruiseControlException {
187         ValidationHelper.assertIsSet(journalFile, "journalfile", this.getClass());
188         ValidationHelper.assertIsSet(ssDir, "ssdir", this.getClass());
189     }
190
191     /**
192      * Do the work... I'm writing to a file since VSS will start wrapping lines if I read directly from the stream.
193      */

194     public List JavaDoc getModifications(Date JavaDoc lastBuild, Date JavaDoc now) {
195         this.lastBuild = lastBuild;
196         modifications.clear();
197
198         try {
199             final BufferedReader JavaDoc br = new BufferedReader JavaDoc(new FileReader JavaDoc(journalFile));
200             try {
201                 String JavaDoc s = br.readLine();
202                 while (s != null) {
203                     ArrayList JavaDoc entry = new ArrayList JavaDoc();
204                     entry.add(s);
205                     s = br.readLine();
206                     while (s != null && !s.equals("")) {
207                         entry.add(s);
208                         s = br.readLine();
209                     }
210                     Modification mod = handleEntry(entry);
211                     if (mod != null) {
212                         modifications.add(mod);
213                     }
214
215                     if ("".equals(s)) {
216                         s = br.readLine();
217                     }
218                 }
219             } finally {
220                 br.close();
221             }
222         } catch (Exception JavaDoc e) {
223             LOG.warn(e);
224         }
225
226         if (property != null && modifications.size() > 0) {
227             properties.put(property, "true");
228         }
229
230         LOG.info("Found " + modifications.size() + " modified files");
231         return modifications;
232     }
233
234     /**
235      * Parse individual VSS history entry
236      *
237      * @param historyEntry
238      */

239     protected Modification handleEntry(List JavaDoc historyEntry) {
240         Modification mod = new Modification("vss");
241         String JavaDoc nameAndDateLine = (String JavaDoc) historyEntry.get(2);
242         mod.userName = parseUser(nameAndDateLine);
243         mod.modifiedTime = parseDate(nameAndDateLine);
244
245         String JavaDoc folderLine = (String JavaDoc) historyEntry.get(0);
246         String JavaDoc fileLine = (String JavaDoc) historyEntry.get(3);
247         boolean setPropertyOnDelete = false;
248
249         if (!isInSsDir(folderLine)) {
250             // We are only interested in modifications to files in the specified ssdir
251
return null;
252         } else if (isBeforeLastBuild(mod.modifiedTime)) {
253             // We are only interested in modifications since the last build
254
return null;
255         } else if (fileLine.startsWith("Labeled")) {
256             // We don't add labels.
257
return null;
258         } else if (fileLine.startsWith("Checked in")) {
259             String JavaDoc fileName = substringFromLastSlash(folderLine);
260             String JavaDoc folderName = substringToLastSlash(folderLine);
261             Modification.ModifiedFile modfile = mod.createModifiedFile(fileName, folderName);
262
263             modfile.action = "checkin";
264             mod.comment = parseComment(historyEntry);
265         } else if (fileLine.indexOf(" renamed to ") > -1) {
266             // TODO: This is a special case that is really two modifications: deleted and recovered.
267
// For now I'll consider it a deleted to force a clean build.
268
// I should really make this two modifications.
269
mod.comment = parseComment(historyEntry);
270
271             String JavaDoc fileName = fileLine.substring(0, fileLine.indexOf(" "));
272
273             Modification.ModifiedFile modfile = mod.createModifiedFile(fileName, folderLine);
274             modfile.action = "delete";
275             setPropertyOnDelete = true;
276
277         } else if (fileLine.indexOf(" moved to ") > -1) {
278             // TODO: This is a special case that is really two modifications: deleted and recovered.
279
// For now I'll consider it a deleted to force a clean build.
280
// I should really make this two modifications.
281
mod.comment = parseComment(historyEntry);
282             String JavaDoc fileName = fileLine.substring(0, fileLine.indexOf(" "));
283
284             Modification.ModifiedFile modfile = mod.createModifiedFile(fileName, folderLine);
285             modfile.action = "delete";
286             setPropertyOnDelete = true;
287
288         } else {
289             String JavaDoc fileName = fileLine.substring(0, fileLine.lastIndexOf(" "));
290             Modification.ModifiedFile modfile = mod.createModifiedFile(fileName, folderLine);
291
292             mod.comment = parseComment(historyEntry);
293
294             if (fileLine.endsWith("added")) {
295                 modfile.action = "add";
296             } else if (fileLine.endsWith("deleted")) {
297                 modfile.action = "delete";
298                 setPropertyOnDelete = true;
299             } else if (fileLine.endsWith("recovered")) {
300                 modfile.action = "recover";
301             } else if (fileLine.endsWith("shared")) {
302                 modfile.action = "branch";
303             }
304         }
305
306         if (propertyOnDelete != null && setPropertyOnDelete) {
307             properties.put(propertyOnDelete, "true");
308         }
309
310         if (property != null) {
311             properties.put(property, "true");
312         }
313         return mod;
314     }
315
316     /**
317      * parse comment from vss history (could be multiline)
318      *
319      * @param a
320      * @return the comment
321      */

322     private String JavaDoc parseComment(List JavaDoc a) {
323         StringBuffer JavaDoc comment = new StringBuffer JavaDoc();
324         for (int i = 4; i < a.size(); i++) {
325             comment.append(a.get(i)).append(" ");
326         }
327         return comment.toString().trim();
328     }
329
330     /**
331      * Parse date/time from VSS file history
332      *
333      * The nameAndDateLine will look like User: Etucker Date: 6/26/01 Time: 11:53a Sometimes also this User: Aaggarwa
334      * Date: 6/29/:1 Time: 3:40p Note the ":" instead of a "0"
335      *
336      * May give additional DateFormats through the vssjournaldateformat tag. E.g.
337      * <code><vssjournaldateformat format="yy-MM-dd hh:mm"/></code>
338      *
339      * @return Date
340      * @param nameAndDateLine
341      */

342     public Date JavaDoc parseDate(String JavaDoc nameAndDateLine) {
343         // Extract date and time into one string with just one space separating the date from the time
344
String JavaDoc dateString = nameAndDateLine.substring(
345                 nameAndDateLine.indexOf("Date:") + 5,
346                 nameAndDateLine.indexOf("Time:")).trim();
347         
348         String JavaDoc timeString = nameAndDateLine.substring(
349                 nameAndDateLine.indexOf("Time:") + 5).trim();
350         
351         if (!overridenDateFormat) {
352             // Fixup for weird format
353
int indexOfColon = dateString.indexOf("/:");
354             if (indexOfColon != -1) {
355                 dateString = dateString.substring(0, indexOfColon)
356                         + dateString.substring(indexOfColon, indexOfColon + 2).replace(':', '0')
357                         + dateString.substring(indexOfColon + 2);
358             }
359
360         }
361         StringBuffer JavaDoc dateToParse = new StringBuffer JavaDoc();
362         dateToParse.append(dateString);
363         dateToParse.append(" ");
364         dateToParse.append(timeString);
365         if (!overridenDateFormat) {
366             // the am/pm marker of java.text.SimpleDateFormat is 'am' or 'pm'
367
// but we have 'a' or 'p' in default VSS logs with default time format
368
// (for example '6:08p' instead of '6:08pm')
369
dateToParse.append("m");
370         }
371         try {
372             return vssDateTimeFormat.parse(dateToParse.toString());
373             
374         } catch (ParseException JavaDoc pe) {
375             LOG.error("Could not parse date in VssJournal file : " + dateToParse.toString(), pe);
376         }
377         return null;
378     }
379
380     /**
381      * Parse username from VSS file history
382      *
383      * @param userLine
384      * @return the user name who made the modification
385      */

386     public String JavaDoc parseUser(String JavaDoc userLine) {
387         final int startOfUserName = 6;
388
389         try {
390             return userLine.substring(startOfUserName, userLine.indexOf("Date: ") - 1).trim();
391         } catch (StringIndexOutOfBoundsException JavaDoc e) {
392             LOG.error("Unparsable string was: " + userLine);
393             throw e;
394         }
395
396     }
397
398     /**
399      * Returns the substring of the given string from the last "/" character. UNLESS the last slash character is the
400      * last character or the string does not contain a slash. In that case, return the whole string.
401      */

402     public String JavaDoc substringFromLastSlash(String JavaDoc input) {
403         int lastSlashPos = input.lastIndexOf("/");
404         if (lastSlashPos > 0 && lastSlashPos + 1 <= input.length()) {
405             return input.substring(lastSlashPos + 1);
406         }
407         
408         return input;
409     }
410
411     /**
412      * Returns the substring of the given string from the beginning to the last "/" character or till the end of the
413      * string if no slash character exists.
414      */

415     public String JavaDoc substringToLastSlash(String JavaDoc input) {
416         int lastSlashPos = input.lastIndexOf("/");
417         if (lastSlashPos > 0) {
418             return input.substring(0, lastSlashPos);
419         }
420         
421         return input;
422     }
423
424     /**
425      * Determines if the given folder is in the ssdir specified for this VssJournalElement.
426      */

427     protected boolean isInSsDir(String JavaDoc path) {
428         boolean isInDir = (path.toLowerCase().startsWith(ssDir.toLowerCase()));
429         if (isInDir) {
430             // exclude similarly prefixed paths
431
if (ssDir.equalsIgnoreCase(path) // is exact same as ssDir (this happens)
432
|| ('/' == path.charAt(ssDir.length())) // subdirs below matching ssDir
433
|| "$/".equalsIgnoreCase(ssDir)) { // everything is included
434

435                 // do nothing
436
} else {
437                 // is not really in subdir
438
isInDir = false;
439             }
440         }
441         return isInDir;
442     }
443
444     /**
445      * Determines if the date given is before the last build for this VssJournalElement.
446      */

447     protected boolean isBeforeLastBuild(Date JavaDoc date) {
448         return date.before(lastBuild);
449     }
450 }
451
Popular Tags