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