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