View Javadoc

1   package org.apache.maven.cvslib;
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  import java.io.ByteArrayInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  
25  import java.text.SimpleDateFormat;
26  
27  import java.util.Collection;
28  import java.util.Date;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.maven.changelog.AbstractChangeLogGenerator;
33  import org.apache.maven.changelog.ChangeLog;
34  import org.apache.maven.changelog.ChangeLogParser;
35  import org.apache.maven.util.AsyncStreamReader;
36  import org.apache.maven.util.RepositoryUtils;
37  import org.apache.tools.ant.types.Commandline;
38  
39  
40  /**
41   * A CVS implementation of the {@link org.apache.maven.changelog.ChangeLog}
42   * interface.
43   *
44   * @author Glenn McAllister
45   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
46   * @author <a href="mailto:jeff.martin@synamic.co.uk">Jeff Martin</a>
47   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
48   * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
49   * @author <a href="mailto:bodewig@apache.org">Stefan Bodewig</a>
50   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
51   * @author <a href="mailto:pete-apache-dev@kazmier.com">Pete Kazmier</a>
52   * @version $Id: CvsChangeLogGenerator.java,v 1.6 2003/04/11 18:53:19 bwalding
53   *          Exp $
54   */
55  public class CvsChangeLogGenerator extends AbstractChangeLogGenerator
56  {
57      /** Log */
58      private static final Log LOG =
59          LogFactory.getLog( CvsChangeLogGenerator.class );
60      public static final int POS_SCM = 0;
61      public static final int POS_SCM_TYPE = 1;
62      public static final int POS_SCM_SUBTYPE = 2;
63      public static final int POS_SCM_USERHOST = 3;
64      public static final int POS_SCM_PATH = 4;
65      public static final int POS_SCM_MODULE = 5;
66      private boolean quoteDate;
67  
68      /**
69       * Set the quoteDate property.
70       * @param newQuoteDate the quoteDate property to set.
71       */
72      public void setQuoteDate( boolean newQuoteDate )
73      {
74          this.quoteDate = newQuoteDate;
75      }
76  
77      /**
78       * Get the quoteDate property.
79       *
80       * @return the quoteDate property.
81       */
82      public boolean getQuoteDate()
83      {
84          return quoteDate;
85      }
86  
87      /**
88       * Initialize the generator from the changelog controller.
89       *
90       * @param changeLog The invoking controller (useful for logging)
91       * @see ChangeLogGenerator#init(ChangeLog)
92       */
93      public void init( ChangeLog changeLog )
94      {
95          setQuoteDate( changeLog.getQuoteDate() );
96          super.init( changeLog );
97      }
98  
99      /**
100      * Execute cvslib client driving the given parser. @todo Currently the
101      * output from the logListener is a String, which is then converted to an
102      * InputStream. The output of logListener really should be an input stream.
103      *
104      * @param parser A {@link ChangeLogParser parser}to process the scm
105      *            output.
106      * @return A collection of {@link ChangeLogEntry entries}parsed from the
107      *         scm output.
108      * @throws IOException When there are issues executing scm.
109      * @see ChangeLogGenerator#getEntries(ChangeLogParser)
110      */
111     public Collection getEntries( ChangeLogParser parser )
112         throws IOException
113     {
114         if ( parser == null )
115         {
116             throw new NullPointerException( "parser cannot be null" );
117         }
118 
119         if ( base == null )
120         {
121             throw new NullPointerException( "basedir must be set" );
122         }
123 
124         if ( !base.exists() )
125         {
126             throw new FileNotFoundException( "Cannot find base dir "
127                 + base.getAbsolutePath() );
128         }
129 
130         clParser = parser;
131 
132         String[] args = getScmLogCommand().getArguments();
133         CvsLogListener logListener = new CvsLogListener();
134 
135         try
136         {
137             CvsConnection.processCommand( args,
138                 this.changeLogExecutor.getBasedir().toString(), logListener );
139             entries =
140                 clParser.parse( new ByteArrayInputStream( 
141                         logListener.getStdout().toString().getBytes() ) );
142         }
143         catch ( IllegalArgumentException iae )
144         {
145             entries = super.getEntries( parser );
146         }
147         catch ( Exception e )
148         {
149             LOG.error( "Error processing command", e );
150         }
151 
152         return entries;
153     }
154 
155     /**
156      * @return the cvs command line to be executed.
157      */
158     protected Commandline getScmLogCommand()
159     {
160         String[] tokens =
161             RepositoryUtils.splitSCMConnection( getConnection() );
162 
163         if ( !tokens[POS_SCM_TYPE].equals( "cvs" ) )
164         {
165             throw new IllegalArgumentException( "repository connection string"
166                 + " does not specify 'cvs' as the scm"
167                 + System.getProperty( "line.separator" )
168                 + "If using another scm, maven.changelog.factory"
169                 + " must be set." + System.getProperty( "line.separator" )
170                 + "See the maven changelog plugin documentation"
171                 + " for correct settings." );
172         }
173 
174         Commandline command = new Commandline();
175 
176         command.setExecutable( "cvs" );
177         command.createArgument().setValue( "-d" );
178 
179         // from format:
180         // scm:cvs:pserver:anoncvs@cvs.apache.org:/home/cvspublic:jakarta-turbine-maven/src/plugins-build/changelog/
181         // to format:
182         // :pserver:anoncvs@cvs.apache.org:/home/cvspublic
183         // use tokens 3+4+5
184         String connectionBuffer = "";
185 
186         if ( tokens[POS_SCM_SUBTYPE].equalsIgnoreCase( "local" ) )
187         {
188             // use the local repository directory eg. '/home/cvspublic'
189             connectionBuffer = tokens[POS_SCM_PATH];
190         }
191         else if ( tokens[POS_SCM_SUBTYPE].equalsIgnoreCase( "lserver" ) )
192         {
193             //create the cvsroot as the local socket cvsroot
194             connectionBuffer =
195                 tokens[POS_SCM_USERHOST] + ":" + tokens[POS_SCM_PATH];
196         }
197         else
198         {
199             //create the cvsroot as the remote cvsroot
200             connectionBuffer =
201                 ":" + tokens[POS_SCM_SUBTYPE] + ":" + tokens[POS_SCM_USERHOST]
202                 + ":" + tokens[POS_SCM_PATH];
203         }
204 
205         command.createArgument().setValue( connectionBuffer.toString() );
206         command.createArgument().setValue( "log" );
207 
208         if ( dateRange != null )
209         {
210             command.createArgument().setValue( dateRange );
211         }
212         else if ( tag != null )
213         {
214             command.createArgument().setValue( tag );
215         }
216 
217         return command;
218     }
219 
220     /**
221      * Construct the CVS command-line argument that is used to specify the
222      * appropriate date range.
223      *
224      * @param before The starting point.
225      * @param to The ending point.
226      * @return A string that can be used to specify a date to a scm system.
227      */
228     protected String getScmDateArgument( Date before, Date to )
229     {
230         SimpleDateFormat outputDate = new SimpleDateFormat( "yyyy-MM-dd" );
231         String cmd =
232             outputDate.format( before ) + "<" + outputDate.format( to );
233 
234         if ( getQuoteDate() )
235         {
236             cmd = "\"" + cmd + "\"";
237         }
238 
239         return "-d " + cmd;
240     }
241 
242     /**
243      * @see AbstractChangeLogGenerator#getScmTagArgument(String, String)
244      */
245     protected String getScmTagArgument( String tagStart, String tagEnd )
246     {
247         return "-r" + tagStart + "::" + ( ( tagEnd != null ) ? tagEnd : "" );
248     }
249 
250     /**
251      * Handle ChangeLogParser IOExceptions.
252      *
253      * @param ioe The IOException thrown.
254      * @throws IOException If the handler doesn't wish to handle the exception.
255      */
256     protected void handleParserException( IOException ioe )
257         throws IOException
258     {
259         if ( ( ioe.getMessage().indexOf( "CreateProcess" ) != -1 )
260             || ( ioe.getMessage().indexOf( "cvs: not found" ) != -1 ) )
261         {
262             // can't find CVS on Win32 or Linux...
263             if ( LOG.isWarnEnabled() )
264             {
265                 LOG.warn( 
266                     "Unable to find cvs executable. Please check that it is in your path, and avoid using spaces in the path name. Changelog will be empty" );
267             }
268         }
269         else
270         {
271             throw ioe;
272         }
273     }
274 
275     /**
276      * Set the error stream for reading from cvs log. This stream will be read
277      * on a separate thread.
278      *
279      * @param is - an {@link java.io.InputStream}
280      */
281     public void setProcessErrorStream( InputStream is )
282     {
283         errorReader = new CvsAsyncErrorReader( is );
284     }
285 
286     /**
287      * A private AsyncStreamReader class that "swallows" the "cvs server:
288      * Logging" lines.
289      */
290     private static class CvsAsyncErrorReader extends AsyncStreamReader
291     {
292         /**
293          * The obvious constructor.
294          *
295          * @param anInputStream the input stream to consume
296          */
297         public CvsAsyncErrorReader( InputStream anInputStream )
298         {
299             super( anInputStream );
300         }
301 
302         /**
303          * If the line does not start with "cvs server: Logging", it's ok to
304          * consume it.
305          *
306          * @param line the line to check
307          * @return <code>true</code> if the line does not start with "cvs
308          *         server: Logging"
309          */
310         protected boolean okToConsume( String line )
311         {
312             return !line.startsWith( "cvs server: Logging" );
313         }
314     }
315 }