1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.exec;
19
20 import java.io.File;
21 import java.nio.file.Path;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.StringTokenizer;
27 import java.util.Vector;
28
29 import org.apache.commons.exec.util.StringUtils;
30
31
32
33
34 public class CommandLine {
35
36
37
38
39 static final class Argument {
40
41 private final String value;
42 private final boolean handleQuoting;
43
44 private Argument(final String value, final boolean handleQuoting) {
45 this.value = value.trim();
46 this.handleQuoting = handleQuoting;
47 }
48
49 private String getValue() {
50 return value;
51 }
52
53 private boolean isHandleQuoting() {
54 return handleQuoting;
55 }
56 }
57
58
59
60
61
62
63
64
65 public static CommandLine parse(final String line) {
66 return parse(line, null);
67 }
68
69
70
71
72
73
74
75
76
77 public static CommandLine parse(final String line, final Map<String, ?> substitutionMap) {
78
79 if (line == null) {
80 throw new IllegalArgumentException("Command line cannot be null");
81 }
82 if (line.trim().isEmpty()) {
83 throw new IllegalArgumentException("Command line cannot be empty");
84 }
85 final String[] tmp = translateCommandline(line);
86
87 final CommandLine cl = new CommandLine(tmp[0]);
88 cl.setSubstitutionMap(substitutionMap);
89 for (int i = 1; i < tmp.length; i++) {
90 cl.addArgument(tmp[i]);
91 }
92
93 return cl;
94 }
95
96
97
98
99
100
101
102 private static String[] translateCommandline(final String toProcess) {
103 if (toProcess == null || toProcess.trim().isEmpty()) {
104
105 return new String[0];
106 }
107
108
109
110 final int normal = 0;
111 final int inQuote = 1;
112 final int inDoubleQuote = 2;
113 int state = normal;
114 final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
115 final ArrayList<String> list = new ArrayList<>();
116 StringBuilder current = new StringBuilder();
117 boolean lastTokenHasBeenQuoted = false;
118
119 while (tok.hasMoreTokens()) {
120 final String nextTok = tok.nextToken();
121 switch (state) {
122 case inQuote:
123 if ("\'".equals(nextTok)) {
124 lastTokenHasBeenQuoted = true;
125 state = normal;
126 } else {
127 current.append(nextTok);
128 }
129 break;
130 case inDoubleQuote:
131 if ("\"".equals(nextTok)) {
132 lastTokenHasBeenQuoted = true;
133 state = normal;
134 } else {
135 current.append(nextTok);
136 }
137 break;
138 default:
139 switch (nextTok) {
140 case "\'":
141 state = inQuote;
142 break;
143 case "\"":
144 state = inDoubleQuote;
145 break;
146 case " ":
147 if (lastTokenHasBeenQuoted || current.length() != 0) {
148 list.add(current.toString());
149 current = new StringBuilder();
150 }
151 break;
152 default:
153 current.append(nextTok);
154 break;
155 }
156 lastTokenHasBeenQuoted = false;
157 break;
158 }
159 }
160
161 if (lastTokenHasBeenQuoted || current.length() != 0) {
162 list.add(current.toString());
163 }
164
165 if (state == inQuote || state == inDoubleQuote) {
166 throw new IllegalArgumentException("Unbalanced quotes in " + toProcess);
167 }
168
169 final String[] args = new String[list.size()];
170 return list.toArray(args);
171 }
172
173
174
175
176 private final Vector<Argument> arguments = new Vector<>();
177
178
179
180
181 private final String executable;
182
183
184
185
186 private Map<String, ?> substitutionMap;
187
188
189
190
191 private final boolean isFile;
192
193
194
195
196
197
198 public CommandLine(final CommandLine other) {
199 this.executable = other.getExecutable();
200 this.isFile = other.isFile();
201 this.arguments.addAll(other.arguments);
202
203 if (other.getSubstitutionMap() != null) {
204 this.substitutionMap = new HashMap<>(other.getSubstitutionMap());
205 }
206 }
207
208
209
210
211
212
213 public CommandLine(final File executable) {
214 this.isFile = true;
215 this.executable = toCleanExecutable(executable.getAbsolutePath());
216 }
217
218
219
220
221
222
223
224 public CommandLine(final Path executable) {
225 this.isFile = true;
226 this.executable = toCleanExecutable(executable.toAbsolutePath().toString());
227 }
228
229
230
231
232
233
234
235
236 public CommandLine(final String executable) {
237 this.isFile = false;
238 this.executable = toCleanExecutable(executable);
239 }
240
241
242
243
244
245
246
247
248 public CommandLine addArgument(final String argument) {
249 return addArgument(argument, true);
250 }
251
252
253
254
255
256
257
258
259 public CommandLine addArgument(final String argument, final boolean handleQuoting) {
260
261 if (argument == null) {
262 return this;
263 }
264
265
266
267 if (handleQuoting) {
268 StringUtils.quoteArgument(argument);
269 }
270
271 arguments.add(new Argument(argument, handleQuoting));
272 return this;
273 }
274
275
276
277
278
279
280
281
282 public CommandLine addArguments(final String addArguments) {
283 return addArguments(addArguments, true);
284 }
285
286
287
288
289
290
291
292
293
294 public CommandLine addArguments(final String addArguments, final boolean handleQuoting) {
295 if (addArguments != null) {
296 final String[] argumentsArray = translateCommandline(addArguments);
297 addArguments(argumentsArray, handleQuoting);
298 }
299
300 return this;
301 }
302
303
304
305
306
307
308
309 public CommandLine addArguments(final String[] addArguments) {
310 return addArguments(addArguments, true);
311 }
312
313
314
315
316
317
318
319
320 public CommandLine addArguments(final String[] addArguments, final boolean handleQuoting) {
321 if (addArguments != null) {
322 for (final String addArgument : addArguments) {
323 addArgument(addArgument, handleQuoting);
324 }
325 }
326 return this;
327 }
328
329
330
331
332
333
334
335 private String expandArgument(final String argument) {
336 final StringBuffer stringBuffer = StringUtils.stringSubstitution(argument, getSubstitutionMap(), true);
337 return stringBuffer.toString();
338 }
339
340
341
342
343
344
345 public String[] getArguments() {
346
347 Argument currArgument;
348 String expandedArgument;
349 final String[] result = new String[arguments.size()];
350
351 for (int i = 0; i < result.length; i++) {
352 currArgument = arguments.get(i);
353 expandedArgument = expandArgument(currArgument.getValue());
354 result[i] = currArgument.isHandleQuoting() ? StringUtils.quoteArgument(expandedArgument) : expandedArgument;
355 }
356
357 return result;
358 }
359
360
361
362
363
364
365 public String getExecutable() {
366
367
368
369 return StringUtils.fixFileSeparatorChar(expandArgument(executable));
370 }
371
372
373
374
375
376
377 public Map<String, ?> getSubstitutionMap() {
378 return substitutionMap;
379 }
380
381
382
383
384
385
386 public boolean isFile() {
387 return isFile;
388 }
389
390
391
392
393
394
395 public void setSubstitutionMap(final Map<String, ?> substitutionMap) {
396 this.substitutionMap = substitutionMap;
397 }
398
399
400
401
402
403
404
405
406
407 private String toCleanExecutable(final String dirtyExecutable) {
408 Objects.requireNonNull(dirtyExecutable, "dirtyExecutable");
409 if (dirtyExecutable.trim().isEmpty()) {
410 throw new IllegalArgumentException("Executable cannot be empty");
411 }
412 return StringUtils.fixFileSeparatorChar(dirtyExecutable);
413 }
414
415
416
417
418
419
420
421 @Override
422 public String toString() {
423 return "[" + String.join(", ", toStrings()) + "]";
424 }
425
426
427
428
429
430
431 public String[] toStrings() {
432 final String[] result = new String[arguments.size() + 1];
433 result[0] = getExecutable();
434 System.arraycopy(getArguments(), 0, result, 1, result.length - 1);
435 return result;
436 }
437 }