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