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