View Javadoc

1   package org.apache.maven.starteamlib;
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.TreeMap;
34  
35  // commons imports
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.maven.changelog.ChangeLog;
39  import org.apache.maven.changelog.ChangeLogEntry;
40  import org.apache.maven.changelog.ChangeLogFile;
41  import org.apache.maven.changelog.ChangeLogParser;
42  
43  
44  /**
45   * A class to parse starteam log output
46   *
47   * @author <a href="mailto:evenisse@ifrance">Emmanuel Venisse</a>
48   * @version $Id: StarteamChangeLogParser.java 532339 2007-04-25 12:28:56Z ltheussl $
49   */
50  public class StarteamChangeLogParser implements ChangeLogParser
51  {
52      /**
53       * Custom date/time formatter.  Rounds ChangeLogEntry times to the nearest
54       * minute.
55       */
56      private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT =
57          new SimpleDateFormat( "yyyyMMddHHmm" );
58  
59      /** Log */
60      private static final Log LOG =
61          LogFactory.getLog( StarteamChangeLogGenerator.class );
62  
63      // state machine constants for reading Starteam output
64  
65      /** expecting file information */
66      private static final int GET_FILE = 1;
67  
68      /** expecting date */
69      private static final int GET_AUTHOR = 2;
70  
71      /** expecting comments */
72      private static final int GET_COMMENT = 3;
73  
74      /** expecting revision */
75      private static final int GET_REVISION = 4;
76  
77      /** Marks start of file data*/
78      private static final String START_FILE = "History for: ";
79  
80      /** Marks end of file */
81      private static final String END_FILE =
82          "==================================="
83          + "==========================================";
84  
85      /** Marks start of revision */
86      private static final String START_REVISION = "----------------------------";
87  
88      /** Marks revision data */
89      private static final String REVISION_TAG = "Branch Revision: ";
90  
91      /** Marks author data */
92      private static final String AUTHOR_TAG = "Author: ";
93  
94      /** Marks date data */
95      private static final String DATE_TAG = " Date: ";
96      private SimpleDateFormat localFormat = new SimpleDateFormat();
97  
98      /**
99       * rcs entries, in reverse (date, time, author, comment) order
100      */
101     private Map entries = new TreeMap( Collections.reverseOrder() );
102 
103     /** current status of the parser */
104     private int status = GET_FILE;
105 
106     /** the current log entry being processed by the parser*/
107     private ChangeLogEntry currentLogEntry = null;
108 
109     /** the current file being processed by the parser */
110     private ChangeLogFile currentFile = null;
111 
112     /** the controlling task */
113     private ChangeLog changeLog = null;
114 
115     /** the before date */
116     private Date beforeDate;
117 
118     /** the to date */
119     private Date toDate;
120 
121     /** the test mode (In test mode, we dont't use the date range*/
122     private boolean testMode = false;
123 
124     /**
125      * Create a new ChangeLogParser.
126      */
127     public StarteamChangeLogParser()
128     {
129     }
130 
131     /**
132      * initialize the parser from the change log
133      * @param changeLog the controlling task
134      * @see ChangeLogParser#init(ChangeLog)
135      */
136     public void init( ChangeLog changeLog )
137     {
138         this.changeLog = changeLog;
139 
140         if ( ( changeLog.getRange() != null )
141             && ( changeLog.getRange().length() != 0 ) )
142         {
143             setDateRange( changeLog.getRange() );
144         }
145 
146         if ( changeLog.getDateFormat() != null )
147         {
148             localFormat = new SimpleDateFormat( changeLog.getDateFormat() );
149         }
150     }
151 
152     /**
153      * Clean up any parser resources
154      * @see ChangeLogParser#cleanup()
155      */
156     public void cleanup()
157     {
158     }
159 
160     /**
161      * Use only with StarteamChangeLogParserTest class
162      */
163     public void setTestMode( boolean testMode )
164     {
165         this.testMode = testMode;
166     }
167 
168     /**
169      * Set the date formatter for parse starteam stream
170      * @param aSimpleDateFormat a simpleDateFormat for replace the local format
171      */
172     public void setDateFormatInFile( String dateFormat )
173     {
174         localFormat = new SimpleDateFormat( dateFormat );
175     }
176 
177     /**
178      * Parse the input stream into a collection.
179      * @param anInputStream an input stream containing Starteam log output
180      * @return a collection of ChangeLogEntry's
181      * @throws IOException when there are errors reading the provided stream
182      */
183     public Collection parse( InputStream anInputStream )
184         throws IOException
185     {
186         BufferedReader stream =
187             new BufferedReader( new InputStreamReader( anInputStream ) );
188 
189         // current state transitions in the state machine - starts with Get File
190         //      Get File                -> Get Revision
191         //      Get Revision            -> Get Date or Get File
192         //      Get Date                -> Get Comment
193         //      Get Comment             -> Get Comment or Get Revision
194         String line = null;
195 
196         while ( ( line = stream.readLine() ) != null )
197         {
198             switch ( getStatus() )
199             {
200             case GET_FILE :
201                 processGetFile( line );
202 
203                 break;
204 
205             case GET_REVISION :
206                 processGetRevision( line );
207 
208                 break;
209 
210             case GET_AUTHOR :
211                 processGetAuthor( line );
212 
213                 break;
214 
215             case GET_COMMENT :
216                 processGetComment( line );
217 
218                 break;
219 
220             default :
221                 throw new IllegalStateException( "Unknown state: " + status );
222             }
223         }
224 
225         return entries.values();
226     }
227 
228     /**
229      * Add a change log entry to the list (if it's not already there)
230      * with the given file.
231      * @param entry a {@link ChangeLogEntry} to be added to the list if another
232      *      with the same key doesn't exist already. If the entry's author
233      *      is null, the entry wont be added
234      * @param file a {@link ChangeLogFile} to be added to the entry
235      */
236     private void addEntry( ChangeLogEntry entry, ChangeLogFile file )
237     {
238         // do not add if entry is not populated
239         if ( entry.getAuthor() == null )
240         {
241             return;
242         }
243 
244         // do not add if entry is out of date range
245         if ( !testMode )
246         {
247             if ( ( beforeDate != null ) && ( toDate != null ) )
248             {
249                 if ( entry.getDate().before( beforeDate )
250                     || entry.getDate().after( toDate ) )
251                 {
252                     return;
253                 }
254             }
255         }
256 
257         String key =
258             ENTRY_KEY_TIMESTAMP_FORMAT.format( entry.getDate() )
259             + entry.getAuthor() + entry.getComment();
260 
261         if ( !entries.containsKey( key ) )
262         {
263             entry.addFile( file );
264             entries.put( key, entry );
265         }
266         else
267         {
268             ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get( key );
269 
270             existingEntry.addFile( file );
271         }
272     }
273 
274     /**
275      * Process the current input line in the Get File state.
276      * @param line a line of text from the Starteam log output
277      */
278     private void processGetFile( String line )
279     {
280         if ( line.startsWith( START_FILE ) )
281         {
282             setCurrentLogEntry( new ChangeLogEntry() );
283             setCurrentFile( new ChangeLogFile( line.substring( 
284                         START_FILE.length(), line.length() ) ) );
285             setStatus( GET_REVISION );
286         }
287     }
288 
289     /**
290      * Process the current input line in the Get Revision state.
291      * @param line a line of text from the Starteam log output
292      */
293     private void processGetRevision( String line )
294     {
295         int pos;
296 
297         if ( ( pos = line.indexOf( REVISION_TAG ) ) != -1 )
298         {
299             getCurrentFile().setRevision( line.substring( pos
300                     + REVISION_TAG.length() ) );
301             setStatus( GET_AUTHOR );
302         }
303         else if ( line.startsWith( END_FILE ) )
304         {
305             // If we encounter an end of file line, it means there 
306             // are no more revisions for the current file.
307             // there could also be a file still being processed.
308             setStatus( GET_FILE );
309             addEntry( getCurrentLogEntry(), getCurrentFile() );
310         }
311     }
312 
313     /**
314      * Process the current input line in the Get Author/Date state.
315      * @param line a line of text from the Starteam log output
316      */
317     private void processGetAuthor( String line )
318     {
319         if ( line.startsWith( AUTHOR_TAG ) )
320         {
321             int posDateTag = line.indexOf( DATE_TAG );
322             String author = line.substring( AUTHOR_TAG.length(), posDateTag );
323 
324             getCurrentLogEntry().setAuthor( author );
325 
326             String date = line.substring( posDateTag + DATE_TAG.length() );
327 
328             getCurrentLogEntry().setDate( parseDate( date ) );
329             setStatus( GET_COMMENT );
330         }
331     }
332 
333     /**
334      * Process the current input line in the Get Comment state.
335      * @param line a line of text from the Starteam log output
336      */
337     private void processGetComment( String line )
338     {
339         if ( line.startsWith( START_REVISION ) )
340         {
341             // add entry, and set state to get revision
342             addEntry( getCurrentLogEntry(), getCurrentFile() );
343 
344             // new change log entry
345             setCurrentLogEntry( new ChangeLogEntry() );
346 
347             // same file name, but different rev
348             setCurrentFile( new ChangeLogFile( getCurrentFile().getName() ) );
349             setStatus( GET_REVISION );
350         }
351         else if ( line.startsWith( END_FILE ) )
352         {
353             addEntry( getCurrentLogEntry(), getCurrentFile() );
354             setStatus( GET_FILE );
355         }
356         else
357         {
358             // keep gathering comments
359             getCurrentLogEntry().setComment( getCurrentLogEntry()
360                                                    .getComment() + line
361                 + "\n" );
362         }
363     }
364 
365     /**
366      * Getter for property currentFile.
367      * @return Value of property currentFile.
368      */
369     private ChangeLogFile getCurrentFile()
370     {
371         return currentFile;
372     }
373 
374     /**
375      * Setter for property currentFile.
376      * @param currentFile New value of property currentFile.
377      */
378     private void setCurrentFile( ChangeLogFile currentFile )
379     {
380         this.currentFile = currentFile;
381     }
382 
383     /**
384      * Getter for property currentLogEntry.
385      * @return Value of property currentLogEntry.
386      */
387     private ChangeLogEntry getCurrentLogEntry()
388     {
389         return currentLogEntry;
390     }
391 
392     /**
393      * Setter for property currentLogEntry.
394      * @param currentLogEntry New value of property currentLogEntry.
395      */
396     private void setCurrentLogEntry( ChangeLogEntry currentLogEntry )
397     {
398         this.currentLogEntry = currentLogEntry;
399     }
400 
401     /**
402      * Getter for property status.
403      * @return Value of property status.
404      */
405     private int getStatus()
406     {
407         return status;
408     }
409 
410     /**
411      * Setter for property status.
412      * @param status New value of property status.
413      */
414     private void setStatus( int status )
415     {
416         this.status = status;
417     }
418 
419     /**
420      * Converts the date timestamp from the svn output into a date
421      * object.
422      *
423      * @return A date representing the timestamp of the log entry.
424      */
425     private Date parseDate( String date )
426     {
427         try
428         {
429             return localFormat.parse( date.toString() );
430         }
431         catch ( ParseException e )
432         {
433             LOG.error( "ParseException Caught", e );
434 
435             return null;
436         }
437     }
438 
439     /**
440      * Set the beforeDate and toDate member based on the number of days
441      * obtained from the ChangeLog.
442      *
443      * @param numDaysString The number of days of log output to
444      * generate.
445      */
446     private void setDateRange( String numDaysString )
447     {
448         int days = Integer.parseInt( numDaysString );
449 
450         beforeDate =
451             new Date( System.currentTimeMillis()
452                 - ( (long) days * 24 * 60 * 60 * 1000 ) );
453         toDate =
454             new Date( System.currentTimeMillis()
455                 + ( (long) 1 * 24 * 60 * 60 * 1000 ) );
456     }
457 }