1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.shared.utils.cli;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.Vector;
31
32 import org.apache.maven.shared.utils.Os;
33 import org.apache.maven.shared.utils.StringUtils;
34 import org.apache.maven.shared.utils.cli.shell.BourneShell;
35 import org.apache.maven.shared.utils.cli.shell.CmdShell;
36 import org.apache.maven.shared.utils.cli.shell.Shell;
37
38 /**
39 * <p>
40 * Commandline objects help handling command lines specifying processes to
41 * execute.
42 * </p>
43 * <p>
44 * The class can be used to define a command line as nested elements or as a
45 * helper to define a command line by an application.
46 * </p>
47 * <code>
48 * <someelement><br>
49 * <acommandline executable="/executable/to/run"><br>
50 * <argument value="argument 1" /><br>
51 * <argument line="argument_1 argument_2 argument_3" /><br>
52 * <argument value="argument 4" /><br>
53 * </acommandline><br>
54 * </someelement><br>
55 * </code>
56 * <p>
57 * The element <code>someelement</code> must provide a method
58 * <code>createAcommandline</code> which returns an instance of this class.
59 * </p>
60 *
61 * @author thomas.haas@softwired-inc.com
62 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
63 */
64 public class Commandline implements Cloneable {
65 private final List<Arg> arguments = new Vector<>();
66
67 private final Map<String, String> envVars = Collections.synchronizedMap(new LinkedHashMap<String, String>());
68
69 private Shell shell;
70
71 private boolean shellEnvironmentInherited = true;
72
73 /**
74 * Create a new command line object.
75 * Shell is autodetected from operating system.
76 *
77 * @param shell the shell instance
78 */
79 public Commandline(Shell shell) {
80 this.shell = shell;
81 }
82
83 /**
84 * Create a new command line object.
85 * Shell is autodetected from operating system.
86 *
87 * @param toProcess the command to process
88 * @throws CommandLineException in case of unbalanced quotes.
89 */
90 public Commandline(String toProcess) throws CommandLineException {
91 setDefaultShell();
92 String[] tmp = CommandLineUtils.translateCommandline(toProcess);
93 if ((tmp.length > 0)) {
94 setExecutable(tmp[0]);
95 for (int i = 1; i < tmp.length; i++) {
96 createArg().setValue(tmp[i]);
97 }
98 }
99 }
100
101 /**
102 * Create a new command line object.
103 * Shell is autodetected from operating system.
104 */
105 public Commandline() {
106 setDefaultShell();
107 }
108
109 /**
110 * <p>Sets the shell or command-line interpreter for the detected operating system,
111 * and the shell arguments.</p>
112 */
113 private void setDefaultShell() {
114 // If this is windows set the shell to command.com or cmd.exe with correct arguments.
115 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
116 setShell(new CmdShell());
117 } else {
118 setShell(new BourneShell());
119 }
120 }
121
122 /**
123 * Creates an empty argument object and inserts it at the end of the argument list.
124 *
125 * @return the argument object
126 */
127 public Arg createArg() {
128 return this.createArg(false);
129 }
130
131 /**
132 * Creates an argument object and adds it to the list of args.
133 *
134 * @param insertAtStart if true, the argument is inserted at the
135 * beginning of the list of args. Otherwise it is appended.
136 * @return the argument
137 */
138 public Arg createArg(boolean insertAtStart) {
139 Arg argument = new Argument();
140 if (insertAtStart) {
141 arguments.add(0, argument);
142 } else {
143 arguments.add(argument);
144 }
145 return argument;
146 }
147
148 /**
149 * Sets the executable to run.
150 *
151 * @param executable the executable
152 */
153 public void setExecutable(String executable) {
154 shell.setExecutable(executable);
155 }
156
157 /**
158 * @return the executable
159 */
160 public String getExecutable() {
161
162 return shell.getExecutable();
163 }
164
165 /**
166 * @param line the arguments
167 */
168 public void addArguments(String... line) {
169 for (String aLine : line) {
170 createArg().setValue(aLine);
171 }
172 }
173
174 /**
175 * Add an environment variable.
176 *
177 * @param name the name of the environment variable
178 * @param value the appropriate value
179 */
180 public void addEnvironment(String name, String value) {
181 envVars.put(name, value);
182 }
183
184 /**
185 * Add system environment variables.
186 *
187 * @deprecated please use {@link #setShellEnvironmentInherited(boolean)}
188 */
189 @Deprecated
190 public void addSystemEnvironment() {}
191
192 private void copySystemEnvironment() {
193 Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
194
195 for (Object o : systemEnvVars.keySet()) {
196 String key = (String) o;
197 if (!envVars.containsKey(key)) {
198 addEnvironment(key, systemEnvVars.getProperty(key));
199 }
200 }
201 }
202
203 /**
204 * Return the list of environment variables.
205 *
206 * @return an array of all environment variables
207 */
208 public String[] getEnvironmentVariables() {
209 if (isShellEnvironmentInherited()) {
210 copySystemEnvironment();
211 }
212
213 List<String> environmentVars = new ArrayList<>();
214 for (String name : envVars.keySet()) {
215 String value = envVars.get(name);
216 if (value != null) {
217 environmentVars.add(name + "=" + value);
218 }
219 }
220 return environmentVars.toArray(new String[0]);
221 }
222
223 /**
224 * Returns the executable and all defined arguments.
225 *
226 * @return an array of all arguments including the executable
227 */
228 public String[] getCommandline() {
229 final String[] args = getArguments();
230 String executable = getExecutable();
231
232 if (executable == null) {
233 return args;
234 }
235 final String[] result = new String[args.length + 1];
236 result[0] = executable;
237 System.arraycopy(args, 0, result, 1, args.length);
238 return result;
239 }
240
241 /**
242 * @return the shell, executable and all defined arguments without masking any arguments
243 */
244 private String[] getShellCommandline() {
245 return getShellCommandline(false);
246 }
247
248 /**
249 * @param mask flag to mask any arguments (having his {@code mask} field to {@code true})
250 * @return the shell, executable and all defined arguments with masking some arguments if
251 * {@code mask} parameter is on
252 */
253 private String[] getShellCommandline(boolean mask) {
254 List<String> shellCommandLine = getShell().getShellCommandLine(getArguments(mask));
255 return shellCommandLine.toArray(new String[shellCommandLine.size()]);
256 }
257
258 /**
259 * Returns all arguments defined by <code>addLine</code>,
260 * <code>addValue</code> or the argument object.
261 * @return an array of arguments.
262 */
263 public String[] getArguments() {
264 return getArguments(false);
265 }
266
267 /**
268 * Returns all arguments defined by <code>addLine</code>,
269 * <code>addValue</code>, or the argument object.
270 *
271 * @param mask flag to mask any arguments (having his {@code mask} field to {@code true})
272 * @return an array of arguments
273 */
274 public String[] getArguments(boolean mask) {
275 List<String> result = new ArrayList<>(arguments.size() * 2);
276 for (Arg argument : arguments) {
277 Argument arg = (Argument) argument;
278 String[] s = arg.getParts();
279 if (s != null) {
280 if (mask && (arg.isMask())) {
281 // should be a key-pair argument
282 if (s.length > 0) {
283
284 // use a masked copy
285 String[] copy = new String[s.length];
286 Arrays.fill(copy, "*****");
287 s = copy;
288 }
289 }
290 Collections.addAll(result, s);
291 }
292 }
293
294 return result.toArray(new String[result.size()]);
295 }
296
297 /** {@inheritDoc}
298 */
299 public String toString() {
300 return StringUtils.join(getShellCommandline(true), " ");
301 }
302
303 /** {@inheritDoc}
304 */
305 public Object clone() {
306 throw new RuntimeException("Do we ever clone a commandline?");
307 /* Commandline c = new Commandline( (Shell) shell.clone() );
308 c.addArguments( getArguments() );
309 return c;*/
310 }
311
312 /**
313 * Sets working directory.
314 *
315 * @param path the working directory
316 */
317 public void setWorkingDirectory(String path) {
318 shell.setWorkingDirectory(path);
319 }
320
321 /**
322 * Sets working directory.
323 *
324 * @param workingDirectory the working directory
325 */
326 public void setWorkingDirectory(File workingDirectory) {
327 shell.setWorkingDirectory(workingDirectory);
328 }
329
330 /**
331 * @return the working directory
332 */
333 public File getWorkingDirectory() {
334 return shell.getWorkingDirectory();
335 }
336
337 /**
338 * Clear out the arguments but leave the executable in place for another operation.
339 */
340 public void clearArgs() {
341 arguments.clear();
342 }
343
344 /**
345 * Indicates whether the environment variables of the current process
346 * should are propagated to the executing Command.
347 * By default, the current environment variables are inherited by the new Command line execution.
348 *
349 * @return <code>true</code> if the environment variables should be propagated, <code>false</code> otherwise.
350 */
351 public boolean isShellEnvironmentInherited() {
352 return shellEnvironmentInherited;
353 }
354
355 /**
356 * Specifies whether the environment variables of the current process should be propagated to the executing Command.
357 *
358 * @param shellEnvironmentInherited <code>true</code> if the environment variables should be propagated,
359 * <code>false</code> otherwise.
360 */
361 public void setShellEnvironmentInherited(boolean shellEnvironmentInherited) {
362 this.shellEnvironmentInherited = shellEnvironmentInherited;
363 }
364
365 /**
366 * Execute the command.
367 *
368 * @return the process
369 * @throws CommandLineException in case of errors
370 */
371 public Process execute() throws CommandLineException {
372 Process process;
373
374 String[] environment = getEnvironmentVariables();
375
376 File workingDir = shell.getWorkingDirectory();
377
378 try {
379 if (workingDir == null) {
380 process = Runtime.getRuntime().exec(getShellCommandline(), environment);
381 } else {
382 if (!workingDir.exists()) {
383 throw new CommandLineException(
384 "Working directory \"" + workingDir.getPath() + "\" does not exist!");
385 } else if (!workingDir.isDirectory()) {
386 throw new CommandLineException(
387 "Path \"" + workingDir.getPath() + "\" does not specify a directory.");
388 }
389
390 process = Runtime.getRuntime().exec(getShellCommandline(), environment, workingDir);
391 }
392 } catch (IOException ex) {
393 throw new CommandLineException("Error while executing process.", ex);
394 }
395
396 return process;
397 }
398
399 /**
400 * Set the shell to be used for this command line.
401 *
402 * @param shell the shell
403 */
404 void setShell(Shell shell) {
405 this.shell = shell;
406 }
407
408 /**
409 * Get the shell to be used in this command line.
410 *
411 * @return the shell
412 */
413 public Shell getShell() {
414 return shell;
415 }
416
417 /**
418 * A single command line argument
419 */
420 public static class Argument implements Arg {
421 private String[] parts;
422
423 private boolean mask;
424
425 /**
426 * {@inheritDoc}
427 */
428 public void setValue(String value) {
429 if (value != null) {
430 parts = new String[] {value};
431 }
432 }
433
434 /**
435 * {@inheritDoc}
436 */
437 public void setLine(String line) throws CommandLineException {
438 if (line == null) {
439 return;
440 }
441 parts = CommandLineUtils.translateCommandline(line);
442 }
443
444 /**
445 * {@inheritDoc}
446 */
447 public void setFile(File value) {
448 parts = new String[] {value.getAbsolutePath()};
449 }
450
451 /**
452 * {@inheritDoc}
453 */
454 public void setMask(boolean mask) {
455 this.mask = mask;
456 }
457
458 /**
459 * @return the parts
460 */
461 private String[] getParts() {
462 return parts;
463 }
464
465 /**
466 * @return true/false
467 */
468 public boolean isMask() {
469 return mask;
470 }
471 }
472 }