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.simpligility.maven.plugins.androidndk;
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 * @return the process id for the executed command.
103 */
104 long getPid();
105
106 /**
107 * Returns the standard output from executing the command.
108 *
109 * @return the standard output from executing the command
110 */
111 String getStandardOut();
112
113 /**
114 * Returns the standard error from executing the command.
115 *
116 * @return the standard error from executing the command
117 */
118 String getStandardError();
119
120 /**
121 * Adds an environment variable with the specified name and value to the executor.
122 */
123 void addEnvironment( String name, String value );
124
125 void setErrorListener( ErrorListener errorListener );
126
127 void setCustomShell( Shell s );
128
129 void setCaptureStdOut( boolean captureStdOut );
130 void setCaptureStdErr( boolean captureStdErr );
131
132 /**
133 *
134 */
135 public interface ErrorListener
136 {
137 boolean isError( String error );
138 }
139
140 /**
141 * Provides factory services for creating a default instance of the command executor.
142 */
143 class Factory
144 {
145
146 /**
147 * Constructor
148 */
149 private Factory()
150 {
151 }
152
153 private static final class DefaultCommandExecutor implements CommandExecutor
154 {
155 private Map< String, String > environment;
156 /**
157 * Instance of a plugin logger.
158 */
159 private Log logger;
160 /**
161 * Standard Out
162 */
163 private StreamConsumer stdOut;
164 /**
165 * Standard Error
166 */
167 private ErrorStreamConsumer stdErr;
168 /**
169 * Process result
170 */
171 private int result;
172 /*
173 */
174 private ErrorListener errorListener;
175 long pid;
176 private Commandline commandline;
177 private Shell customShell;
178
179 private boolean captureStdOut;
180 private boolean captureStdErr;
181
182 @Override
183 public void setLogger( Log logger )
184 {
185 this.logger = logger;
186 }
187
188 @Override
189 public void executeCommand( String executable, List< String > commands ) throws ExecutionException
190 {
191 executeCommand( executable, commands, null, true );
192 }
193
194 @Override
195 public void executeCommand( String executable, List< String > commands, boolean failsOnErrorOutput )
196 throws ExecutionException
197 {
198 executeCommand( executable, commands, null, failsOnErrorOutput );
199 }
200
201 @Override
202 public void executeCommand( String executable, List< String > commands, File workingDirectory,
203 boolean failsOnErrorOutput ) throws ExecutionException
204 {
205 if ( commands == null )
206 {
207 commands = new ArrayList< String >();
208 }
209 stdOut = new StreamConsumerImpl( logger, captureStdOut );
210 stdErr = new ErrorStreamConsumer( logger, errorListener, captureStdErr );
211 commandline = new Commandline();
212 if ( customShell != null )
213 {
214 commandline.setShell( customShell );
215 }
216 commandline.setExecutable( executable );
217
218 // Add the environment variables as needed
219 if ( environment != null )
220 {
221 for ( Map.Entry< String, String > entry : environment.entrySet() )
222 {
223 commandline.addEnvironment( entry.getKey(), entry.getValue() );
224 }
225 }
226
227 commandline.addArguments( commands.toArray( new String[ commands.size() ] ) );
228 if ( workingDirectory != null && workingDirectory.exists() )
229 {
230 commandline.setWorkingDirectory( workingDirectory.getAbsolutePath() );
231 }
232 try
233 {
234 logger.debug( "ANDROID-040-000: Executing command: Commandline = " + commandline );
235 result = CommandLineUtils.executeCommandLine( commandline, stdOut, stdErr );
236 if ( logger != null )
237 {
238 logger.debug( "ANDROID-040-000: Executed command: Commandline = " + commandline + ", Result = "
239 + result );
240 }
241 else
242 {
243 System.out.println( "ANDROID-040-000: Executed command: Commandline = " + commandline
244 + ", Result = " + result );
245 }
246 if ( failsOnErrorOutput && stdErr.hasError() || result != 0 )
247 {
248 throw new ExecutionException( "ANDROID-040-001: Could not execute: Command = "
249 + commandline.toString() + ", Result = " + result );
250 }
251 }
252 catch ( CommandLineException e )
253 {
254 throw new ExecutionException( "ANDROID-040-002: Could not execute: Command = "
255 + commandline.toString() + ", Error message = " + e.getMessage() );
256 }
257 setPid( commandline.getPid() );
258 }
259
260 @Override
261 public int getResult()
262 {
263 return result;
264 }
265
266 @Override
267 public String getStandardOut()
268 {
269 if ( ! captureStdOut )
270 {
271 throw new IllegalStateException( "Unable to provide StdOut since it was not captured" );
272 }
273 return stdOut.toString();
274 }
275
276 @Override
277 public String getStandardError()
278 {
279 if ( ! captureStdErr )
280 {
281 throw new IllegalStateException( "Unable to provide StdOut since it was not captured" );
282 }
283 return stdErr.toString();
284 }
285
286 @Override
287 public void addEnvironment( String name, String value )
288 {
289 if ( environment == null )
290 {
291 environment = new HashMap< String, String >();
292 }
293 environment.put( name, value );
294 }
295
296 @Override
297 public void setErrorListener( ErrorListener errorListener )
298 {
299 this.errorListener = errorListener;
300 }
301
302 public void setPid( long pid )
303 {
304 this.pid = pid;
305 }
306
307 @Override
308 public long getPid()
309 {
310 return pid;
311 }
312
313 @Override
314 public void setCustomShell( Shell shell )
315 {
316 this.customShell = shell;
317 }
318
319 @Override
320 public void setCaptureStdOut( boolean captureStdOut )
321 {
322 this.captureStdOut = captureStdOut;
323 }
324
325 @Override
326 public void setCaptureStdErr( boolean captureStdErr )
327 {
328 this.captureStdErr = captureStdErr;
329 }
330 }
331
332 /**
333 * StreamConsumer instance that buffers the entire output
334 */
335 static class StreamConsumerImpl implements StreamConsumer
336 {
337 private StringBuffer sb = new StringBuffer();
338 private final Log logger;
339 private boolean captureStdOut;
340
341 StreamConsumerImpl( Log logger, boolean captureStdOut )
342 {
343 this.logger = logger;
344 this.captureStdOut = captureStdOut;
345 }
346
347 @Override
348 public void consumeLine( String line )
349 {
350 if ( captureStdOut )
351 {
352 sb.append( line );
353 }
354 if ( logger != null )
355 {
356 logger.debug( line );
357 }
358 }
359
360 /**
361 * Returns the stream
362 *
363 * @return the stream
364 */
365 @Override
366 public String toString()
367 {
368 return sb.toString();
369 }
370 }
371
372 /**
373 * Provides behavior for determining whether the command utility wrote anything to the Standard Error Stream.
374 * NOTE: I am using this to decide whether to fail the NMaven build. If the compiler implementation chooses to
375 * write warnings to the error stream, then the build will fail on warnings!!!
376 */
377 static class ErrorStreamConsumer implements StreamConsumer
378 {
379 /** Is true if there was anything consumed from the stream, otherwise false */
380 private boolean error;
381 /** Buffer to store the stream */
382 private StringBuffer sbe = new StringBuffer();
383 private final Log logger;
384 private final ErrorListener errorListener;
385 private boolean captureStdErr;
386
387 ErrorStreamConsumer( Log logger, ErrorListener errorListener, boolean captureStdErr )
388 {
389 this.logger = logger;
390 this.errorListener = errorListener;
391 this.captureStdErr = captureStdErr;
392
393 if ( logger == null )
394 {
395 System.out.println( "ANDROID-040-003: Error Log not set: Will not output error logs" );
396 }
397 error = false;
398 }
399
400 @Override
401 public void consumeLine( String line )
402 {
403 if ( captureStdErr )
404 {
405 sbe.append( line );
406 }
407 if ( logger != null )
408 {
409 logger.info( line );
410 }
411 if ( errorListener != null )
412 {
413 error = errorListener.isError( line );
414 }
415 else
416 {
417 error = true;
418 }
419 }
420
421 /**
422 * Returns false if the command utility wrote to the Standard Error Stream, otherwise returns true.
423 *
424 * @return false if the command utility wrote to the Standard Error Stream, otherwise returns true.
425 */
426 public boolean hasError()
427 {
428 return error;
429 }
430
431 /**
432 * Returns the error stream
433 *
434 * @return error stream
435 */
436 @Override
437 public String toString()
438 {
439 return sbe.toString();
440 }
441 }
442
443 /**
444 * Returns a default instance of the command executor
445 *
446 * @return a default instance of the command executor
447 */
448 public static CommandExecutor createDefaultCommmandExecutor()
449 {
450 return new DefaultCommandExecutor();
451
452 }
453 }
454 }