1818
1919import java .io .IOException ;
2020import java .io .InputStream ;
21- import java .lang .reflect .Method ;
2221import java .util .Locale ;
2322import java .util .Map ;
2423import java .util .Properties ;
2524import java .util .StringTokenizer ;
2625import java .util .Vector ;
26+
2727import org .codehaus .plexus .util .Os ;
2828import org .codehaus .plexus .util .StringUtils ;
2929
3333 */
3434public abstract class CommandLineUtils
3535{
36+
37+ /**
38+ * A {@code StreamConsumer} providing consumed lines as a {@code String}.
39+ *
40+ * @see #getOutput()
41+ */
3642 public static class StringStreamConsumer
3743 implements StreamConsumer
3844 {
45+
3946 private StringBuffer string = new StringBuffer ();
4047
4148 private String ls = System .getProperty ( "line.separator" );
@@ -49,24 +56,18 @@ public String getOutput()
4956 {
5057 return string .toString ();
5158 }
52- }
53-
54- private static class ProcessHook extends Thread {
55- private final Process process ;
56-
57- private ProcessHook ( Process process )
58- {
59- super ("CommandlineUtils process shutdown hook" );
60- this .process = process ;
61- this .setContextClassLoader ( null );
62- }
6359
64- public void run ()
65- {
66- process .destroy ();
67- }
6860 }
6961
62+ /**
63+ * Number of milliseconds per second.
64+ */
65+ private static final long MILLIS_PER_SECOND = 1000L ;
66+
67+ /**
68+ * Number of nanoseconds per second.
69+ */
70+ private static final long NANOS_PER_SECOND = 1000000000L ;
7071
7172 public static int executeCommandLine ( Commandline cl , StreamConsumer systemOut , StreamConsumer systemErr )
7273 throws CommandLineException
@@ -120,10 +121,11 @@ public static int executeCommandLine( Commandline cl, InputStream systemIn, Stre
120121 * @throws CommandLineException or CommandLineTimeOutException if time out occurs
121122 * @noinspection ThrowableResultOfMethodCallIgnored
122123 */
123- public static CommandLineCallable executeCommandLineAsCallable ( final Commandline cl , final InputStream systemIn ,
124- final StreamConsumer systemOut ,
125- final StreamConsumer systemErr ,
126- final int timeoutInSeconds )
124+ public static CommandLineCallable executeCommandLineAsCallable ( final Commandline cl ,
125+ final InputStream systemIn ,
126+ final StreamConsumer systemOut ,
127+ final StreamConsumer systemErr ,
128+ final int timeoutInSeconds )
127129 throws CommandLineException
128130 {
129131 if ( cl == null )
@@ -133,108 +135,215 @@ public static CommandLineCallable executeCommandLineAsCallable( final Commandlin
133135
134136 final Process p = cl .execute ();
135137
136- final StreamFeeder inputFeeder = systemIn != null ?
137- new StreamFeeder ( systemIn , p .getOutputStream () ) : null ;
138-
139- final StreamPumper outputPumper = new StreamPumper ( p .getInputStream (), systemOut );
140-
141- final StreamPumper errorPumper = new StreamPumper ( p .getErrorStream (), systemErr );
142-
143- if ( inputFeeder != null )
138+ final Thread processHook = new Thread ()
144139 {
145- inputFeeder .start ();
146- }
147140
148- outputPumper .start ();
141+ {
142+ this .setName ( "CommandLineUtils process shutdown hook" );
143+ this .setContextClassLoader ( null );
144+ }
149145
150- errorPumper .start ();
146+ @ Override
147+ public void run ()
148+ {
149+ p .destroy ();
150+ }
151151
152- final ProcessHook processHook = new ProcessHook ( p ) ;
152+ } ;
153153
154154 ShutdownHookUtils .addShutDownHook ( processHook );
155155
156156 return new CommandLineCallable ()
157157 {
158+
158159 public Integer call ()
159160 throws CommandLineException
160161 {
162+ StreamFeeder inputFeeder = null ;
163+ StreamPumper outputPumper = null ;
164+ StreamPumper errorPumper = null ;
165+ boolean success = false ;
161166 try
162167 {
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+
163180 int returnValue ;
164181 if ( timeoutInSeconds <= 0 )
165182 {
166183 returnValue = p .waitFor ();
167184 }
168185 else
169186 {
170- long now = System .currentTimeMillis ();
171- long timeoutInMillis = 1000L * timeoutInSeconds ;
172- long finish = now + timeoutInMillis ;
173- while ( isAlive ( p ) && ( System .currentTimeMillis () < finish ) )
187+ final long now = System .nanoTime ();
188+ final long timeout = now + NANOS_PER_SECOND * timeoutInSeconds ;
189+
190+ while ( isAlive ( p ) && ( System .nanoTime () < timeout ) )
174191 {
175- Thread .sleep ( 10 );
192+ // The timeout is specified in seconds. Therefore we must not sleep longer than one second
193+ // but we should sleep as long as possible to reduce the number of iterations performed.
194+ Thread .sleep ( MILLIS_PER_SECOND - 1L );
176195 }
196+
177197 if ( isAlive ( p ) )
178198 {
179- throw new InterruptedException ( "Process timeout out after " + timeoutInSeconds + " seconds" );
199+ throw new InterruptedException ( String .format ( "Process timed out after %d seconds." ,
200+ timeoutInSeconds ) );
180201 }
202+
181203 returnValue = p .exitValue ();
182204 }
183205
184- waitForAllPumpers ( inputFeeder , outputPumper , errorPumper );
185-
186- if ( outputPumper .getException () != null )
206+ // TODO Find out if waitUntilDone needs to be called using a try-finally construct. The method may throw an
207+ // InterruptedException so that calls to waitUntilDone may be skipped.
208+ // try
209+ // {
210+ // if ( inputFeeder != null )
211+ // {
212+ // inputFeeder.waitUntilDone();
213+ // }
214+ // }
215+ // finally
216+ // {
217+ // try
218+ // {
219+ // outputPumper.waitUntilDone();
220+ // }
221+ // finally
222+ // {
223+ // errorPumper.waitUntilDone();
224+ // }
225+ // }
226+ if ( inputFeeder != null )
187227 {
188- throw new CommandLineException ( "Error inside systemOut parser" , outputPumper . getException () );
228+ inputFeeder . waitUntilDone ( );
189229 }
190230
191- if ( errorPumper .getException () != null )
231+ outputPumper .waitUntilDone ();
232+ errorPumper .waitUntilDone ();
233+
234+ if ( inputFeeder != null )
192235 {
193- throw new CommandLineException ( "Error inside systemErr parser" , errorPumper .getException () );
236+ inputFeeder .close ();
237+ handleException ( inputFeeder , "stdin" );
194238 }
195239
240+ outputPumper .close ();
241+ handleException ( outputPumper , "stdout" );
242+
243+ errorPumper .close ();
244+ handleException ( errorPumper , "stderr" );
245+
246+ success = true ;
196247 return returnValue ;
197248 }
198249 catch ( InterruptedException ex )
199250 {
200- if ( inputFeeder != null )
201- {
202- inputFeeder .disable ();
203- }
204- outputPumper .disable ();
205- errorPumper .disable ();
206- throw new CommandLineTimeOutException ( "Error while executing external command, process killed." , ex );
251+ throw new CommandLineTimeOutException ( "Error while executing external command, process killed." ,
252+ ex );
253+
207254 }
208255 finally
209256 {
210- ShutdownHookUtils .removeShutdownHook ( processHook );
211-
212- processHook .run ();
213-
214257 if ( inputFeeder != null )
215258 {
216- inputFeeder .close ();
259+ inputFeeder .disable ();
260+ }
261+ if ( outputPumper != null )
262+ {
263+ outputPumper .disable ();
264+ }
265+ if ( errorPumper != null )
266+ {
267+ errorPumper .disable ();
217268 }
218269
219- outputPumper .close ();
220-
221- errorPumper .close ();
270+ try
271+ {
272+ ShutdownHookUtils .removeShutdownHook ( processHook );
273+ processHook .run ();
274+ }
275+ finally
276+ {
277+ try
278+ {
279+ if ( inputFeeder != null )
280+ {
281+ inputFeeder .close ();
282+
283+ if ( success )
284+ {
285+ success = false ;
286+ handleException ( inputFeeder , "stdin" );
287+ success = true ; // Only reached when no exception has been thrown.
288+ }
289+ }
290+ }
291+ finally
292+ {
293+ try
294+ {
295+ if ( outputPumper != null )
296+ {
297+ outputPumper .close ();
298+
299+ if ( success )
300+ {
301+ success = false ;
302+ handleException ( outputPumper , "stdout" );
303+ success = true ; // Only reached when no exception has been thrown.
304+ }
305+ }
306+ }
307+ finally
308+ {
309+ if ( errorPumper != null )
310+ {
311+ errorPumper .close ();
312+
313+ if ( success )
314+ {
315+ handleException ( errorPumper , "stderr" );
316+ }
317+ }
318+ }
319+ }
320+ }
222321 }
223322 }
323+
224324 };
225325 }
226326
227- private static void waitForAllPumpers ( StreamFeeder inputFeeder , StreamPumper outputPumper ,
228- StreamPumper errorPumper )
229- throws InterruptedException
327+ private static void handleException ( final StreamPumper streamPumper , final String streamName )
328+ throws CommandLineException
230329 {
231- if ( inputFeeder != null )
330+ if ( streamPumper . getException () != null )
232331 {
233- inputFeeder .waitUntilDone ();
332+ throw new CommandLineException ( String .format ( "Failure processing %s." , streamName ),
333+ streamPumper .getException () );
334+
234335 }
336+ }
235337
236- outputPumper .waitUntilDone ();
237- errorPumper .waitUntilDone ();
338+ private static void handleException ( final StreamFeeder streamFeeder , final String streamName )
339+ throws CommandLineException
340+ {
341+ if ( streamFeeder .getException () != null )
342+ {
343+ throw new CommandLineException ( String .format ( "Failure processing %s." , streamName ),
344+ streamFeeder .getException () );
345+
346+ }
238347 }
239348
240349 /**
0 commit comments