18
18
19
19
import java .io .IOException ;
20
20
import java .io .InputStream ;
21
- import java .lang .reflect .Method ;
22
21
import java .util .Locale ;
23
22
import java .util .Map ;
24
23
import java .util .Properties ;
25
24
import java .util .StringTokenizer ;
26
25
import java .util .Vector ;
26
+
27
27
import org .codehaus .plexus .util .Os ;
28
28
import org .codehaus .plexus .util .StringUtils ;
29
29
33
33
*/
34
34
public abstract class CommandLineUtils
35
35
{
36
+
37
+ /**
38
+ * A {@code StreamConsumer} providing consumed lines as a {@code String}.
39
+ *
40
+ * @see #getOutput()
41
+ */
36
42
public static class StringStreamConsumer
37
43
implements StreamConsumer
38
44
{
45
+
39
46
private StringBuffer string = new StringBuffer ();
40
47
41
48
private String ls = System .getProperty ( "line.separator" );
@@ -49,24 +56,18 @@ public String getOutput()
49
56
{
50
57
return string .toString ();
51
58
}
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
- }
63
59
64
- public void run ()
65
- {
66
- process .destroy ();
67
- }
68
60
}
69
61
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 ;
70
71
71
72
public static int executeCommandLine ( Commandline cl , StreamConsumer systemOut , StreamConsumer systemErr )
72
73
throws CommandLineException
@@ -120,10 +121,11 @@ public static int executeCommandLine( Commandline cl, InputStream systemIn, Stre
120
121
* @throws CommandLineException or CommandLineTimeOutException if time out occurs
121
122
* @noinspection ThrowableResultOfMethodCallIgnored
122
123
*/
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 )
127
129
throws CommandLineException
128
130
{
129
131
if ( cl == null )
@@ -133,108 +135,215 @@ public static CommandLineCallable executeCommandLineAsCallable( final Commandlin
133
135
134
136
final Process p = cl .execute ();
135
137
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 ()
144
139
{
145
- inputFeeder .start ();
146
- }
147
140
148
- outputPumper .start ();
141
+ {
142
+ this .setName ( "CommandLineUtils process shutdown hook" );
143
+ this .setContextClassLoader ( null );
144
+ }
149
145
150
- errorPumper .start ();
146
+ @ Override
147
+ public void run ()
148
+ {
149
+ p .destroy ();
150
+ }
151
151
152
- final ProcessHook processHook = new ProcessHook ( p ) ;
152
+ } ;
153
153
154
154
ShutdownHookUtils .addShutDownHook ( processHook );
155
155
156
156
return new CommandLineCallable ()
157
157
{
158
+
158
159
public Integer call ()
159
160
throws CommandLineException
160
161
{
162
+ StreamFeeder inputFeeder = null ;
163
+ StreamPumper outputPumper = null ;
164
+ StreamPumper errorPumper = null ;
165
+ boolean success = false ;
161
166
try
162
167
{
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
+
163
180
int returnValue ;
164
181
if ( timeoutInSeconds <= 0 )
165
182
{
166
183
returnValue = p .waitFor ();
167
184
}
168
185
else
169
186
{
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 ) )
174
191
{
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 );
176
195
}
196
+
177
197
if ( isAlive ( p ) )
178
198
{
179
- throw new InterruptedException ( "Process timeout out after " + timeoutInSeconds + " seconds" );
199
+ throw new InterruptedException ( String .format ( "Process timed out after %d seconds." ,
200
+ timeoutInSeconds ) );
180
201
}
202
+
181
203
returnValue = p .exitValue ();
182
204
}
183
205
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 )
187
227
{
188
- throw new CommandLineException ( "Error inside systemOut parser" , outputPumper . getException () );
228
+ inputFeeder . waitUntilDone ( );
189
229
}
190
230
191
- if ( errorPumper .getException () != null )
231
+ outputPumper .waitUntilDone ();
232
+ errorPumper .waitUntilDone ();
233
+
234
+ if ( inputFeeder != null )
192
235
{
193
- throw new CommandLineException ( "Error inside systemErr parser" , errorPumper .getException () );
236
+ inputFeeder .close ();
237
+ handleException ( inputFeeder , "stdin" );
194
238
}
195
239
240
+ outputPumper .close ();
241
+ handleException ( outputPumper , "stdout" );
242
+
243
+ errorPumper .close ();
244
+ handleException ( errorPumper , "stderr" );
245
+
246
+ success = true ;
196
247
return returnValue ;
197
248
}
198
249
catch ( InterruptedException ex )
199
250
{
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
+
207
254
}
208
255
finally
209
256
{
210
- ShutdownHookUtils .removeShutdownHook ( processHook );
211
-
212
- processHook .run ();
213
-
214
257
if ( inputFeeder != null )
215
258
{
216
- inputFeeder .close ();
259
+ inputFeeder .disable ();
260
+ }
261
+ if ( outputPumper != null )
262
+ {
263
+ outputPumper .disable ();
264
+ }
265
+ if ( errorPumper != null )
266
+ {
267
+ errorPumper .disable ();
217
268
}
218
269
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
+ }
222
321
}
223
322
}
323
+
224
324
};
225
325
}
226
326
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
230
329
{
231
- if ( inputFeeder != null )
330
+ if ( streamPumper . getException () != null )
232
331
{
233
- inputFeeder .waitUntilDone ();
332
+ throw new CommandLineException ( String .format ( "Failure processing %s." , streamName ),
333
+ streamPumper .getException () );
334
+
234
335
}
336
+ }
235
337
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
+ }
238
347
}
239
348
240
349
/**
0 commit comments