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