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