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