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.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 }