1 package org.codehaus.plexus.util.cli;
2
3 /*
4 * Copyright The Codehaus Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * 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, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 /***************************************************************************************************
20 * CruiseControl, a Continuous Integration Toolkit Copyright (c) 2001-2003, ThoughtWorks, Inc. 651 W
21 * Washington Ave. Suite 500 Chicago, IL 60661 USA All rights reserved.
22 *
23 * Redistribution and use in source and binary forms, with or without modification, are permitted
24 * provided that the following conditions are met: + Redistributions of source code must retain the
25 * above copyright notice, this list of conditions and the following disclaimer. + Redistributions
26 * in binary form must reproduce the above copyright notice, this list of conditions and the
27 * following disclaimer in the documentation and/or other materials provided with the distribution. +
28 * Neither the name of ThoughtWorks, Inc., CruiseControl, nor the names of its contributors may be
29 * used to endorse or promote products derived from this software without specific prior written
30 * permission.
31 *
32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
33 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
34 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
35 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
37 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 **************************************************************************************************/
41
42 /*
43 * ====================================================================
44 * Copyright 2003-2004 The Apache Software Foundation.
45 *
46 * Licensed under the Apache License, Version 2.0 (the "License");
47 * you may not use this file except in compliance with the License.
48 * You may obtain a copy of the License at
49 *
50 * http://www.apache.org/licenses/LICENSE-2.0
51 *
52 * Unless required by applicable law or agreed to in writing, software
53 * distributed under the License is distributed on an "AS IS" BASIS,
54 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55 * See the License for the specific language governing permissions and
56 * limitations under the License.
57 * ====================================================================
58 */
59
60 import java.io.File;
61 import java.io.IOException;
62 import java.util.Collections;
63 import java.util.LinkedHashMap;
64 import java.util.Map;
65 import java.util.Properties;
66 import java.util.Vector;
67
68 import org.codehaus.plexus.util.Os;
69 import org.codehaus.plexus.util.StringUtils;
70 import org.codehaus.plexus.util.cli.shell.BourneShell;
71 import org.codehaus.plexus.util.cli.shell.CmdShell;
72 import org.codehaus.plexus.util.cli.shell.CommandShell;
73 import org.codehaus.plexus.util.cli.shell.Shell;
74
75 /**
76 * <p>Commandline objects help handling command lines specifying processes to execute.</p>
77 *
78 * <p>The class can be used to define a command line as nested elements or as a helper to define a command line by an
79 * application.</p>
80 *
81 * <code>
82 * <someelement><br>
83 * <acommandline executable="/executable/to/run"><br>
84 * <argument value="argument 1" /><br>
85 * <argument line="argument_1 argument_2 argument_3" /><br>
86 * <argument value="argument 4" /><br>
87 * </acommandline><br>
88 * </someelement><br>
89 * </code>
90 *
91 * <p>The element <code>someelement</code> must provide a method <code>createAcommandline</code> which returns an instance
92 * of this class.</p>
93 *
94 * @author thomas.haas@softwired-inc.com
95 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
96 */
97 public class Commandline implements Cloneable {
98 /**
99 * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
100 */
101 @Deprecated
102 protected static final String OS_NAME = "os.name";
103
104 /**
105 * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
106 */
107 @Deprecated
108 protected static final String WINDOWS = "Windows";
109
110 protected Vector<Arg> arguments = new Vector<>();
111
112 // protected Vector envVars = new Vector();
113 // synchronized added to preserve synchronize of Vector class
114 protected Map<String, String> envVars = Collections.synchronizedMap(new LinkedHashMap<String, String>());
115
116 private long pid = -1;
117
118 private Shell shell;
119
120 /**
121 * @deprecated Use {@link Commandline#setExecutable(String)} instead.
122 */
123 @Deprecated
124 protected String executable;
125
126 /**
127 * @deprecated Use {@link Commandline#setWorkingDirectory(File)} or {@link Commandline#setWorkingDirectory(String)}
128 * instead.
129 */
130 @Deprecated
131 private File workingDir;
132
133 /**
134 * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
135 * generating code for remote execution.
136 *
137 * @param toProcess sh to process
138 * @param shell Shell to use
139 */
140 public Commandline(String toProcess, Shell shell) {
141 this.shell = shell;
142
143 String[] tmp = new String[0];
144 try {
145 tmp = CommandLineUtils.translateCommandline(toProcess);
146 } catch (Exception e) {
147 System.err.println("Error translating Commandline.");
148 }
149 if ((tmp != null) && (tmp.length > 0)) {
150 setExecutable(tmp[0]);
151 for (int i = 1; i < tmp.length; i++) {
152 createArgument().setValue(tmp[i]);
153 }
154 }
155 }
156
157 /**
158 * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
159 * generating code for remote execution.
160 * @param shell the Shell
161 */
162 public Commandline(Shell shell) {
163 this.shell = shell;
164 }
165
166 /**
167 * Create a new command line object, given a command following POSIX sh quoting rules
168 *
169 * @param toProcess the process
170 */
171 public Commandline(String toProcess) {
172 setDefaultShell();
173 String[] tmp = new String[0];
174 try {
175 tmp = CommandLineUtils.translateCommandline(toProcess);
176 } catch (Exception e) {
177 System.err.println("Error translating Commandline.");
178 }
179 if ((tmp != null) && (tmp.length > 0)) {
180 setExecutable(tmp[0]);
181 for (int i = 1; i < tmp.length; i++) {
182 createArgument().setValue(tmp[i]);
183 }
184 }
185 }
186
187 /**
188 * Create a new command line object.
189 */
190 public Commandline() {
191 setDefaultShell();
192 }
193
194 public long getPid() {
195 if (pid == -1) {
196 pid = Long.parseLong(String.valueOf(System.currentTimeMillis()));
197 }
198
199 return pid;
200 }
201
202 public void setPid(long pid) {
203 this.pid = pid;
204 }
205
206 /**
207 * Class to keep track of the position of an Argument.
208 */
209 // <p>This class is there to support the srcfile and targetfile
210 // elements of <execon> and <transform> - don't know
211 // whether there might be additional use cases.</p> --SB
212 public class Marker {
213
214 private int position;
215
216 private int realPos = -1;
217
218 Marker(int position) {
219 this.position = position;
220 }
221
222 /**
223 * @return the number of arguments that preceded this marker.
224 *
225 * <p>The name of the executable - if set - is counted as the very first argument.</p>
226 */
227 public int getPosition() {
228 if (realPos == -1) {
229 realPos = (getLiteralExecutable() == null ? 0 : 1);
230 for (int i = 0; i < position; i++) {
231 Arg arg = arguments.elementAt(i);
232 realPos += arg.getParts().length;
233 }
234 }
235 return realPos;
236 }
237 }
238
239 /**
240 * <p>
241 * Sets the shell or command-line interpreter for the detected operating system, and the shell arguments.
242 * </p>
243 */
244 private void setDefaultShell() {
245 // If this is windows set the shell to command.com or cmd.exe with correct arguments.
246 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
247 if (Os.isFamily(Os.FAMILY_WIN9X)) {
248 setShell(new CommandShell());
249 } else {
250 setShell(new CmdShell());
251 }
252 } else {
253 setShell(new BourneShell());
254 }
255 }
256
257 /**
258 * <p>Creates an argument object.</p>
259 *
260 * <p>Each commandline object has at most one instance of the argument class. This method calls
261 * <code>this.createArgument(false)</code>.</p>
262 *
263 * @return the argument object.
264 * @see #createArgument(boolean)
265 * @deprecated Use {@link Commandline#createArg()} instead
266 */
267 @Deprecated
268 public Argument createArgument() {
269 return this.createArgument(false);
270 }
271
272 /**
273 * <p>Creates an argument object and adds it to our list of args.</p>
274 *
275 * <p>Each commandline object has at most one instance of the argument class.</p>
276 *
277 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
278 * appended.
279 * @deprecated Use {@link Commandline#createArg(boolean)} instead
280 * @return Argument the argument Object
281 */
282 @Deprecated
283 public Argument createArgument(boolean insertAtStart) {
284 Argument argument = new Argument();
285 if (insertAtStart) {
286 arguments.insertElementAt(argument, 0);
287 } else {
288 arguments.addElement(argument);
289 }
290 return argument;
291 }
292
293 /**
294 * <p>Creates an argument object.</p>
295 *
296 * <p>Each commandline object has at most one instance of the argument class. This method calls
297 * <code>this.createArgument(false)</code>.</p>
298 *
299 * @return the argument object.
300 * @see #createArgument(boolean)
301 */
302 public Arg createArg() {
303 return this.createArg(false);
304 }
305
306 /**
307 * @return Creates an argument object and adds it to our list of args.
308 *
309 * <p>Each commandline object has at most one instance of the argument class.</p>
310 *
311 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
312 * appended.
313 */
314 public Arg createArg(boolean insertAtStart) {
315 Arg argument = new Argument();
316 if (insertAtStart) {
317 arguments.insertElementAt(argument, 0);
318 } else {
319 arguments.addElement(argument);
320 }
321 return argument;
322 }
323
324 /**
325 * @param argument the argument
326 * @see #addArg(Arg,boolean)
327 */
328 public void addArg(Arg argument) {
329 this.addArg(argument, false);
330 }
331
332 /**
333 * Adds an argument object to our list of args.
334 * @param argument the argument
335 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
336 * appended.
337 */
338 public void addArg(Arg argument, boolean insertAtStart) {
339 if (insertAtStart) {
340 arguments.insertElementAt(argument, 0);
341 } else {
342 arguments.addElement(argument);
343 }
344 }
345
346 /**
347 * Sets the executable to run.
348 * @param executable the executable
349 */
350 public void setExecutable(String executable) {
351 shell.setExecutable(executable);
352 this.executable = executable;
353 }
354
355 /**
356 * @return Executable to be run, as a literal string (no shell quoting/munging)
357 */
358 public String getLiteralExecutable() {
359 return executable;
360 }
361
362 /**
363 * Return an executable name, quoted for shell use. Shell usage is only desirable when generating code for remote
364 * execution.
365 *
366 * @return Executable to be run, quoted for shell interpretation
367 */
368 public String getExecutable() {
369 String exec = shell.getExecutable();
370
371 if (exec == null) {
372 exec = executable;
373 }
374
375 return exec;
376 }
377
378 public void addArguments(String[] line) {
379 for (String aLine : line) {
380 createArgument().setValue(aLine);
381 }
382 }
383
384 /**
385 * Add an environment variable
386 * @param name name
387 * @param value value
388 */
389 public void addEnvironment(String name, String value) {
390 // envVars.add( name + "=" + value );
391 envVars.put(name, value);
392 }
393
394 /**
395 * Add system environment variables
396 * @throws Exception if error
397 */
398 public void addSystemEnvironment() throws Exception {
399 Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
400
401 for (Object o : systemEnvVars.keySet()) {
402 String key = (String) o;
403 if (!envVars.containsKey(key)) {
404 addEnvironment(key, systemEnvVars.getProperty(key));
405 }
406 }
407 }
408
409 /**
410 * @return String[] Return the list of environment variables
411 * @throws CommandLineException if error
412 */
413 public String[] getEnvironmentVariables() throws CommandLineException {
414 try {
415 addSystemEnvironment();
416 } catch (Exception e) {
417 throw new CommandLineException("Error setting up environmental variables", e);
418 }
419 String[] environmentVars = new String[envVars.size()];
420 int i = 0;
421 for (Object o : envVars.keySet()) {
422 String name = (String) o;
423 String value = envVars.get(name);
424 environmentVars[i] = name + "=" + value;
425 i++;
426 }
427 return environmentVars;
428 }
429
430 /**
431 * @return Returns the executable and all defined arguments.
432 * For Windows Family, {@link Commandline#getShellCommandline()} is returned
433 */
434 public String[] getCommandline() {
435 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
436 return getShellCommandline();
437 }
438
439 return getRawCommandline();
440 }
441
442 /**
443 * Returns the executable and all defined arguments.
444 * @return the command line as array not escaped neither quoted
445 */
446 public String[] getRawCommandline() {
447 final String[] args = getArguments();
448 String executable = getLiteralExecutable();
449
450 if (executable == null) {
451 return args;
452 }
453 final String[] result = new String[args.length + 1];
454 result[0] = executable;
455 System.arraycopy(args, 0, result, 1, args.length);
456 return result;
457 }
458
459 /**
460 * Returns the shell, executable and all defined arguments. Shell usage is only desirable when generating code for
461 * remote execution.
462 * @return the command line as array
463 */
464 public String[] getShellCommandline() {
465 // TODO: Provided only for backward compat. with <= 1.4
466 verifyShellState();
467
468 return getShell().getShellCommandLine(getArguments()).toArray(new String[0]);
469 }
470
471 /**
472 * @return Returns all arguments defined by <code>addLine</code>, <code>addValue</code> or the argument object.
473 */
474 public String[] getArguments() {
475 Vector<String> result = new Vector<>(arguments.size() * 2);
476 for (int i = 0; i < arguments.size(); i++) {
477 Arg arg = arguments.elementAt(i);
478 String[] s = arg.getParts();
479 if (s != null) {
480 for (String value : s) {
481 result.addElement(value);
482 }
483 }
484 }
485
486 String[] res = new String[result.size()];
487 result.copyInto(res);
488 return res;
489 }
490
491 @Override
492 public String toString() {
493 return StringUtils.join(getShellCommandline(), " ");
494 }
495
496 public int size() {
497 return getCommandline().length;
498 }
499
500 @Override
501 public Object clone() {
502 Commandline c = new Commandline((Shell) shell.clone());
503 c.executable = executable;
504 c.workingDir = workingDir;
505 c.addArguments(getArguments());
506 return c;
507 }
508
509 /**
510 * Clear out the whole command line.
511 */
512 public void clear() {
513 executable = null;
514 workingDir = null;
515 shell.setExecutable(null);
516 shell.clearArguments();
517 arguments.removeAllElements();
518 }
519
520 /**
521 * Clear out the arguments but leave the executable in place for another operation.
522 */
523 public void clearArgs() {
524 arguments.removeAllElements();
525 }
526
527 /**
528 *
529 * <p>This marker can be used to locate a position on the commandline - to insert something for example - when all
530 * parameters have been set.
531 * </p>
532 * @return Return a marker.
533 */
534 public Marker createMarker() {
535 return new Marker(arguments.size());
536 }
537
538 /**
539 * Sets execution directory.
540 * @param path the working directory as String
541 */
542 public void setWorkingDirectory(String path) {
543 shell.setWorkingDirectory(path);
544 workingDir = new File(path);
545 }
546
547 /**
548 * Sets execution directory.
549 * @param workingDirectory the File used as working directory
550 */
551 public void setWorkingDirectory(File workingDirectory) {
552 shell.setWorkingDirectory(workingDirectory);
553 workingDir = workingDirectory;
554 }
555
556 public File getWorkingDirectory() {
557 File workDir = shell.getWorkingDirectory();
558
559 if (workDir == null) {
560 workDir = workingDir;
561 }
562
563 return workDir;
564 }
565
566 /**
567 * Executes the command.
568 * @return the Process
569 * @throws CommandLineException if error
570 */
571 public Process execute() throws CommandLineException {
572 // TODO: Provided only for backward compat. with <= 1.4
573 verifyShellState();
574
575 Process process;
576
577 // addEnvironment( "MAVEN_TEST_ENVAR", "MAVEN_TEST_ENVAR_VALUE" );
578
579 String[] environment = getEnvironmentVariables();
580
581 File workingDir = shell.getWorkingDirectory();
582
583 try {
584 if (workingDir == null) {
585 process = Runtime.getRuntime().exec(getCommandline(), environment, workingDir);
586 } else {
587 if (!workingDir.exists()) {
588 throw new CommandLineException(
589 "Working directory \"" + workingDir.getPath() + "\" does not exist!");
590 } else if (!workingDir.isDirectory()) {
591 throw new CommandLineException(
592 "Path \"" + workingDir.getPath() + "\" does not specify a directory.");
593 }
594
595 process = Runtime.getRuntime().exec(getCommandline(), environment, workingDir);
596 }
597 } catch (IOException ex) {
598 throw new CommandLineException("Error while executing process.", ex);
599 }
600
601 return process;
602 }
603
604 /**
605 * @deprecated Remove once backward compat with plexus-utils <= 1.4 is no longer a consideration
606 */
607 @Deprecated
608 private void verifyShellState() {
609 if (shell.getWorkingDirectory() == null) {
610 shell.setWorkingDirectory(workingDir);
611 }
612
613 if (shell.getOriginalExecutable() == null) {
614 shell.setExecutable(executable);
615 }
616 }
617
618 public Properties getSystemEnvVars() throws Exception {
619 return CommandLineUtils.getSystemEnvVars();
620 }
621
622 /**
623 * Allows to set the shell to be used in this command line. Shell usage is only desirable when generating code for
624 * remote execution.
625 *
626 * @param shell Shell to use
627 * @since 1.2
628 */
629 public void setShell(Shell shell) {
630 this.shell = shell;
631 }
632
633 /**
634 * Get the shell to be used in this command line. Shell usage is only desirable when generating code for remote
635 * execution.
636 *
637 * @since 1.2
638 * @return the Shell
639 */
640 public Shell getShell() {
641 return shell;
642 }
643
644 /**
645 * @param toProcess the process
646 * @return the command line arguments
647 * @throws Exception if error happen
648 * @deprecated Use {@link CommandLineUtils#translateCommandline(String)} instead.
649 */
650 @Deprecated
651 public static String[] translateCommandline(String toProcess) throws Exception {
652 return CommandLineUtils.translateCommandline(toProcess);
653 }
654
655 /**
656 * @param argument the argument
657 * @return the quote arg
658 * @throws CommandLineException if error happen
659 * @deprecated Use {@link CommandLineUtils#quote(String)} instead.
660 */
661 @Deprecated
662 public static String quoteArgument(String argument) throws CommandLineException {
663 return CommandLineUtils.quote(argument);
664 }
665
666 /**
667 * @deprecated Use {@link CommandLineUtils#toString(String[])} instead.
668 * @param line the lines
669 * @return lines as single String
670 */
671 @Deprecated
672 public static String toString(String[] line) {
673 return CommandLineUtils.toString(line);
674 }
675
676 public static class Argument implements Arg {
677 private String[] parts;
678
679 /*
680 * (non-Javadoc)
681 * @see org.codehaus.plexus.util.cli.Argument#setValue(java.lang.String)
682 */
683 @Override
684 public void setValue(String value) {
685 if (value != null) {
686 parts = new String[] {value};
687 }
688 }
689
690 /*
691 * (non-Javadoc)
692 * @see org.codehaus.plexus.util.cli.Argument#setLine(java.lang.String)
693 */
694 @Override
695 public void setLine(String line) {
696 if (line == null) {
697 return;
698 }
699 try {
700 parts = CommandLineUtils.translateCommandline(line);
701 } catch (Exception e) {
702 System.err.println("Error translating Commandline.");
703 }
704 }
705
706 /*
707 * (non-Javadoc)
708 * @see org.codehaus.plexus.util.cli.Argument#setFile(java.io.File)
709 */
710 @Override
711 public void setFile(File value) {
712 parts = new String[] {value.getAbsolutePath()};
713 }
714
715 /*
716 * (non-Javadoc)
717 * @see org.codehaus.plexus.util.cli.Argument#getParts()
718 */
719 @Override
720 public String[] getParts() {
721 return parts;
722 }
723 }
724 }