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