View Javadoc

1   package org.apache.maven.changelog;
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.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  // commons imports
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  // maven imports
38  import org.apache.maven.util.AsyncStreamReader;
39  import org.apache.tools.ant.taskdefs.Execute;
40  
41  // ant imports
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 // type == "range" (the default)
153         {
154             // This lets the user 'not' set a limit on the log command.  We
155             // need this cuz Subversion doesn't currently support date
156             // commands on web-based repositories, so it would be nice to
157             // let the user still use the changelog plugin.
158             if ( ( changeLogExecutor.getRange() != null )
159                 && ( changeLogExecutor.getRange().length() != 0 ) )
160             {
161                 setDateRange( changeLogExecutor.getRange() );
162             }
163         }
164 
165         setConnection( changeLogExecutor.getRepositoryConnection() );
166 
167         // set the comment query string for the RCS.
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             // log messages from stderr
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         // TODO: Auto-generated method stub
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 }