1 package org.apache.maven.cvslib;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
180
181
182
183
184 String connectionBuffer = "";
185
186 if ( tokens[POS_SCM_SUBTYPE].equalsIgnoreCase( "local" ) )
187 {
188
189 connectionBuffer = tokens[POS_SCM_PATH];
190 }
191 else if ( tokens[POS_SCM_SUBTYPE].equalsIgnoreCase( "lserver" ) )
192 {
193
194 connectionBuffer =
195 tokens[POS_SCM_USERHOST] + ":" + tokens[POS_SCM_PATH];
196 }
197 else
198 {
199
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
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 }