View Javadoc
1   package org.apache.maven.shared.utils.cli.shell;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  
23  import java.io.File;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.List;
27  import org.apache.maven.shared.utils.StringUtils;
28  
29  /**
30   * Class that abstracts the Shell functionality,
31   * with subclasses for shells that behave particularly, like
32   * 
33   * <ul>
34   * <li><code>command.com</code></li>
35   * <li><code>cmd.exe</code></li>
36   * </ul>
37   *
38   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
39   * 
40   */
41  public class Shell
42      implements Cloneable
43  {
44      private static final char[] DEFAULT_QUOTING_TRIGGER_CHARS = { ' ' };
45  
46      private String shellCommand;
47  
48      private final List<String> shellArgs = new ArrayList<String>();
49  
50      private boolean quotedArgumentsEnabled = true;
51  
52      private boolean unconditionalQuoting = false;
53  
54      private String executable;
55  
56      private String workingDir;
57  
58      private boolean quotedExecutableEnabled = true;
59  
60      private boolean singleQuotedArgumentEscaped = false;
61  
62      private boolean singleQuotedExecutableEscaped = false;
63  
64      private char argQuoteDelimiter = '\"';
65  
66      private char exeQuoteDelimiter = '\"';
67  
68      /**
69       * Set the command to execute the shell (e.g. COMMAND.COM, /bin/bash,...).
70       *
71       * @param shellCommand the command
72       */
73      void setShellCommand( String shellCommand )
74      {
75          this.shellCommand = shellCommand;
76      }
77  
78      /**
79       * Get the command to execute the shell.
80       *
81       * @return the command
82       */
83      String getShellCommand()
84      {
85          return shellCommand;
86      }
87  
88      /**
89       * Set the shell arguments when calling a command line (not the executable arguments)
90       * (e.g. /X /C for CMD.EXE).
91       *
92       * @param shellArgs the arguments to the shell
93       */
94      void setShellArgs( String[] shellArgs )
95      {
96          this.shellArgs.clear();
97          this.shellArgs.addAll( Arrays.asList( shellArgs ) );
98      }
99  
100     /**
101      * Get the shell arguments
102      *
103      * @return the arguments
104      */
105     String[] getShellArgs()
106     {
107         if ( shellArgs.isEmpty() )
108         {
109             return null;
110         }
111         else
112         {
113             return shellArgs.toArray( new String[0] );
114         }
115     }
116 
117     protected String quoteOneItem( String inputString, boolean isExecutable )
118     {
119         char[] escapeChars = getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
120         return StringUtils.quoteAndEscape(
121                 inputString,
122                 isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
123                 escapeChars,
124                 getQuotingTriggerChars(),
125                 '\\',
126                 unconditionalQuoting
127         );
128     }
129 
130     /**
131      * Get the command line for the provided executable and arguments in this shell
132      *
133      * @param executableParameter executable that the shell has to call
134      * @param argumentsParameter  arguments for the executable, not the shell
135      * @return list with one String object with executable and arguments quoted as needed
136      */
137     List<String> getCommandLine( String executableParameter, String... argumentsParameter )
138     {
139         return getRawCommandLine( executableParameter, argumentsParameter );
140     }
141 
142     /**
143      * @param executableParameter Executable
144      * @param argumentsParameter the arguments for the executable
145      * @return the list on command line
146      */
147     List<String> getRawCommandLine( String executableParameter, String... argumentsParameter )
148     {
149         List<String> commandLine = new ArrayList<>();
150         StringBuilder sb = new StringBuilder();
151 
152         if ( executableParameter != null )
153         {
154             String preamble = getExecutionPreamble();
155             if ( preamble != null )
156             {
157                 sb.append( preamble );
158             }
159 
160             if ( isQuotedExecutableEnabled() )
161             {
162                 sb.append( quoteOneItem( executableParameter, true ) );
163             }
164             else
165             {
166                 sb.append( executableParameter );
167             }
168         }
169         for ( String argument : argumentsParameter )
170         {
171             if ( sb.length() > 0 )
172             {
173                 sb.append( ' ' );
174             }
175 
176             if ( isQuotedArgumentsEnabled() )
177             {
178                 sb.append( quoteOneItem( argument, false ) );
179             }
180             else
181             {
182                 sb.append( argument );
183             }
184         }
185 
186         commandLine.add( sb.toString() );
187 
188         return commandLine;
189     }
190 
191     char[] getQuotingTriggerChars()
192     {
193         return DEFAULT_QUOTING_TRIGGER_CHARS;
194     }
195 
196     String getExecutionPreamble()
197     {
198         return null;
199     }
200 
201     char[] getEscapeChars( boolean includeSingleQuote, boolean includeDoubleQuote )
202     {
203         StringBuilder buf = new StringBuilder( 2 );
204         if ( includeSingleQuote )
205         {
206             buf.append( '\'' );
207         }
208 
209         if ( includeDoubleQuote )
210         {
211             buf.append( '\"' );
212         }
213 
214         char[] result = new char[buf.length()];
215         buf.getChars( 0, buf.length(), result, 0 );
216 
217         return result;
218     }
219 
220     /**
221      * @return false in all cases 
222      */
223     protected boolean isDoubleQuotedArgumentEscaped()
224     {
225         return false;
226     }
227 
228     /**
229      * @return {@link #singleQuotedArgumentEscaped}
230      */
231     protected boolean isSingleQuotedArgumentEscaped()
232     {
233         return singleQuotedArgumentEscaped;
234     }
235 
236     boolean isDoubleQuotedExecutableEscaped()
237     {
238         return false;
239     }
240 
241     boolean isSingleQuotedExecutableEscaped()
242     {
243         return singleQuotedExecutableEscaped;
244     }
245 
246     /**
247      * @param argQuoteDelimiterParameter {@link #argQuoteDelimiter}
248      */
249     void setArgumentQuoteDelimiter( char argQuoteDelimiterParameter )
250     {
251         this.argQuoteDelimiter = argQuoteDelimiterParameter;
252     }
253 
254     char getArgumentQuoteDelimiter()
255     {
256         return argQuoteDelimiter;
257     }
258 
259     /**
260      * @param exeQuoteDelimiterParameter {@link #exeQuoteDelimiter}
261      */
262     void setExecutableQuoteDelimiter( char exeQuoteDelimiterParameter )
263     {
264         this.exeQuoteDelimiter = exeQuoteDelimiterParameter;
265     }
266 
267     char getExecutableQuoteDelimiter()
268     {
269         return exeQuoteDelimiter;
270     }
271 
272     /**
273      * Get the full command line to execute, including shell command, shell arguments,
274      * executable and executable arguments
275      *
276      * @param arguments arguments for the executable, not the shell
277      * @return List of String objects, whose array version is suitable to be used as argument
278      *         of Runtime.getRuntime().exec()
279      */
280     public List<String> getShellCommandLine( String... arguments )
281     {
282 
283         List<String> commandLine = new ArrayList<>();
284 
285         if ( getShellCommand() != null )
286         {
287             commandLine.add( getShellCommand() );
288         }
289 
290         if ( getShellArgs() != null )
291         {
292             commandLine.addAll( getShellArgsList() );
293         }
294 
295         commandLine.addAll( getCommandLine( executable, arguments ) );
296 
297         return commandLine;
298 
299     }
300 
301     List<String> getShellArgsList()
302     {
303         return shellArgs;
304     }
305 
306     /**
307      * @param quotedArgumentsEnabled {@link #quotedArgumentsEnabled}
308      */
309     public void setQuotedArgumentsEnabled( boolean quotedArgumentsEnabled )
310     {
311         this.quotedArgumentsEnabled = quotedArgumentsEnabled;
312     }
313 
314     boolean isQuotedArgumentsEnabled()
315     {
316         return quotedArgumentsEnabled;
317     }
318 
319     void setQuotedExecutableEnabled( boolean quotedExecutableEnabled )
320     {
321         this.quotedExecutableEnabled = quotedExecutableEnabled;
322     }
323 
324     boolean isQuotedExecutableEnabled()
325     {
326         return quotedExecutableEnabled;
327     }
328 
329     /**
330      * Sets the executable to run.
331      * @param executable The executable.
332      */
333     public void setExecutable( String executable )
334     {
335         if ( ( executable == null ) || ( executable.length() == 0 ) )
336         {
337             return;
338         }
339         this.executable = executable.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
340     }
341 
342     /**
343      * @return The executable.
344      */
345     public String getExecutable()
346     {
347         return executable;
348     }
349 
350     /**
351      * Sets execution directory.
352      * @param path The path which should be used as working directory.
353      */
354     public void setWorkingDirectory( String path )
355     {
356         if ( path != null )
357         {
358             this.workingDir = path;
359         }
360     }
361 
362     /**
363      * Sets execution directory.
364      * 
365      * @param workingDirectory the working directory
366      */
367     public void setWorkingDirectory( File workingDirectory )
368     {
369         if ( workingDirectory != null )
370         {
371             this.workingDir = workingDirectory.getAbsolutePath();
372         }
373     }
374 
375     /**
376      * @return the working directory
377      */
378     public File getWorkingDirectory()
379     {
380         return workingDir == null ? null : new File( workingDir );
381     }
382 
383     String getWorkingDirectoryAsString()
384     {
385         return workingDir;
386     }
387 
388     /** {@inheritDoc} */
389     public Object clone()
390     {
391         throw new RuntimeException( "Do we ever clone this?" );
392 /*        Shell shell = new Shell();
393         shell.setExecutable( getExecutable() );
394         shell.setWorkingDirectory( getWorkingDirectory() );
395         shell.setShellArgs( getShellArgs() );
396         return shell;*/
397     }
398 
399     void setSingleQuotedArgumentEscaped( boolean singleQuotedArgumentEscaped )
400     {
401         this.singleQuotedArgumentEscaped = singleQuotedArgumentEscaped;
402     }
403 
404     void setSingleQuotedExecutableEscaped( boolean singleQuotedExecutableEscaped )
405     {
406         this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
407     }
408 
409     public boolean isUnconditionalQuoting()
410     {
411         return unconditionalQuoting;
412     }
413 
414     public void setUnconditionalQuoting( boolean unconditionalQuoting )
415     {
416         this.unconditionalQuoting = unconditionalQuoting;
417     }
418 }