1 package org.apache.maven.changelog;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29
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.util.AsyncStreamReader;
39 import org.apache.tools.ant.taskdefs.Execute;
40
41
42 import org.apache.tools.ant.taskdefs.ExecuteStreamHandler;
43 import org.apache.tools.ant.types.Commandline;
44
45 /**
46 * An abstract implementation of the {@link org.apache.maven.changelog.ChangeLog}
47 * interface.
48 *
49 * @author Glenn McAllister
50 * @author <a href="mailto:jeff.martin@synamic.co.uk">Jeff Martin</a>
51 * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
52 * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
53 * @author <a href="mailto:bodewig@apache.org">Stefan Bodewig</a>
54 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
55 * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
56 * @version
57 * $Id: AbstractChangeLogGenerator.java 532339 2007-04-25 12:28:56Z ltheussl $
58 */
59 public abstract class AbstractChangeLogGenerator implements ChangeLogGenerator,
60 ExecuteStreamHandler
61 {
62 /** Log */
63 private static final Log LOG =
64 LogFactory.getLog( AbstractChangeLogGenerator.class );
65
66 /**
67 * The working directory.
68 */
69 protected File base;
70
71 /**
72 * Reference to the enclosing ChangeLog instance - used to obtain
73 * any necessary configuration information.
74 */
75 protected ChangeLog changeLogExecutor;
76
77 /**
78 * The parser that takes the log output and transforms it into a
79 * collection of ChangeLogEntry's.
80 */
81 protected ChangeLogParser clParser;
82
83 /** The connection string from the project */
84 private String connection;
85
86 /** The log type (range, date, or tag). */
87 protected String type;
88
89 /**
90 * The date range command line argument.
91 */
92 protected String dateRange;
93
94 /** The tag command line argument. */
95 protected String tag;
96
97 /** Represents when this log starts (for the report). */
98 protected String logStart = "";
99
100 /** Represents when this log ends (for the report). */
101 protected String logEnd = "";
102
103 /**
104 * The collection of ChangeLogEntry's returned from clParser.
105 */
106 protected Collection entries;
107
108 /**
109 * Stderr stream eater.
110 */
111 protected AsyncStreamReader errorReader;
112
113 /**
114 * The scm process input stream.
115 */
116 protected InputStream in;
117
118 /**
119 * The comment format string used in interrogating the RCS.
120 */
121 protected String commentFormat;
122
123 /**
124 * Initialize the generator from the changelog controller.
125 *
126 * @param changeLog The invoking controller (useful for logging)
127 * @see ChangeLogGenerator#init(ChangeLog)
128 */
129 public void init( ChangeLog changeLog )
130 {
131 changeLogExecutor = changeLog;
132
133 base = changeLogExecutor.getBasedir();
134
135 type = changeLogExecutor.getType();
136
137 if ( type.equalsIgnoreCase( "tag" ) )
138 {
139 tag = getScmTagArgument( changeLogExecutor.getMarkerStart(),
140 changeLogExecutor.getMarkerEnd() );
141 logStart = changeLogExecutor.getMarkerStart();
142 logEnd =
143 ( changeLogExecutor.getMarkerEnd() == null ) ? ""
144 : changeLogExecutor
145 .getMarkerEnd();
146 }
147 else if ( type.equalsIgnoreCase( "date" ) )
148 {
149 setDateRangeFromAbsoluteDate( changeLogExecutor.getMarkerStart(),
150 changeLogExecutor.getMarkerEnd() );
151 }
152 else
153 {
154
155
156
157
158 if ( ( changeLogExecutor.getRange() != null )
159 && ( changeLogExecutor.getRange().length() != 0 ) )
160 {
161 setDateRange( changeLogExecutor.getRange() );
162 }
163 }
164
165 setConnection( changeLogExecutor.getRepositoryConnection() );
166
167
168 setCommentFormat( changeLogExecutor.getCommentFormat() );
169 }
170
171 /**
172 * Set the dateRange member based on the number of days obtained
173 * from the ChangeLog.
174 *
175 * @param numDaysString The number of days of log output to
176 * generate.
177 */
178 protected void setDateRange( String numDaysString )
179 {
180 int days = Integer.parseInt( numDaysString );
181
182 Date before =
183 new Date( System.currentTimeMillis()
184 - ( (long) days * 24 * 60 * 60 * 1000 ) );
185 Date to =
186 new Date( System.currentTimeMillis()
187 + ( (long) 1 * 24 * 60 * 60 * 1000 ) );
188
189 dateRange = getScmDateArgument( before, to );
190 setLogStart( before );
191 setLogEnd( to );
192 }
193
194 /**
195 * Set the dateRange member based on an absolute date.
196 * @param startDate The start date for the range.
197 * @param endDate The end date for the range, or <code>null</code> to use the present time.
198 */
199 protected void setDateRangeFromAbsoluteDate( String startDate,
200 String endDate )
201 {
202 String dateFormat = changeLogExecutor.getDateFormat();
203 SimpleDateFormat format =
204 ( dateFormat == null ) ? new SimpleDateFormat( "yyyy-MM-dd" )
205 : new SimpleDateFormat( dateFormat );
206
207 Date before;
208
209 try
210 {
211 before = format.parse( startDate );
212 }
213 catch ( ParseException ex )
214 {
215 throw new IllegalArgumentException( "Unable to parse start date "
216 + startDate + ": " + ex.getLocalizedMessage() );
217 }
218
219 Date to;
220
221 try
222 {
223 to = ( endDate != null ) ? format.parse( endDate )
224 : new Date( System.currentTimeMillis()
225 + ( (long) 1 * 24 * 60 * 60 * 1000 ) );
226 }
227 catch ( ParseException ex )
228 {
229 throw new IllegalArgumentException( "Unable to parse end date "
230 + endDate + ": " + ex.getLocalizedMessage() );
231 }
232
233 dateRange = getScmDateArgument( before, to );
234 setLogStart( before );
235 setLogEnd( to );
236 }
237
238 /**
239 * Sets the log start string based on the given date.
240 * This uses the date format supplied in the plugin properties.
241 *
242 * @param start date the log started.
243 */
244 protected void setLogStart( Date start )
245 {
246 String dateFormat = changeLogExecutor.getDateFormat();
247 SimpleDateFormat format =
248 ( dateFormat == null ) ? new SimpleDateFormat( "yyyy-MM-dd" )
249 : new SimpleDateFormat( dateFormat );
250
251 logStart = format.format( start );
252 }
253
254 /**
255 * Sets the log end string based on the given date.
256 * This uses the date format supplied in the plugin properties.
257 *
258 * @param end date the log ended.
259 */
260 protected void setLogEnd( Date end )
261 {
262 String dateFormat = changeLogExecutor.getDateFormat();
263 SimpleDateFormat format =
264 ( dateFormat == null ) ? new SimpleDateFormat( "yyyy-MM-dd" )
265 : new SimpleDateFormat( dateFormat );
266
267 logEnd = format.format( end );
268 }
269
270 /**
271 * Execute scm client driving the given parser.
272 *
273 * @param parser A {@link ChangeLogParser parser} to process the scm
274 * output.
275 * @return A collection of {@link ChangeLogEntry entries} parsed from
276 * the scm output.
277 * @throws IOException When there are issues executing scm.
278 * @see ChangeLogGenerator#getEntries(ChangeLogParser)
279 */
280 public Collection getEntries( ChangeLogParser parser )
281 throws IOException
282 {
283 if ( parser == null )
284 {
285 throw new NullPointerException( "parser cannot be null" );
286 }
287
288 if ( base == null )
289 {
290 throw new NullPointerException( "basedir must be set" );
291 }
292
293 if ( !base.exists() )
294 {
295 throw new FileNotFoundException( "Cannot find base dir "
296 + base.getAbsolutePath() );
297 }
298
299 clParser = parser;
300
301 try
302 {
303 Execute exe = new Execute( this );
304
305 exe.setCommandline( getScmLogCommand().getCommandline() );
306 exe.setWorkingDirectory( base );
307 logExecute( exe, base );
308
309 exe.execute();
310
311
312 String errors = errorReader.toString().trim();
313
314 if ( errors.length() > 0 )
315 {
316 LOG.error( errors );
317 }
318 }
319 catch ( IOException ioe )
320 {
321 handleParserException( ioe );
322 }
323
324 return entries;
325 }
326
327 /**
328 * Handle ChangeLogParser IOExceptions. The default implementation
329 * just throws the exception again.
330 *
331 * @param ioe The IOException thrown.
332 * @throws IOException If the handler doesn't wish to handle the
333 * exception (the default behavior).
334 */
335 protected void handleParserException( IOException ioe )
336 throws IOException
337 {
338 throw ioe;
339 }
340
341 /**
342 * @see ChangeLogGenerator#getLogStart()
343 */
344 public String getLogStart()
345 {
346 return logStart;
347 }
348
349 /**
350 * @see ChangeLogGenerator#getLogEnd()
351 */
352 public String getLogEnd()
353 {
354
355 return logEnd;
356 }
357
358 /**
359 * Clean up any generated resources for this run.
360 *
361 * @see ChangeLogGenerator#cleanup()
362 */
363 public void cleanup()
364 {
365 }
366
367 /**
368 * Constructs the appropriate command line to execute the scm's
369 * log command. This method must be implemented by subclasses.
370 *
371 * @return The command line to be executed.
372 */
373 protected abstract Commandline getScmLogCommand();
374
375 /**
376 * Construct the command-line argument that is passed to the scm
377 * client to specify the appropriate date range.
378 *
379 * @param before The starting point.
380 * @param to The ending point.
381 * @return A string that can be used to specify a date to a scm
382 * system.
383 */
384 protected abstract String getScmDateArgument( Date before, Date to );
385
386 /**
387 * Construct the command-line argument that is passed to the scm
388 * client to specify the appropriate tag.
389 *
390 * @param tagStart The tag name for the start of the log (log shouldn't actually contain the tag).
391 * @param tagEnd The tag name for the end of the log (the log can contain this tag), or <code>null</code> to
392 * log all changes since <code>tagStart</code>.
393 * @return A string that can be used to specify the tag range to a scm system.
394 */
395 protected abstract String getScmTagArgument( String tagStart, String tagEnd );
396
397 /**
398 * Stop the process - currently unimplemented
399 */
400 public void stop()
401 {
402 }
403
404 /**
405 * Set the input stream for the scm process.
406 * @param os An {@link java.io.OutputStream}
407 */
408 public void setProcessInputStream( OutputStream os )
409 {
410 }
411
412 /**
413 * Set the error stream for reading from scm log. This stream will
414 * be read on a separate thread.
415 *
416 * @param is An {@link java.io.InputStream}
417 */
418 public void setProcessErrorStream( InputStream is )
419 {
420 errorReader = new AsyncStreamReader( is );
421 }
422
423 /**
424 * Set the input stream used to read from scm log.
425 *
426 * @param is A stream of scm log output to be read from
427 */
428 public void setProcessOutputStream( InputStream is )
429 {
430 in = is;
431 }
432
433 /**
434 * Start read from the scm log.
435 *
436 * @throws IOException When there are errors reading from the
437 * streams previously provided
438 */
439 public void start() throws IOException
440 {
441 errorReader.start();
442 entries = clParser.parse( in );
443 }
444
445 /**
446 * Returns the connection.
447 * @return String
448 */
449 public String getConnection()
450 {
451 return connection;
452 }
453
454 /**
455 * Sets the connection.
456 * @param connection The connection to set
457 */
458 public void setConnection( String connection )
459 {
460 this.connection = connection;
461 }
462
463 /**
464 * Returns the commentFormat used to interrogate the RCS.
465 * @return String
466 */
467 public String getCommentFormat()
468 {
469 return commentFormat;
470 }
471
472 /**
473 * Sets the commentFormat.
474 * @param commentFormat The commentFormat to set
475 */
476 public void setCommentFormat( String commentFormat )
477 {
478 this.commentFormat = commentFormat;
479 }
480
481 /**
482 * Logs the pertinent details to the logging system (info level)
483 * @param exe The object to log
484 * @param base The working directory
485 */
486 public static void logExecute( Execute exe, File base )
487 {
488 String[] c = exe.getCommandline();
489
490 LOG.info( "SCM Working Directory: " + base );
491
492 for ( int i = 0; i < c.length; i++ )
493 {
494 String string = c[i];
495
496 LOG.info( "SCM Command Line[" + i + "]: " + string );
497 }
498 }
499 }