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.shell;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import org.apache.maven.shared.utils.StringUtils;
27  
28  /**
29   * Class that abstracts the Shell functionality,
30   * with subclasses for shells that behave particularly, like
31   *
32   * <ul>
33   * <li><code>command.com</code></li>
34   * <li><code>cmd.exe</code></li>
35   * </ul>
36   *
37   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
38   *
39   */
40  public class Shell implements Cloneable {
41      private static final char[] DEFAULT_QUOTING_TRIGGER_CHARS = {' '};
42  
43      private String shellCommand;
44  
45      private final List<String> shellArgs = new ArrayList<String>();
46  
47      private boolean quotedArgumentsEnabled = true;
48  
49      private boolean unconditionalQuoting = false;
50  
51      private String executable;
52  
53      private String workingDir;
54  
55      private boolean quotedExecutableEnabled = true;
56  
57      private boolean singleQuotedArgumentEscaped = false;
58  
59      private boolean singleQuotedExecutableEscaped = false;
60  
61      private char argQuoteDelimiter = '\"';
62  
63      private char exeQuoteDelimiter = '\"';
64  
65      /**
66       * Set the command to execute the shell (e.g. COMMAND.COM, /bin/bash,...).
67       *
68       * @param shellCommand the command
69       */
70      void setShellCommand(String shellCommand) {
71          this.shellCommand = shellCommand;
72      }
73  
74      /**
75       * Get the command to execute the shell.
76       *
77       * @return the command
78       */
79      String getShellCommand() {
80          return shellCommand;
81      }
82  
83      /**
84       * Set the shell arguments when calling a command line (not the executable arguments)
85       * (e.g. /X /C for CMD.EXE).
86       *
87       * @param shellArgs the arguments to the shell
88       */
89      void setShellArgs(String[] shellArgs) {
90          this.shellArgs.clear();
91          this.shellArgs.addAll(Arrays.asList(shellArgs));
92      }
93  
94      /**
95       * Get the shell arguments
96       *
97       * @return the arguments
98       */
99      String[] getShellArgs() {
100         if (shellArgs.isEmpty()) {
101             return null;
102         } else {
103             return shellArgs.toArray(new String[0]);
104         }
105     }
106 
107     protected String quoteOneItem(String inputString, boolean isExecutable) {
108         char[] escapeChars = getEscapeChars(isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped());
109         return StringUtils.quoteAndEscape(
110                 inputString,
111                 isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
112                 escapeChars,
113                 getQuotingTriggerChars(),
114                 '\\',
115                 unconditionalQuoting);
116     }
117 
118     /**
119      * Get the command line for the provided executable and arguments in this shell
120      *
121      * @param executableParameter executable that the shell has to call
122      * @param argumentsParameter  arguments for the executable, not the shell
123      * @return list with one String object with executable and arguments quoted as needed
124      */
125     List<String> getCommandLine(String executableParameter, String... argumentsParameter) {
126         return getRawCommandLine(executableParameter, argumentsParameter);
127     }
128 
129     /**
130      * @param executableParameter Executable
131      * @param argumentsParameter the arguments for the executable
132      * @return the list on command line
133      */
134     List<String> getRawCommandLine(String executableParameter, String... argumentsParameter) {
135         List<String> commandLine = new ArrayList<>();
136         StringBuilder sb = new StringBuilder();
137 
138         if (executableParameter != null) {
139             String preamble = getExecutionPreamble();
140             if (preamble != null) {
141                 sb.append(preamble);
142             }
143 
144             if (isQuotedExecutableEnabled()) {
145                 sb.append(quoteOneItem(executableParameter, true));
146             } else {
147                 sb.append(executableParameter);
148             }
149         }
150         for (String argument : argumentsParameter) {
151             if (sb.length() > 0) {
152                 sb.append(' ');
153             }
154 
155             if (isQuotedArgumentsEnabled()) {
156                 sb.append(quoteOneItem(argument, false));
157             } else {
158                 sb.append(argument);
159             }
160         }
161 
162         commandLine.add(sb.toString());
163 
164         return commandLine;
165     }
166 
167     char[] getQuotingTriggerChars() {
168         return DEFAULT_QUOTING_TRIGGER_CHARS;
169     }
170 
171     String getExecutionPreamble() {
172         return null;
173     }
174 
175     char[] getEscapeChars(boolean includeSingleQuote, boolean includeDoubleQuote) {
176         StringBuilder buf = new StringBuilder(2);
177         if (includeSingleQuote) {
178             buf.append('\'');
179         }
180 
181         if (includeDoubleQuote) {
182             buf.append('\"');
183         }
184 
185         char[] result = new char[buf.length()];
186         buf.getChars(0, buf.length(), result, 0);
187 
188         return result;
189     }
190 
191     /**
192      * @return false in all cases
193      */
194     protected boolean isDoubleQuotedArgumentEscaped() {
195         return false;
196     }
197 
198     /**
199      * @return {@link #singleQuotedArgumentEscaped}
200      */
201     protected boolean isSingleQuotedArgumentEscaped() {
202         return singleQuotedArgumentEscaped;
203     }
204 
205     boolean isDoubleQuotedExecutableEscaped() {
206         return false;
207     }
208 
209     boolean isSingleQuotedExecutableEscaped() {
210         return singleQuotedExecutableEscaped;
211     }
212 
213     /**
214      * @param argQuoteDelimiterParameter {@link #argQuoteDelimiter}
215      */
216     void setArgumentQuoteDelimiter(char argQuoteDelimiterParameter) {
217         this.argQuoteDelimiter = argQuoteDelimiterParameter;
218     }
219 
220     char getArgumentQuoteDelimiter() {
221         return argQuoteDelimiter;
222     }
223 
224     /**
225      * @param exeQuoteDelimiterParameter {@link #exeQuoteDelimiter}
226      */
227     void setExecutableQuoteDelimiter(char exeQuoteDelimiterParameter) {
228         this.exeQuoteDelimiter = exeQuoteDelimiterParameter;
229     }
230 
231     char getExecutableQuoteDelimiter() {
232         return exeQuoteDelimiter;
233     }
234 
235     /**
236      * Get the full command line to execute, including shell command, shell arguments,
237      * executable and executable arguments
238      *
239      * @param arguments arguments for the executable, not the shell
240      * @return List of String objects, whose array version is suitable to be used as argument
241      *         of Runtime.getRuntime().exec()
242      */
243     public List<String> getShellCommandLine(String... arguments) {
244 
245         List<String> commandLine = new ArrayList<>();
246 
247         if (getShellCommand() != null) {
248             commandLine.add(getShellCommand());
249         }
250 
251         if (getShellArgs() != null) {
252             commandLine.addAll(getShellArgsList());
253         }
254 
255         commandLine.addAll(getCommandLine(executable, arguments));
256 
257         return commandLine;
258     }
259 
260     List<String> getShellArgsList() {
261         return shellArgs;
262     }
263 
264     /**
265      * @param quotedArgumentsEnabled {@link #quotedArgumentsEnabled}
266      */
267     public void setQuotedArgumentsEnabled(boolean quotedArgumentsEnabled) {
268         this.quotedArgumentsEnabled = quotedArgumentsEnabled;
269     }
270 
271     boolean isQuotedArgumentsEnabled() {
272         return quotedArgumentsEnabled;
273     }
274 
275     void setQuotedExecutableEnabled(boolean quotedExecutableEnabled) {
276         this.quotedExecutableEnabled = quotedExecutableEnabled;
277     }
278 
279     boolean isQuotedExecutableEnabled() {
280         return quotedExecutableEnabled;
281     }
282 
283     /**
284      * Sets the executable to run.
285      * @param executable The executable.
286      */
287     public void setExecutable(String executable) {
288         if ((executable == null) || (executable.length() == 0)) {
289             return;
290         }
291         this.executable = executable.replace('/', File.separatorChar).replace('\\', File.separatorChar);
292     }
293 
294     /**
295      * @return The executable.
296      */
297     public String getExecutable() {
298         return executable;
299     }
300 
301     /**
302      * Sets execution directory.
303      * @param path The path which should be used as working directory.
304      */
305     public void setWorkingDirectory(String path) {
306         if (path != null) {
307             this.workingDir = path;
308         }
309     }
310 
311     /**
312      * Sets execution directory.
313      *
314      * @param workingDirectory the working directory
315      */
316     public void setWorkingDirectory(File workingDirectory) {
317         if (workingDirectory != null) {
318             this.workingDir = workingDirectory.getAbsolutePath();
319         }
320     }
321 
322     /**
323      * @return the working directory
324      */
325     public File getWorkingDirectory() {
326         return workingDir == null ? null : new File(workingDir);
327     }
328 
329     String getWorkingDirectoryAsString() {
330         return workingDir;
331     }
332 
333     /** {@inheritDoc} */
334     public Object clone() {
335         throw new RuntimeException("Do we ever clone this?");
336         /*        Shell shell = new Shell();
337         shell.setExecutable( getExecutable() );
338         shell.setWorkingDirectory( getWorkingDirectory() );
339         shell.setShellArgs( getShellArgs() );
340         return shell;*/
341     }
342 
343     void setSingleQuotedArgumentEscaped(boolean singleQuotedArgumentEscaped) {
344         this.singleQuotedArgumentEscaped = singleQuotedArgumentEscaped;
345     }
346 
347     void setSingleQuotedExecutableEscaped(boolean singleQuotedExecutableEscaped) {
348         this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
349     }
350 
351     public boolean isUnconditionalQuoting() {
352         return unconditionalQuoting;
353     }
354 
355     public void setUnconditionalQuoting(boolean unconditionalQuoting) {
356         this.unconditionalQuoting = unconditionalQuoting;
357     }
358 }