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