1 package org.apache.maven.shared.utils.cli;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.lang.reflect.InvocationTargetException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.StringTokenizer;
31 import org.apache.maven.shared.utils.Os;
32 import org.apache.maven.shared.utils.StringUtils;
33
34
35
36
37
38 public abstract class CommandLineUtils
39 {
40
41
42 public static class StringStreamConsumer
43 implements StreamConsumer
44 {
45 private final StringBuffer string = new StringBuffer();
46
47 private static final String LS = System.getProperty( "line.separator" );
48
49 public void consumeLine( String line )
50 {
51 string.append( line ).append( LS );
52 }
53
54 public String getOutput()
55 {
56 return string.toString();
57 }
58 }
59
60 private static class ProcessHook
61 extends Thread
62 {
63 private final Process process;
64
65 private ProcessHook( Process process )
66 {
67 super( "CommandlineUtils process shutdown hook" );
68 this.process = process;
69 this.setContextClassLoader( null );
70 }
71
72 public void run()
73 {
74 process.destroy();
75 }
76 }
77
78
79 public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr )
80 throws CommandLineException
81 {
82 return executeCommandLine( cl, null, systemOut, systemErr, 0 );
83 }
84
85 public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr,
86 int timeoutInSeconds )
87 throws CommandLineException
88 {
89 return executeCommandLine( cl, null, systemOut, systemErr, timeoutInSeconds );
90 }
91
92 public static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
93 StreamConsumer systemErr )
94 throws CommandLineException
95 {
96 return executeCommandLine( cl, systemIn, systemOut, systemErr, 0 );
97 }
98
99
100
101
102
103
104
105
106
107
108
109 private static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
110 StreamConsumer systemErr, int timeoutInSeconds )
111 throws CommandLineException
112 {
113 final CommandLineCallable future =
114 executeCommandLineAsCallable( cl, systemIn, systemOut, systemErr, timeoutInSeconds );
115 return future.call();
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 private static CommandLineCallable executeCommandLineAsCallable( final Commandline cl, final InputStream systemIn,
133 final StreamConsumer systemOut,
134 final StreamConsumer systemErr,
135 final int timeoutInSeconds )
136 throws CommandLineException
137 {
138 if ( cl == null )
139 {
140 throw new IllegalArgumentException( "cl cannot be null." );
141 }
142
143 final Process p = cl.execute();
144
145 final StreamFeeder inputFeeder = systemIn != null ? new StreamFeeder( systemIn, p.getOutputStream() ) : null;
146
147 final StreamPumper outputPumper = new StreamPumper( p.getInputStream(), systemOut );
148
149 final StreamPumper errorPumper = new StreamPumper( p.getErrorStream(), systemErr );
150
151 if ( inputFeeder != null )
152 {
153 inputFeeder.start();
154 }
155
156 outputPumper.start();
157
158 errorPumper.start();
159
160 final ProcessHook processHook = new ProcessHook( p );
161
162 ShutdownHookUtils.addShutDownHook( processHook );
163
164 return new CommandLineCallable()
165 {
166 public Integer call()
167 throws CommandLineException
168 {
169 try
170 {
171 int returnValue;
172 if ( timeoutInSeconds <= 0 )
173 {
174 returnValue = p.waitFor();
175 }
176 else
177 {
178 long now = System.currentTimeMillis();
179 long timeoutInMillis = 1000L * timeoutInSeconds;
180 long finish = now + timeoutInMillis;
181 while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
182 {
183 Thread.sleep( 10 );
184 }
185 if ( isAlive( p ) )
186 {
187 throw new InterruptedException(
188 "Process timeout out after " + timeoutInSeconds + " seconds" );
189 }
190
191 returnValue = p.exitValue();
192 }
193
194 waitForAllPumpers( inputFeeder, outputPumper, errorPumper );
195
196 if ( outputPumper.getException() != null )
197 {
198 throw new CommandLineException( "Error inside systemOut parser", outputPumper.getException() );
199 }
200
201 if ( errorPumper.getException() != null )
202 {
203 throw new CommandLineException( "Error inside systemErr parser", errorPumper.getException() );
204 }
205
206 return returnValue;
207 }
208 catch ( InterruptedException ex )
209 {
210 if ( inputFeeder != null )
211 {
212 inputFeeder.disable();
213 }
214
215 outputPumper.disable();
216 errorPumper.disable();
217 throw new CommandLineTimeOutException( "Error while executing external command, process killed.",
218 ex );
219 }
220 finally
221 {
222 ShutdownHookUtils.removeShutdownHook( processHook );
223
224 processHook.run();
225
226 if ( inputFeeder != null )
227 {
228 inputFeeder.close();
229 }
230
231 outputPumper.close();
232
233 errorPumper.close();
234 }
235 }
236 };
237 }
238
239 private static void waitForAllPumpers( StreamFeeder inputFeeder, StreamPumper outputPumper,
240 StreamPumper errorPumper )
241 throws InterruptedException
242 {
243 if ( inputFeeder != null )
244 {
245 inputFeeder.waitUntilDone();
246 }
247
248 outputPumper.waitUntilDone();
249 errorPumper.waitUntilDone();
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263 public static Properties getSystemEnvVars()
264 throws IOException
265 {
266 return getSystemEnvVars( !Os.isFamily( Os.FAMILY_WINDOWS ) );
267 }
268
269
270
271
272
273
274
275
276
277
278
279 public static Properties getSystemEnvVars( boolean caseSensitive )
280 throws IOException
281 {
282
283
284
285 try
286 {
287 Map<String, String> envs = System.getenv();
288 return ensureCaseSensitivity( envs, caseSensitive );
289 }
290 catch ( IllegalAccessException e )
291 {
292 throw new IOException( e.getMessage() );
293 }
294 catch ( IllegalArgumentException e )
295 {
296 throw new IOException( e.getMessage() );
297 }
298 catch ( InvocationTargetException e )
299 {
300 throw new IOException( e.getMessage() );
301 }
302 }
303
304 private static boolean isAlive( Process p )
305 {
306 if ( p == null )
307 {
308 return false;
309 }
310
311 try
312 {
313 p.exitValue();
314 return false;
315 }
316 catch ( IllegalThreadStateException e )
317 {
318 return true;
319 }
320 }
321
322 public static String[] translateCommandline( String toProcess )
323 throws Exception
324 {
325 if ( ( toProcess == null ) || ( toProcess.length() == 0 ) )
326 {
327 return new String[0];
328 }
329
330
331
332 final int normal = 0;
333 final int inQuote = 1;
334 final int inDoubleQuote = 2;
335 int state = normal;
336 StringTokenizer tok = new StringTokenizer( toProcess, "\"\' ", true );
337 List<String> tokens = new ArrayList<String>();
338 StringBuilder current = new StringBuilder();
339
340 while ( tok.hasMoreTokens() )
341 {
342 String nextTok = tok.nextToken();
343 switch ( state )
344 {
345 case inQuote:
346 if ( "\'".equals( nextTok ) )
347 {
348 state = normal;
349 }
350 else
351 {
352 current.append( nextTok );
353 }
354 break;
355 case inDoubleQuote:
356 if ( "\"".equals( nextTok ) )
357 {
358 state = normal;
359 }
360 else
361 {
362 current.append( nextTok );
363 }
364 break;
365 default:
366 if ( "\'".equals( nextTok ) )
367 {
368 state = inQuote;
369 }
370 else if ( "\"".equals( nextTok ) )
371 {
372 state = inDoubleQuote;
373 }
374 else if ( " ".equals( nextTok ) )
375 {
376 if ( current.length() != 0 )
377 {
378 tokens.add( current.toString() );
379 current.setLength( 0 );
380 }
381 }
382 else
383 {
384 current.append( nextTok );
385 }
386 break;
387 }
388 }
389
390 if ( current.length() != 0 )
391 {
392 tokens.add( current.toString() );
393 }
394
395 if ( ( state == inQuote ) || ( state == inDoubleQuote ) )
396 {
397 throw new CommandLineException( "unbalanced quotes in " + toProcess );
398 }
399
400 return tokens.toArray( new String[tokens.size()] );
401 }
402
403 public static String toString( String... line )
404 {
405
406 if ( ( line == null ) || ( line.length == 0 ) )
407 {
408 return "";
409 }
410
411
412 final StringBuilder result = new StringBuilder();
413 for ( int i = 0; i < line.length; i++ )
414 {
415 if ( i > 0 )
416 {
417 result.append( ' ' );
418 }
419 try
420 {
421 result.append( StringUtils.quoteAndEscape( line[i], '\"' ) );
422 }
423 catch ( Exception e )
424 {
425 System.err.println( "Error quoting argument: " + e.getMessage() );
426 }
427 }
428 return result.toString();
429 }
430
431 static Properties ensureCaseSensitivity( Map<String, String> envs, boolean preserveKeyCase )
432 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
433 {
434 Properties envVars = new Properties();
435 for ( Map.Entry<String, String> entry : envs.entrySet() )
436 {
437 envVars.put( !preserveKeyCase ? entry.getKey().toUpperCase( Locale.ENGLISH ) : entry.getKey(),
438 entry.getValue() );
439 }
440 return envVars;
441 }
442 }