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