View Javadoc

1   package org.apache.maven.cvslib;
2   
3   /* ====================================================================
4    *   Licensed to the Apache Software Foundation (ASF) under one or more
5    *   contributor license agreements.  See the NOTICE file distributed with
6    *   this work for additional information regarding copyright ownership.
7    *   The ASF licenses this file to You under the Apache License, Version 2.0
8    *   (the "License"); you may not use this file except in compliance with
9    *   the License.  You may obtain a copy of the License at
10   *
11   *       http://www.apache.org/licenses/LICENSE-2.0
12   *
13   *   Unless required by applicable law or agreed to in writing, software
14   *   distributed under the License is distributed on an "AS IS" BASIS,
15   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *   See the License for the specific language governing permissions and
17   *   limitations under the License.
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      // state machine constants for reading cvs output
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         // current state transitions in the state machine - starts with Get File
151         //      Get File                -> Get Revision
152         //      Get Revision            -> Get Date or Get File
153         //      Get Date                -> Get Comment
154         //      Get Comment             -> Get Comment or Get Revision
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         // do not add if entry is not populated
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             // If we encounter an end of file line, it means there 
252             // are no more revisions for the current file.
253             // there could also be a file still being processed.
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             //date: YYYY/mm/dd HH:mm:ss; author: name
268             //or date: YYYY-mm-dd HH:mm:ss Z; author: name
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             //try other format
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             // add entry, and set state to get revision
326             addEntry( getCurrentLogEntry(), getCurrentFile() );
327 
328             // new change log entry
329             setCurrentLogEntry( new ChangeLogEntry() );
330 
331             // same file name, but different rev
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             // keep gathering comments
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 }