1 package org.apache.maven.svnlib;
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.ArrayList;
30 import java.util.Collection;
31 import java.util.Date;
32
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37
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 import org.apache.regexp.RE;
45 import org.apache.regexp.RESyntaxException;
46
47
48 /**
49 * A class to parse the log output from Subversion.
50 *
51 * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
52 * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
53 * @author Daniel Rall
54 * @version $Id: SvnChangeLogParser.java 532339 2007-04-25 12:28:56Z ltheussl $
55 */
56 public class SvnChangeLogParser implements ChangeLogParser
57 {
58 /** Date formatter for svn timestamp (after a little massaging) */
59 private static final SimpleDateFormat SVN_TIMESTAMP =
60 new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss zzzzzzzzz" );
61
62 /** Log */
63 private static final Log LOG =
64 LogFactory.getLog( SvnChangeLogParser.class );
65
66 /** State machine constant: expecting header */
67 private static final int GET_HEADER = 1;
68
69 /** State machine constant: expecting file information */
70 private static final int GET_FILE = 2;
71
72 /** State machine constant: expecting comments */
73 private static final int GET_COMMENT = 3;
74
75 /** A file line begins with a space character */
76 private static final String FILE_BEGIN_TOKEN = " ";
77
78 /** The file section ends with a blank line */
79 private static final String FILE_END_TOKEN = "";
80
81 /** The filename starts after 5 characters */
82 private static final int FILE_START_INDEX = 5;
83
84 /** The comment section ends with a dashed line */
85 private static final String COMMENT_END_TOKEN =
86 "------------------------------------"
87 + "------------------------------------";
88
89 /** The pattern used to match svn header lines */
90 private static final String pattern =
91 "^r(\\d+)\\s+\\|\\s+"
92 + "([^|]+)\\|\\s+"
93 + "(\\d+-\\d+-\\d+ "
94 + "\\d+:\\d+:\\d+) "
95 + "([\\-+])(\\d\\d)(\\d\\d)";
96
97 /** Current status of the parser */
98 private int status = GET_HEADER;
99
100 /** List of change log entries */
101 private Collection entries = new ArrayList();
102
103 /** The current log entry being processed by the parser */
104 private ChangeLogEntry currentLogEntry;
105
106 /** The current revision of the entry being processed by the parser */
107 private String currentRevision;
108
109 /** The current comment of the entry being processed by the parser */
110 private StringBuffer currentComment;
111
112 /** The regular expression used to match header lines */
113 private RE headerRegexp;
114
115 /** The invoking changelog controller (useful to log messages) */
116 private ChangeLog changeLog;
117
118 /**
119 * Default constructor.
120 */
121 public SvnChangeLogParser()
122 {
123 try
124 {
125 headerRegexp = new RE( pattern );
126 }
127 catch ( RESyntaxException ignored )
128 {
129 LOG.error( "Could not create regexp to parse svn log file", ignored );
130 }
131 }
132
133 /**
134 * Initialize the parser from the change log.
135 *
136 * @param changeLog The controlling task
137 * @see ChangeLogParser#init(ChangeLog)
138 */
139 public void init( ChangeLog changeLog )
140 {
141 this.changeLog = changeLog;
142 }
143
144 /**
145 * Clean up any parser resources.
146 *
147 * @see ChangeLogParser#cleanup()
148 */
149 public void cleanup()
150 {
151 }
152
153 /**
154 * Parse the input stream into a collection.
155 *
156 * @param anInputStream An input stream containing svn log output
157 * @return A collection of ChangeLogEntry's
158 * @throws IOException When there are errors reading the provided stream
159 */
160 public Collection parse( InputStream anInputStream )
161 throws IOException
162 {
163 BufferedReader stream =
164 new BufferedReader( new InputStreamReader( anInputStream ) );
165
166
167
168
169
170 String line = null;
171
172 while ( ( line = stream.readLine() ) != null )
173 {
174 switch ( status )
175 {
176 case GET_HEADER :
177 processGetHeader( line );
178
179 break;
180
181 case GET_FILE :
182 processGetFile( line );
183
184 break;
185
186 case GET_COMMENT :
187 processGetComment( line );
188
189 break;
190
191 default :
192 throw new IllegalStateException( "Unknown state: " + status );
193 }
194 }
195
196 return entries;
197 }
198
199 /**
200 * Process the current input line in the GET_HEADER state. The
201 * author, date, and the revision of the entry are gathered. Note,
202 * Subversion does not have per-file revisions, instead, the entire
203 * repository is given a single revision number, which is used for
204 * the revision number of each file.
205 *
206 * @param line A line of text from the svn log output
207 */
208 private void processGetHeader( String line )
209 {
210 if ( !headerRegexp.match( line ) )
211 {
212 return;
213 }
214
215 currentRevision = headerRegexp.getParen( 1 );
216 currentLogEntry = new ChangeLogEntry();
217
218
219 currentLogEntry.setAuthor( headerRegexp.getParen( 2 ).trim() );
220 currentLogEntry.setDate( parseDate() );
221
222 status = GET_FILE;
223 }
224
225 /**
226 * Process the current input line in the GET_FILE state. This state
227 * adds each file entry line to the current change log entry. Note,
228 * the revision number for the entire entry is used for the revision
229 * number of each file.
230 *
231 * @param line A line of text from the svn log output
232 */
233 private void processGetFile( String line )
234 {
235 if ( line.startsWith( FILE_BEGIN_TOKEN ) )
236 {
237
238 String name = line.substring( FILE_START_INDEX );
239
240 currentLogEntry.addFile( new ChangeLogFile( name, currentRevision ) );
241
242 status = GET_FILE;
243 }
244 else if ( line.equals( FILE_END_TOKEN ) )
245 {
246
247
248 currentComment = new StringBuffer();
249
250 status = GET_COMMENT;
251 }
252 }
253
254 /**
255 * Process the current input line in the GET_COMMENT state. This
256 * state gathers all of the comments that are part of a log entry.
257 *
258 * @param line a line of text from the svn log output
259 */
260 private void processGetComment( String line )
261 {
262 if ( line.equals( COMMENT_END_TOKEN ) )
263 {
264 currentLogEntry.setComment( currentComment.toString() );
265 entries.add( currentLogEntry );
266
267 status = GET_HEADER;
268 }
269 else
270 {
271 currentComment.append( line ).append( '\n' );
272 }
273 }
274
275 /**
276 * Converts the date timestamp from the svn output into a date
277 * object.
278 *
279 * @return A date representing the timestamp of the log entry.
280 */
281 private Date parseDate()
282 {
283 try
284 {
285 StringBuffer date =
286 new StringBuffer().append( headerRegexp.getParen( 3 ) )
287 .append( " GMT" )
288 .append( headerRegexp.getParen( 4 ) )
289 .append( headerRegexp.getParen( 5 ) )
290 .append( ':' ).append( headerRegexp
291 .getParen( 6 ) );
292
293 return SVN_TIMESTAMP.parse( date.toString() );
294 }
295 catch ( ParseException e )
296 {
297 LOG.error( "ParseException Caught", e );
298
299 return null;
300 }
301 }
302
303 public void setDateFormatInFile( String dateFormat )
304 {
305 }
306 }