1 /*
2 * Copyright (C) 2009 Jayway AB
3 * Copyright (C) 2007-2008 JVending Masa
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package com.jayway.maven.plugins.android;
18
19 import java.io.File;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.apache.maven.plugin.logging.Log;
26 import org.codehaus.plexus.util.cli.CommandLineException;
27 import org.codehaus.plexus.util.cli.CommandLineUtils;
28 import org.codehaus.plexus.util.cli.Commandline;
29 import org.codehaus.plexus.util.cli.StreamConsumer;
30 import org.codehaus.plexus.util.cli.shell.Shell;
31
32 /**
33 *
34 */
35 public interface CommandExecutor
36 {
37 /**
38 * Sets the plexus logger.
39 *
40 * @param logger
41 * the plexus logger
42 */
43 void setLogger( Log logger );
44
45 /**
46 * Executes the command for the specified executable and list of command options.
47 *
48 * @param executable
49 * the name of the executable (csc, xsd, etc).
50 * @param commands
51 * the command options for the compiler/executable
52 * @throws ExecutionException
53 * if compiler or executable writes anything to the standard error stream or if the process returns a
54 * process result != 0.
55 */
56 void executeCommand( String executable, List< String > commands ) throws ExecutionException;
57
58 /**
59 * Executes the command for the specified executable and list of command options.
60 *
61 * @param executable
62 * the name of the executable (csc, xsd, etc).
63 * @param commands
64 * the commands options for the compiler/executable
65 * @param failsOnErrorOutput
66 * if true, throws an <code>ExecutionException</code> if there the compiler or executable writes anything
67 * to the error output stream. By default, this value is true
68 * @throws ExecutionException
69 * if compiler or executable writes anything to the standard error stream (provided the
70 * failsOnErrorOutput is not false) or if the process returns a process result != 0.
71 */
72 void executeCommand( String executable, List< String > commands, boolean failsOnErrorOutput )
73 throws ExecutionException;
74
75 /**
76 * Executes the command for the specified executable and list of command options. If the compiler or executable is
77 * not within the environmental path, you should use this method to specify the working directory. Always use this
78 * method for executables located within the local maven repository.
79 *
80 * @param executable
81 * the name of the executable (csc, xsd, etc).
82 * @param commands
83 * the command options for the compiler/executable
84 * @param workingDirectory
85 * the directory where the command will be executed
86 * @throws ExecutionException
87 * if compiler or executable writes anything to the standard error stream (provided the
88 * failsOnErrorOutput is not false) or if the process returns a process result != 0.
89 */
90 void executeCommand( String executable, List< String > commands, File workingDirectory, boolean failsOnErrorOutput )
91 throws ExecutionException;
92
93 /**
94 * Returns the process result of executing the command. Typically a value of 0 means that the process executed
95 * successfully.
96 *
97 * @return the process result of executing the command
98 */
99 int getResult();
100
101 /**
102 * Get the process id for the executed command.
103 *
104 * @return
105 */
106 long getPid();
107
108 /**
109 * Returns the standard output from executing the command.
110 *
111 * @return the standard output from executing the command
112 */
113 String getStandardOut();
114
115 /**
116 * Returns the standard error from executing the command.
117 *
118 * @return the standard error from executing the command
119 */
120 String getStandardError();
121
122 /**
123 * Adds an environment variable with the specified name and value to the executor.
124 *
125 * @param name
126 * @param value
127 */
128 void addEnvironment( String name, String value );
129
130 void setErrorListener( ErrorListener errorListener );
131
132 void setCustomShell( Shell s );
133
134 /**
135 *
136 */
137 public interface ErrorListener
138 {
139 boolean isError( String error );
140 }
141
142 /**
143 * Provides factory services for creating a default instance of the command executor.
144 */
145 public static class Factory
146 {
147
148 /**
149 * Constructor
150 */
151 private Factory()
152 {
153 }
154
155 private static final class DefaultCommandExecutor implements CommandExecutor
156 {
157 private Map< String, String > environment;
158 /**
159 * Instance of a plugin logger.
160 */
161 private Log logger;
162 /**
163 * Standard Out
164 */
165 private StreamConsumer stdOut;
166 /**
167 * Standard Error
168 */
169 private ErrorStreamConsumer stdErr;
170 /**
171 * Process result
172 */
173 private int result;
174 /*
175 */
176 private ErrorListener errorListener;
177 long pid;
178 private Commandline commandline;
179 private Shell customShell;
180
181 @Override
182 public void setLogger( Log logger )
183 {
184 this.logger = logger;
185 }
186
187 @Override
188 public void executeCommand( String executable, List< String > commands ) throws ExecutionException
189 {
190 executeCommand( executable, commands, null, true );
191 }
192
193 @Override
194 public void executeCommand( String executable, List< String > commands, boolean failsOnErrorOutput )
195 throws ExecutionException
196 {
197 executeCommand( executable, commands, null, failsOnErrorOutput );
198 }
199
200 @Override
201 public void executeCommand( String executable, List< String > commands, File workingDirectory,
202 boolean failsOnErrorOutput ) throws ExecutionException
203 {
204 if ( commands == null )
205 {
206 commands = new ArrayList< String >();
207 }
208 stdOut = new StreamConsumerImpl( logger );
209 stdErr = new ErrorStreamConsumer( logger, errorListener );
210 commandline = new Commandline();
211 if ( customShell != null )
212 {
213 commandline.setShell( customShell );
214 }
215 commandline.setExecutable( executable );
216
217 // Add the environment variables as needed
218 if ( environment != null )
219 {
220 for ( Map.Entry< String, String > entry : environment.entrySet() )
221 {
222 commandline.addEnvironment( entry.getKey(), entry.getValue() );
223 }
224 }
225
226 commandline.addArguments( commands.toArray( new String[ commands.size() ] ) );
227 if ( workingDirectory != null && workingDirectory.exists() )
228 {
229 commandline.setWorkingDirectory( workingDirectory.getAbsolutePath() );
230 }
231 try
232 {
233 result = CommandLineUtils.executeCommandLine( commandline, stdOut, stdErr );
234 if ( logger != null )
235 {
236 logger.debug( "ANDROID-040-000: Executed command: Commandline = " + commandline + ", Result = "
237 + result );
238 }
239 else
240 {
241 System.out.println( "ANDROID-040-000: Executed command: Commandline = " + commandline
242 + ", Result = " + result );
243 }
244 if ( failsOnErrorOutput && stdErr.hasError() || result != 0 )
245 {
246 throw new ExecutionException( "ANDROID-040-001: Could not execute: Command = "
247 + commandline.toString() + ", Result = " + result );
248 }
249 }
250 catch ( CommandLineException e )
251 {
252 throw new ExecutionException( "ANDROID-040-002: Could not execute: Command = "
253 + commandline.toString() + ", Error message = " + e.getMessage() );
254 }
255 setPid( commandline.getPid() );
256 }
257
258 @Override
259 public int getResult()
260 {
261 return result;
262 }
263
264 @Override
265 public String getStandardOut()
266 {
267 return stdOut.toString();
268 }
269
270 @Override
271 public String getStandardError()
272 {
273 return stdErr.toString();
274 }
275
276 @Override
277 public void addEnvironment( String name, String value )
278 {
279 if ( environment == null )
280 {
281 environment = new HashMap< String, String >();
282 }
283 environment.put( name, value );
284 }
285
286 @Override
287 public void setErrorListener( ErrorListener errorListener )
288 {
289 this.errorListener = errorListener;
290 }
291
292 public void setPid( long pid )
293 {
294 this.pid = pid;
295 }
296
297 @Override
298 public long getPid()
299 {
300 return pid;
301 }
302
303 @Override
304 public void setCustomShell( Shell shell )
305 {
306 this.customShell = shell;
307 }
308 }
309
310 /**
311 * StreamConsumer instance that buffers the entire output
312 */
313 static class StreamConsumerImpl implements StreamConsumer
314 {
315 private StringBuffer sb = new StringBuffer();
316 private final Log logger;
317
318 public StreamConsumerImpl( Log logger )
319 {
320 this.logger = logger;
321 }
322
323 @Override
324 public void consumeLine( String line )
325 {
326 sb.append( line );
327 if ( logger != null )
328 {
329 logger.debug( line );
330 }
331 }
332
333 /**
334 * Returns the stream
335 *
336 * @return the stream
337 */
338 @Override
339 public String toString()
340 {
341 return sb.toString();
342 }
343 }
344
345 /**
346 * Provides behavior for determining whether the command utility wrote anything to the Standard Error Stream.
347 * NOTE: I am using this to decide whether to fail the NMaven build. If the compiler implementation chooses to
348 * write warnings to the error stream, then the build will fail on warnings!!!
349 */
350 static class ErrorStreamConsumer implements StreamConsumer
351 {
352 /** Is true if there was anything consumed from the stream, otherwise false */
353 private boolean error;
354 /** Buffer to store the stream */
355 private StringBuffer sbe = new StringBuffer();
356 private final Log logger;
357 private final ErrorListener errorListener;
358
359 public ErrorStreamConsumer( Log logger, ErrorListener errorListener )
360 {
361 this.logger = logger;
362 this.errorListener = errorListener;
363
364 if ( logger == null )
365 {
366 System.out.println( "ANDROID-040-003: Error Log not set: Will not output error logs" );
367 }
368 error = false;
369 }
370
371 @Override
372 public void consumeLine( String line )
373 {
374 sbe.append( line );
375 if ( logger != null )
376 {
377 logger.info( line );
378 }
379 if ( errorListener != null )
380 {
381 error = errorListener.isError( line );
382 }
383 else
384 {
385 error = true;
386 }
387 }
388
389 /**
390 * Returns false if the command utility wrote to the Standard Error Stream, otherwise returns true.
391 *
392 * @return false if the command utility wrote to the Standard Error Stream, otherwise returns true.
393 */
394 public boolean hasError()
395 {
396 return error;
397 }
398
399 /**
400 * Returns the error stream
401 *
402 * @return error stream
403 */
404 @Override
405 public String toString()
406 {
407 return sbe.toString();
408 }
409 }
410
411 /**
412 * Returns a default instance of the command executor
413 *
414 * @return a default instance of the command executor
415 */
416 public static CommandExecutor createDefaultCommmandExecutor()
417 {
418 return new DefaultCommandExecutor();
419
420 }
421 }
422 }