View Javadoc
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   * &lt;someelement&gt;<br>
83   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
84   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
85   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
86   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
87   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
88   * &lt;/someelement&gt;<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 &lt;execon&gt; and &lt;transform&gt; - 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 }