View Javadoc
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.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      * @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 ).append( '\n' );
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 }