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 org.apache.maven.shared.utils.Os;
23 import org.apache.maven.shared.utils.StringUtils;
24
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.StringTokenizer;
32
33 import org.apache.maven.shared.utils.Os;
34 import org.apache.maven.shared.utils.StringUtils;
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( 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( 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( 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( 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( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
135 StreamConsumer systemErr, int timeoutInSeconds,
136 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 private static CommandLineCallable executeCommandLineAsCallable( final Commandline cl, final InputStream systemIn,
160 final StreamConsumer systemOut,
161 final StreamConsumer systemErr,
162 final int timeoutInSeconds,
163 final Runnable runAfterProcessTermination )
164 throws CommandLineException
165 {
166 if ( cl == null )
167 {
168 throw new IllegalArgumentException( "cl cannot be null." );
169 }
170
171 final Process p = cl.execute();
172
173 final StreamFeeder inputFeeder = systemIn != null ? new StreamFeeder( systemIn, p.getOutputStream() ) : null;
174
175 final StreamPumper outputPumper = new StreamPumper( p.getInputStream(), systemOut );
176
177 final StreamPumper errorPumper = new StreamPumper( p.getErrorStream(), systemErr );
178
179 if ( inputFeeder != null )
180 {
181 inputFeeder.start();
182 }
183
184 outputPumper.start();
185
186 errorPumper.start();
187
188 final ProcessHook processHook = new ProcessHook( p );
189
190 ShutdownHookUtils.addShutDownHook( processHook );
191
192 return new CommandLineCallable()
193 {
194 public Integer call()
195 throws CommandLineException
196 {
197 try
198 {
199 int returnValue;
200 if ( timeoutInSeconds <= 0 )
201 {
202 returnValue = p.waitFor();
203 }
204 else
205 {
206 long now = System.currentTimeMillis();
207 long timeoutInMillis = 1000L * timeoutInSeconds;
208 long finish = now + timeoutInMillis;
209 while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
210 {
211 Thread.sleep( 10 );
212 }
213 if ( isAlive( p ) )
214 {
215 throw new InterruptedException(
216 "Process timeout out after " + timeoutInSeconds + " seconds" );
217 }
218
219 returnValue = p.exitValue();
220 }
221
222 if ( runAfterProcessTermination != null )
223 {
224 runAfterProcessTermination.run();
225 }
226
227 waitForAllPumpers( inputFeeder, outputPumper, errorPumper );
228
229 if ( outputPumper.getException() != null )
230 {
231 throw new CommandLineException( "Error inside systemOut parser", outputPumper.getException() );
232 }
233
234 if ( errorPumper.getException() != null )
235 {
236 throw new CommandLineException( "Error inside systemErr parser", errorPumper.getException() );
237 }
238
239 return returnValue;
240 }
241 catch ( InterruptedException ex )
242 {
243 if ( inputFeeder != null )
244 {
245 inputFeeder.disable();
246 }
247
248 outputPumper.disable();
249 errorPumper.disable();
250 throw new CommandLineTimeOutException( "Error while executing external command, process killed.",
251 ex );
252 }
253 finally
254 {
255 ShutdownHookUtils.removeShutdownHook( processHook );
256
257 processHook.run();
258
259 if ( inputFeeder != null )
260 {
261 inputFeeder.close();
262 }
263
264 outputPumper.close();
265
266 errorPumper.close();
267 }
268 }
269 };
270 }
271
272 private static void waitForAllPumpers( StreamFeeder inputFeeder, StreamPumper outputPumper,
273 StreamPumper errorPumper )
274 throws InterruptedException
275 {
276 if ( inputFeeder != null )
277 {
278 inputFeeder.waitUntilDone();
279 }
280
281 outputPumper.waitUntilDone();
282 errorPumper.waitUntilDone();
283 }
284
285
286
287
288
289
290
291
292
293
294
295 public static Properties getSystemEnvVars()
296 {
297 return getSystemEnvVars( !Os.isFamily( Os.FAMILY_WINDOWS ) );
298 }
299
300
301
302
303
304
305
306
307
308
309 public static Properties getSystemEnvVars( boolean caseSensitive )
310 {
311 Map<String, String> envs = System.getenv();
312 return ensureCaseSensitivity( envs, caseSensitive );
313 }
314
315 private static boolean isAlive( Process p )
316 {
317 if ( p == null )
318 {
319 return false;
320 }
321
322 try
323 {
324 p.exitValue();
325 return false;
326 }
327 catch ( IllegalThreadStateException e )
328 {
329 return true;
330 }
331 }
332
333 public static String[] translateCommandline( String toProcess )
334 throws Exception
335 {
336 if ( ( toProcess == null ) || ( toProcess.length() == 0 ) )
337 {
338 return new String[0];
339 }
340
341
342
343 final int normal = 0;
344 final int inQuote = 1;
345 final int inDoubleQuote = 2;
346 int state = normal;
347 StringTokenizer tok = new StringTokenizer( toProcess, "\"\' ", true );
348 List<String> tokens = new ArrayList<String>();
349 StringBuilder current = new StringBuilder();
350
351 while ( tok.hasMoreTokens() )
352 {
353 String nextTok = tok.nextToken();
354 switch ( state )
355 {
356 case inQuote:
357 if ( "\'".equals( nextTok ) )
358 {
359 state = normal;
360 }
361 else
362 {
363 current.append( nextTok );
364 }
365 break;
366 case inDoubleQuote:
367 if ( "\"".equals( nextTok ) )
368 {
369 state = normal;
370 }
371 else
372 {
373 current.append( nextTok );
374 }
375 break;
376 default:
377 if ( "\'".equals( nextTok ) )
378 {
379 state = inQuote;
380 }
381 else if ( "\"".equals( nextTok ) )
382 {
383 state = inDoubleQuote;
384 }
385 else if ( " ".equals( nextTok ) )
386 {
387 if ( current.length() != 0 )
388 {
389 tokens.add( current.toString() );
390 current.setLength( 0 );
391 }
392 }
393 else
394 {
395 current.append( nextTok );
396 }
397 break;
398 }
399 }
400
401 if ( current.length() != 0 )
402 {
403 tokens.add( current.toString() );
404 }
405
406 if ( ( state == inQuote ) || ( state == inDoubleQuote ) )
407 {
408 throw new CommandLineException( "unbalanced quotes in " + toProcess );
409 }
410
411 return tokens.toArray( new String[tokens.size()] );
412 }
413
414 public static String toString( String... line )
415 {
416
417 if ( ( line == null ) || ( line.length == 0 ) )
418 {
419 return "";
420 }
421
422
423 final StringBuilder result = new StringBuilder();
424 for ( int i = 0; i < line.length; i++ )
425 {
426 if ( i > 0 )
427 {
428 result.append( ' ' );
429 }
430 try
431 {
432 result.append( StringUtils.quoteAndEscape( line[i], '\"' ) );
433 }
434 catch ( Exception e )
435 {
436 System.err.println( "Error quoting argument: " + e.getMessage() );
437 }
438 }
439 return result.toString();
440 }
441
442 static Properties ensureCaseSensitivity( Map<String, String> envs, boolean preserveKeyCase )
443 {
444 Properties envVars = new Properties();
445 for ( Map.Entry<String, String> entry : envs.entrySet() )
446 {
447 envVars.put( !preserveKeyCase ? entry.getKey().toUpperCase( Locale.ENGLISH ) : entry.getKey(),
448 entry.getValue() );
449 }
450 return envVars;
451 }
452 }