View Javadoc
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   * &lt;someelement&gt;<br>
49   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
50   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
51   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
52   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
53   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
54   * &lt;/someelement&gt;<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 }