View Javadoc

1   package org.apache.maven.perforcelib;
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  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.maven.changelog.ChangeLog;
38  import org.apache.maven.changelog.ChangeLogEntry;
39  import org.apache.maven.changelog.ChangeLogFile;
40  import org.apache.maven.changelog.ChangeLogParser;
41  import org.apache.regexp.RE;
42  import org.apache.regexp.RESyntaxException;
43  
44  
45  /**
46   * A class to parse the log output from the Perforce 'filelog'
47   * command.
48   *
49   * @author <a href="mailto:jim@crossleys.org">Jim Crossley</a>
50   * @version $Id:
51   */
52  public class PerforceChangeLogParser implements ChangeLogParser
53  {
54      /** Date formatter for perforce timestamp */
55      private static final SimpleDateFormat PERFORCE_TIMESTAMP =
56          new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" );
57  
58      /** Log */
59      private static final Log LOG =
60          LogFactory.getLog( PerforceChangeLogParser.class );
61  
62      /** State machine constant: expecting revision and/or file information */
63      private static final int GET_REVISION = 1;
64  
65      /** State machine constant: eat the first blank line */
66      private static final int GET_COMMENT_BEGIN = 2;
67  
68      /** State machine constant: expecting comments */
69      private static final int GET_COMMENT = 3;
70  
71      /** The comment section ends with a blank line */
72      private static final String COMMENT_DELIMITER = "";
73  
74      /** A file line begins with two slashes */
75      private static final String FILE_BEGIN_TOKEN = "//";
76      private static final String pattern =
77          "^\\.\\.\\. #(\\d+) " // revision number
78          + "change (\\d+) .* " // changelist number
79          + "on (.*) " // date 
80          + "by (.*)@"; // author
81  
82      /**
83       * RCS entries, in reverse changelist number order
84       */
85      private Map entries = new TreeMap( Collections.reverseOrder() );
86  
87      /** Current status of the parser */
88      private int status = GET_REVISION;
89  
90      /** The current log entry being processed by the parser */
91      private ChangeLogEntry currentLogEntry;
92  
93      /** the current file being processed by the parser */
94      private String currentFile;
95  
96      /** The regular expression used to match header lines */
97      private RE revisionRegexp;
98  
99      /** The invoking changelog controller (useful to log messages) */
100     private ChangeLog changeLog;
101 
102     /** the before date */
103     private Date beforeDate;
104 
105     /** The depot filespec will be stripped from each filename */
106     private int prefixLength;
107 
108     /**
109      * Default constructor.
110      */
111     public PerforceChangeLogParser()
112     {
113         try
114         {
115             revisionRegexp = new RE( pattern );
116         }
117         catch ( RESyntaxException ignored )
118         {
119             LOG.error( "Could not create regexp to parse perforce log file",
120                 ignored );
121         }
122     }
123 
124     /**
125      * Initialize the parser from the change log.
126      *
127      * @param changeLog The controlling task
128      * @see ChangeLogParser#init(ChangeLog)
129      */
130     public void init( ChangeLog changeLog )
131     {
132         this.changeLog = changeLog;
133         setDateRange();
134 
135         String conn = changeLog.getRepositoryConnection();
136         String filespec = conn.substring( conn.lastIndexOf( ':' ) + 1 );
137 
138         this.prefixLength = 1 + filespec.lastIndexOf( '/' );
139     }
140 
141     /**
142      * Clean up any parser resources.
143      *
144      * @see ChangeLogParser#cleanup()
145      */
146     public void cleanup()
147     {
148     }
149 
150     /**
151      * Parse the input stream into a collection of entries
152      *
153      * @param anInputStream An input stream containing perforce log output
154      * @return A collection of ChangeLogEntry's
155      * @throws IOException When there are errors reading the provided stream
156      */
157     public Collection parse( InputStream anInputStream )
158         throws IOException
159     {
160         BufferedReader stream =
161             new BufferedReader( new InputStreamReader( anInputStream ) );
162 
163         String line = null;
164 
165         while ( ( line = stream.readLine() ) != null )
166         {
167             switch ( status )
168             {
169             case GET_REVISION :
170                 processGetRevision( line );
171 
172                 break;
173 
174             case GET_COMMENT_BEGIN :
175                 status = GET_COMMENT;
176 
177                 break;
178 
179             case GET_COMMENT :
180                 processGetComment( line );
181 
182                 break;
183 
184             default :
185                 throw new IllegalStateException( "Unknown state: " + status );
186             }
187         }
188 
189         return entries.values();
190     }
191 
192     /**
193      * Add a change log entry to the list (if it's not already there)
194      * with the given file.
195      * @param entry a {@link ChangeLogEntry} to be added to the list if another
196      *      with the same key (p4 change number) doesn't exist already.
197      * @param file a {@link ChangeLogFile} to be added to the entry
198      */
199     private void addEntry( ChangeLogEntry entry, ChangeLogFile file )
200     {
201         if ( beforeDate != null )
202         {
203             if ( entry.getDate().before( beforeDate ) )
204             {
205                 return;
206             }
207         }
208 
209         Integer key = new Integer( revisionRegexp.getParen( 2 ) );
210 
211         if ( !entries.containsKey( key ) )
212         {
213             entry.addFile( file );
214             entries.put( key, entry );
215         }
216         else
217         {
218             ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get( key );
219 
220             existingEntry.addFile( file );
221         }
222     }
223 
224     /**
225      * Most of the relevant info is on the revision line matching the
226      * 'pattern' string.
227      *
228      * @param line A line of text from the perforce log output
229      */
230     private void processGetRevision( String line )
231     {
232         if ( line.startsWith( FILE_BEGIN_TOKEN ) )
233         {
234             currentFile = line.substring( this.prefixLength );
235 
236             return;
237         }
238 
239         if ( !revisionRegexp.match( line ) )
240         {
241             return;
242         }
243 
244         currentLogEntry = new ChangeLogEntry();
245         currentLogEntry.setDate( parseDate( revisionRegexp.getParen( 3 ) ) );
246         currentLogEntry.setAuthor( revisionRegexp.getParen( 4 ) );
247 
248         status = GET_COMMENT_BEGIN;
249     }
250 
251     /**
252      * Process the current input line in the GET_COMMENT state.  This
253      * state gathers all of the comments that are part of a log entry.
254      *
255      * @param line a line of text from the perforce log output
256      */
257     private void processGetComment( String line )
258     {
259         if ( line.equals( COMMENT_DELIMITER ) )
260         {
261             addEntry( currentLogEntry,
262                 new ChangeLogFile( currentFile, revisionRegexp.getParen( 1 ) ) );
263             status = GET_REVISION;
264         }
265         else
266         {
267             currentLogEntry.setComment( currentLogEntry.getComment() + line
268                 + "\n" );
269         }
270     }
271 
272     /**
273      * Converts the date timestamp from the perforce output into a date
274      * object.
275      *
276      * @return A date representing the timestamp of the log entry.
277      */
278     private Date parseDate( String date )
279     {
280         try
281         {
282             return PERFORCE_TIMESTAMP.parse( date );
283         }
284         catch ( ParseException e )
285         {
286             LOG.error( "ParseException Caught", e );
287 
288             return null;
289         }
290     }
291 
292     /**
293      * Set the beforeDate member based on the number of days obtained
294      * from the ChangeLog.
295      *
296      * @param numDaysString The number of days of log output to
297      * generate.
298      */
299     private void setDateRange()
300     {
301         if ( ( this.changeLog == null )
302             || ( this.changeLog.getRange() == null )
303             || ( this.changeLog.getRange().length() == 0 ) )
304         {
305             return;
306         }
307 
308         int days = Integer.parseInt( this.changeLog.getRange() );
309 
310         beforeDate =
311             new Date( System.currentTimeMillis()
312                 - ( (long) days * 24 * 60 * 60 * 1000 ) );
313     }
314 
315     public void setDateFormatInFile( String dateFormat )
316     {
317     }
318 }