1 package org.apache.maven.cvslib;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25
26 import java.text.ParseException;
27 import java.text.SimpleDateFormat;
28
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.Map;
33 import java.util.StringTokenizer;
34 import java.util.TreeMap;
35
36 import org.apache.maven.changelog.ChangeLog;
37 import org.apache.maven.changelog.ChangeLogEntry;
38 import org.apache.maven.changelog.ChangeLogFile;
39 import org.apache.maven.changelog.ChangeLogParser;
40
41
42 /**
43 * A class to parse cvs log output
44 *
45 * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
46 * @version $Id: CvsChangeLogParser.java 532339 2007-04-25 12:28:56Z ltheussl $
47 */
48 public class CvsChangeLogParser implements ChangeLogParser
49 {
50 /**
51 * Old formatter used to parse CVS date/timestamp.
52 */
53 private static final SimpleDateFormat OLD_CVS_TIMESTAMP_FORMAT =
54 new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" );
55
56 /**
57 * New formatter used to parse CVS date/timestamp.
58 */
59 private static final SimpleDateFormat NEW_CVS_TIMESTAMP_FORMAT =
60 new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss Z" );
61
62 /**
63 * Custom date/time formatter. Rounds ChangeLogEntry times to the nearest
64 * minute.
65 */
66 private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT =
67 new SimpleDateFormat( "yyyyMMddHHmm" );
68
69
70
71 /** expecting file information */
72 private static final int GET_FILE = 1;
73
74 /** expecting date */
75 private static final int GET_DATE = 2;
76
77 /** expecting comments */
78 private static final int GET_COMMENT = 3;
79
80 /** expecting revision */
81 private static final int GET_REVISION = 4;
82
83 /** Marks start of file data*/
84 private static final String START_FILE = "Working file: ";
85
86 /** Marks end of file */
87 private static final String END_FILE =
88 "==================================="
89 + "==========================================";
90
91 /** Marks start of revision */
92 private static final String START_REVISION = "----------------------------";
93
94 /** Marks revision data */
95 private static final String REVISION_TAG = "revision ";
96
97 /** Marks date data */
98 private static final String DATE_TAG = "date: ";
99
100 /**
101 * rcs entries, in reverse (date, time, author, comment) order
102 */
103 private Map entries = new TreeMap( Collections.reverseOrder() );
104
105 /** current status of the parser */
106 private int status = GET_FILE;
107
108 /** the current log entry being processed by the parser*/
109 private ChangeLogEntry currentLogEntry = null;
110
111 /** the current file being processed by the parser */
112 private ChangeLogFile currentFile = null;
113
114 /**
115 * Create a new ChangeLogParser.
116 */
117 public CvsChangeLogParser()
118 {
119 }
120
121 /**
122 * initialize the parser from the change log
123 * @param changeLog the controlling task
124 * @see ChangeLogParser#init(ChangeLog)
125 */
126 public void init( ChangeLog changeLog )
127 {
128 }
129
130 /**
131 * Clean up any parser resources
132 * @see ChangeLogParser#cleanup()
133 */
134 public void cleanup()
135 {
136 }
137
138 /**
139 * Parse the input stream into a collection.
140 * @param anInputStream an input stream containing cvs log output
141 * @return a collection of ChangeLogEntry's
142 * @throws IOException when there are errors reading the provided stream
143 */
144 public Collection parse( InputStream anInputStream )
145 throws IOException
146 {
147 BufferedReader stream =
148 new BufferedReader( new InputStreamReader( anInputStream ) );
149
150
151
152
153
154
155 String line = null;
156
157 while ( ( line = stream.readLine() ) != null )
158 {
159 switch ( getStatus() )
160 {
161 case GET_FILE :
162 processGetFile( line );
163
164 break;
165
166 case GET_REVISION :
167 processGetRevision( line );
168
169 break;
170
171 case GET_DATE :
172 processGetDate( line );
173
174 break;
175
176 case GET_COMMENT :
177 processGetComment( line );
178
179 break;
180
181 default :
182 throw new IllegalStateException( "Unknown state: " + status );
183 }
184 }
185
186 return entries.values();
187 }
188
189 /**
190 * Add a change log entry to the list (if it's not already there)
191 * with the given file.
192 * @param entry a {@link ChangeLogEntry} to be added to the list if another
193 * with the same key doesn't exist already. If the entry's author
194 * is null, the entry wont be added
195 * @param file a {@link ChangeLogFile} to be added to the entry
196 */
197 private void addEntry( ChangeLogEntry entry, ChangeLogFile file )
198 {
199
200 if ( entry.getAuthor() == null )
201 {
202 return;
203 }
204
205 String key =
206 ENTRY_KEY_TIMESTAMP_FORMAT.format( entry.getDate() )
207 + entry.getAuthor() + entry.getComment();
208
209 if ( !entries.containsKey( key ) )
210 {
211 entry.addFile( file );
212 entries.put( key, entry );
213 }
214 else
215 {
216 ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get( key );
217
218 existingEntry.addFile( file );
219 }
220 }
221
222 /**
223 * Process the current input line in the Get File state.
224 * @param line a line of text from the cvs log output
225 */
226 private void processGetFile( String line )
227 {
228 if ( line.startsWith( START_FILE ) )
229 {
230 setCurrentLogEntry( new ChangeLogEntry() );
231 setCurrentFile( new ChangeLogFile( line.substring(
232 START_FILE.length(), line.length() ) ) );
233 setStatus( GET_REVISION );
234 }
235 }
236
237 /**
238 * Process the current input line in the Get Revision state.
239 * @param line a line of text from the cvs log output
240 */
241 private void processGetRevision( String line )
242 {
243 if ( line.startsWith( REVISION_TAG ) )
244 {
245 getCurrentFile().setRevision( line.substring(
246 REVISION_TAG.length() ) );
247 setStatus( GET_DATE );
248 }
249 else if ( line.startsWith( END_FILE ) )
250 {
251
252
253
254 setStatus( GET_FILE );
255 addEntry( getCurrentLogEntry(), getCurrentFile() );
256 }
257 }
258
259 /**
260 * Process the current input line in the Get Date state.
261 * @param line a line of text from the cvs log output
262 */
263 private void processGetDate( String line )
264 {
265 if ( line.startsWith( DATE_TAG ) )
266 {
267
268
269 StringTokenizer tokenizer = new StringTokenizer( line, ";" );
270 String dateToken = tokenizer.nextToken();
271 String dateString =
272 dateToken.trim().substring( "date: ".length() ).trim();
273
274 getCurrentLogEntry().setDate( parseDate( dateString ) );
275
276 String authorToken = tokenizer.nextToken();
277 String author =
278 authorToken.trim().substring( "author: ".length() ).trim();
279
280 getCurrentLogEntry().setAuthor( author );
281 setStatus( GET_COMMENT );
282 }
283 }
284
285 /**
286 * Tries to parse the given String according to all known CVS timeformats.
287 *
288 * @param dateString String to parse
289 * @return <code>java.util.Date</code> representing the time.
290 * @throws IllegalArgumentException if it's not possible to parse the date.
291 */
292 private Date parseDate( String dateString )
293 {
294 Date date;
295
296 try
297 {
298 date = OLD_CVS_TIMESTAMP_FORMAT.parse( dateString );
299 }
300 catch ( ParseException e )
301 {
302
303 try
304 {
305 date = NEW_CVS_TIMESTAMP_FORMAT.parse( dateString );
306 }
307 catch ( ParseException e1 )
308 {
309 throw new IllegalArgumentException(
310 "I don't understand this date: " + dateString );
311 }
312 }
313
314 return date;
315 }
316
317 /**
318 * Process the current input line in the Get Comment state.
319 * @param line a line of text from the cvs log output
320 */
321 private void processGetComment( String line )
322 {
323 if ( line.startsWith( START_REVISION ) )
324 {
325
326 addEntry( getCurrentLogEntry(), getCurrentFile() );
327
328
329 setCurrentLogEntry( new ChangeLogEntry() );
330
331
332 setCurrentFile( new ChangeLogFile( getCurrentFile().getName() ) );
333 setStatus( GET_REVISION );
334 }
335 else if ( line.startsWith( END_FILE ) )
336 {
337 addEntry( getCurrentLogEntry(), getCurrentFile() );
338 setStatus( GET_FILE );
339 }
340 else
341 {
342
343 getCurrentLogEntry().setComment( getCurrentLogEntry()
344 .getComment() + line
345 + "\n" );
346 }
347 }
348
349 /**
350 * Getter for property currentFile.
351 * @return Value of property currentFile.
352 */
353 private ChangeLogFile getCurrentFile()
354 {
355 return currentFile;
356 }
357
358 /**
359 * Setter for property currentFile.
360 * @param currentFile New value of property currentFile.
361 */
362 private void setCurrentFile( ChangeLogFile currentFile )
363 {
364 this.currentFile = currentFile;
365 }
366
367 /**
368 * Getter for property currentLogEntry.
369 * @return Value of property currentLogEntry.
370 */
371 private ChangeLogEntry getCurrentLogEntry()
372 {
373 return currentLogEntry;
374 }
375
376 /**
377 * Setter for property currentLogEntry.
378 * @param currentLogEntry New value of property currentLogEntry.
379 */
380 private void setCurrentLogEntry( ChangeLogEntry currentLogEntry )
381 {
382 this.currentLogEntry = currentLogEntry;
383 }
384
385 /**
386 * Getter for property status.
387 * @return Value of property status.
388 */
389 private int getStatus()
390 {
391 return status;
392 }
393
394 /**
395 * Setter for property status.
396 * @param status New value of property status.
397 */
398 private void setStatus( int status )
399 {
400 this.status = status;
401 }
402
403 public void setDateFormatInFile( String dateFormat )
404 {
405 }
406 }