View Javadoc
1   package org.apache.maven.shared.utils.cli;
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  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Properties;
31  import java.util.Vector;
32  
33  import org.apache.maven.shared.utils.Os;
34  import org.apache.maven.shared.utils.StringUtils;
35  import org.apache.maven.shared.utils.cli.shell.BourneShell;
36  import org.apache.maven.shared.utils.cli.shell.CmdShell;
37  import org.apache.maven.shared.utils.cli.shell.Shell;
38  
39  /**
40   * <p>
41   * Commandline objects help handling command lines specifying processes to
42   * execute.
43   * </p>
44   * <p>
45   * The class can be used to define a command line as nested elements or as a
46   * helper to define a command line by an application.
47   * </p>
48   * <code>
49   * &lt;someelement&gt;<br>
50   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
51   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
52   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
53   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
54   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
55   * &lt;/someelement&gt;<br>
56   * </code>
57   * <p>
58   * The element <code>someelement</code> must provide a method
59   * <code>createAcommandline</code> which returns an instance of this class.
60   * </p>
61   *
62   * @author thomas.haas@softwired-inc.com
63   * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
64   */
65  public class Commandline
66      implements Cloneable
67  {
68      private final List<Arg> arguments = new Vector<Arg>();
69  
70      private final Map<String, String> envVars = Collections.synchronizedMap( new LinkedHashMap<String, String>() );
71  
72      private Shell shell;
73  
74      /**
75       * Create a new command line object.
76       * Shell is autodetected from operating system.
77       * 
78       * @param shell the shell instance
79       */
80      public Commandline( Shell shell )
81      {
82          this.shell = shell;
83      }
84  
85      /**
86       * Create a new command line object.
87       * Shell is autodetected from operating system.
88       *
89       * @param toProcess the command to process
90       * @throws CommandLineException in case of unbalanced quotes.
91       */
92      public Commandline( String toProcess ) throws CommandLineException
93      {
94          setDefaultShell();
95          String[] tmp = CommandLineUtils.translateCommandline( toProcess );
96          if ( ( tmp.length > 0 ) )
97          {
98              setExecutable( tmp[0] );
99              for ( int i = 1; i < tmp.length; i++ )
100             {
101                 createArg().setValue( tmp[i] );
102             }
103         }
104     }
105 
106     /**
107      * Create a new command line object.
108      * Shell is autodetected from operating system.
109      */
110     public Commandline()
111     {
112         setDefaultShell();
113     }
114 
115     /**
116      * <p>Sets the shell or command-line interpreter for the detected operating system,
117      * and the shell arguments.</p>
118      */
119     private void setDefaultShell()
120     {
121         //If this is windows set the shell to command.com or cmd.exe with correct arguments.
122         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
123         {
124             setShell( new CmdShell() );
125         }
126         else
127         {
128             setShell( new BourneShell() );
129         }
130     }
131 
132     /**
133      * Creates an empty argument object and inserts it at the end of the argument list.
134      *
135      * @return the argument object
136      */
137     public Arg createArg()
138     {
139         return this.createArg( false );
140     }
141 
142     /**
143      * Creates an argument object and adds it to the list of args.
144      *
145      * @param insertAtStart if true, the argument is inserted at the
146      *                      beginning of the list of args. Otherwise it is appended.
147      * @return the argument
148      */
149     public Arg createArg( boolean insertAtStart )
150     {
151         Arg argument = new Argument();
152         if ( insertAtStart )
153         {
154             arguments.add( 0, argument );
155         }
156         else
157         {
158             arguments.add( argument );
159         }
160         return argument;
161     }
162 
163     /**
164      * Sets the executable to run.
165      * 
166      * @param executable the executable
167      */
168     public void setExecutable( String executable )
169     {
170         shell.setExecutable( executable );
171     }
172 
173     /**
174      * @return the executable
175      */
176     public String getExecutable()
177     {
178 
179         return shell.getExecutable();
180     }
181 
182     /**
183      * @param line the arguments
184      */
185     public void addArguments( String... line )
186     {
187         for ( String aLine : line )
188         {
189             createArg().setValue( aLine );
190         }
191     }
192 
193     /**
194      * Add an environment variable.
195      * 
196      * @param name the name of the environment variable
197      * @param value the appropriate value
198      */
199     public void addEnvironment( String name, String value )
200     {
201         //envVars.add( name + "=" + value );
202         envVars.put( name, value );
203     }
204 
205     /**
206      * Add system environment variables.
207      */
208     public void addSystemEnvironment()
209     {
210         Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
211 
212         for ( Object o : systemEnvVars.keySet() )
213         {
214             String key = (String) o;
215             if ( !envVars.containsKey( key ) )
216             {
217                 addEnvironment( key, systemEnvVars.getProperty( key ) );
218             }
219         }
220     }
221 
222     /**
223      * Return the list of environment variables.
224      * 
225      * @return an array of all environment variables
226      */
227     public String[] getEnvironmentVariables()
228     {
229         addSystemEnvironment();
230         List<String> environmentVars = new ArrayList<>();
231         for ( String name : envVars.keySet() )
232         {
233             String value = envVars.get( name );
234             if ( value != null )
235             {
236                 environmentVars.add( name + "=" + value );
237             }
238         }
239         return environmentVars.toArray( new String[0] );
240     }
241 
242     /**
243      * Returns the executable and all defined arguments.
244      * 
245      * @return an array of all arguments including the executable
246      */
247     public String[] getCommandline()
248     {
249         final String[] args = getArguments();
250         String executable = getExecutable();
251 
252         if ( executable == null )
253         {
254             return args;
255         }
256         final String[] result = new String[args.length + 1];
257         result[0] = executable;
258         System.arraycopy( args, 0, result, 1, args.length );
259         return result;
260     }
261 
262     /**
263      * @return the shell, executable and all defined arguments without masking any arguments
264      */
265     private String[] getShellCommandline()
266     {
267         return getShellCommandline( false ) ;
268     }
269 
270     /**
271      * @param mask flag to mask any arguments (having his {@code mask} field to {@code true})
272      * @return the shell, executable and all defined arguments with masking some arguments if
273      * {@code mask} parameter is on
274      */
275     private String[] getShellCommandline( boolean mask )
276     {
277         List<String> shellCommandLine = getShell().getShellCommandLine( getArguments( mask ) );
278         return shellCommandLine.toArray( new String[shellCommandLine.size()] );
279     }
280 
281     /**
282      * Returns all arguments defined by <code>addLine</code>,
283      * <code>addValue</code> or the argument object.
284      * @return an array of arguments.
285      */
286     public String[] getArguments()
287     {
288         return getArguments( false );
289     }
290 
291     /**
292      * Returns all arguments defined by <code>addLine</code>,
293      * <code>addValue</code>, or the argument object.
294      *
295      * @param mask flag to mask any arguments (having his {@code mask} field to {@code true})
296      * @return an array of arguments
297      */
298     public String[] getArguments( boolean mask )
299     {
300         List<String> result = new ArrayList<String>( arguments.size() * 2 );
301         for ( Arg argument : arguments )
302         {
303             Argument arg = (Argument) argument;
304             String[] s = arg.getParts();
305             if ( s != null )
306             {
307                 if ( mask && ( arg.isMask() ) )
308                 {
309                     // should be a key-pair argument
310                     if ( s.length > 0 )
311                     {
312 
313                         // use a masked copy
314                         String[] copy = new String[s.length];
315                         Arrays.fill( copy, "*****" );
316                         s = copy;
317                     }
318                 }
319                 Collections.addAll( result, s );
320             }
321         }
322 
323         return result.toArray( new String[result.size()] );
324     }
325 
326     /** {@inheritDoc}
327      */
328     public String toString()
329     {
330         return StringUtils.join( getShellCommandline( true ), " " );
331     }
332 
333 
334     /** {@inheritDoc}
335      */
336     public Object clone()
337     {
338         throw new RuntimeException( "Do we ever clone a commandline?" );
339 /*        Commandline c = new Commandline( (Shell) shell.clone() );
340        c.addArguments( getArguments() );
341         return c;*/
342     }
343 
344     /**
345      * Sets working directory.
346      * 
347      * @param path the working directory
348      */
349     public void setWorkingDirectory( String path )
350     {
351         shell.setWorkingDirectory( path );
352     }
353 
354     /**
355      * Sets working directory.
356      * 
357      * @param workingDirectory the working directory
358      */
359     public void setWorkingDirectory( File workingDirectory )
360     {
361         shell.setWorkingDirectory( workingDirectory );
362     }
363 
364     /**
365      * @return the working directory
366      */
367     public File getWorkingDirectory()
368     {
369         return shell.getWorkingDirectory();
370     }
371 
372     /**
373      * Clear out the arguments but leave the executable in place for another operation.
374      */
375     public void clearArgs()
376     {
377         arguments.clear();
378     }
379 
380     /**
381      * Execute the command.
382      * 
383      * @return the process
384      * @throws CommandLineException in case of errors
385      */
386     public Process execute()
387         throws CommandLineException
388     {
389         Process process;
390 
391         String[] environment = getEnvironmentVariables();
392 
393         File workingDir = shell.getWorkingDirectory();
394 
395         try
396         {
397             if ( workingDir == null )
398             {
399                 process = Runtime.getRuntime().exec( getShellCommandline(), environment );
400             }
401             else
402             {
403                 if ( !workingDir.exists() )
404                 {
405                     throw new CommandLineException(
406                         "Working directory \"" + workingDir.getPath() + "\" does not exist!" );
407                 }
408                 else if ( !workingDir.isDirectory() )
409                 {
410                     throw new CommandLineException(
411                         "Path \"" + workingDir.getPath() + "\" does not specify a directory." );
412                 }
413 
414                 process = Runtime.getRuntime().exec( getShellCommandline(), environment, workingDir );
415             }
416         }
417         catch ( IOException ex )
418         {
419             throw new CommandLineException( "Error while executing process.", ex );
420         }
421 
422         return process;
423     }
424 
425     /**
426      * Set the shell to be used for this command line.
427      *
428      * @param shell the shell
429      */
430     void setShell( Shell shell )
431     {
432         this.shell = shell;
433     }
434 
435     /**
436      * Get the shell to be used in this command line.
437      * 
438      * @return the shell
439      */
440     public Shell getShell()
441     {
442         return shell;
443     }
444 
445     /**
446      * A single command line argument
447      */
448     public static class Argument
449         implements Arg
450     {
451         private String[] parts;
452 
453         private boolean mask;
454 
455         /**
456          * {@inheritDoc}
457          */
458         public void setValue( String value )
459         {
460             if ( value != null )
461             {
462                 parts = new String[]{ value };
463             }
464         }
465 
466         /**
467          * {@inheritDoc}
468          */
469         public void setLine( String line ) throws CommandLineException
470         {
471             if ( line == null )
472             {
473                 return;
474             }
475             parts = CommandLineUtils.translateCommandline( line );
476         }
477 
478         /**
479          * {@inheritDoc}
480          */
481         public void setFile( File value )
482         {
483             parts = new String[]{ value.getAbsolutePath() };
484         }
485 
486         /**
487          * {@inheritDoc}
488          */
489         public void setMask( boolean mask )
490         {
491             this.mask = mask;
492         }
493 
494         /**
495          * @return the parts
496          */
497         private String[] getParts()
498         {
499             return parts;
500         }
501 
502         /**
503          * @return true/false
504          */
505         public boolean isMask()
506         {
507             return mask;
508         }
509     }
510 }