View Javadoc
1   package org.codehaus.plexus.util.cli;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  /***************************************************************************************************
20   * CruiseControl, a Continuous Integration Toolkit Copyright (c) 2001-2003, ThoughtWorks, Inc. 651 W
21   * Washington Ave. Suite 500 Chicago, IL 60661 USA All rights reserved.
22   *
23   * Redistribution and use in source and binary forms, with or without modification, are permitted
24   * provided that the following conditions are met: + Redistributions of source code must retain the
25   * above copyright notice, this list of conditions and the following disclaimer. + Redistributions
26   * in binary form must reproduce the above copyright notice, this list of conditions and the
27   * following disclaimer in the documentation and/or other materials provided with the distribution. +
28   * Neither the name of ThoughtWorks, Inc., CruiseControl, nor the names of its contributors may be
29   * used to endorse or promote products derived from this software without specific prior written
30   * permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
33   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
34   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
35   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
37   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39   * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40   **************************************************************************************************/
41  
42  /*
43   * ====================================================================
44   * Copyright 2003-2004 The Apache Software Foundation.
45   *
46   * Licensed under the Apache License, Version 2.0 (the "License");
47   * you may not use this file except in compliance with the License.
48   * You may obtain a copy of the License at
49   *
50   *     http://www.apache.org/licenses/LICENSE-2.0
51   *
52   * Unless required by applicable law or agreed to in writing, software
53   * distributed under the License is distributed on an "AS IS" BASIS,
54   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55   * See the License for the specific language governing permissions and
56   * limitations under the License.
57   * ====================================================================
58   */
59  
60  import org.codehaus.plexus.util.Os;
61  import org.codehaus.plexus.util.StringUtils;
62  import org.codehaus.plexus.util.cli.shell.BourneShell;
63  import org.codehaus.plexus.util.cli.shell.CmdShell;
64  import org.codehaus.plexus.util.cli.shell.CommandShell;
65  import org.codehaus.plexus.util.cli.shell.Shell;
66  
67  import java.io.File;
68  import java.io.IOException;
69  import java.util.Collections;
70  import java.util.Iterator;
71  import java.util.LinkedHashMap;
72  import java.util.Map;
73  import java.util.Properties;
74  import java.util.Vector;
75  
76  /**
77   * <p/>
78   * Commandline objects help handling command lines specifying processes to execute.
79   * </p>
80   * <p/>
81   * The class can be used to define a command line as nested elements or as a helper to define a command line by an
82   * application.
83   * </p>
84   * <p/>
85   * <code>
86   * &lt;someelement&gt;<br>
87   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
88   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
89   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
90   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
91   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
92   * &lt;/someelement&gt;<br>
93   * </code>
94   * </p>
95   * <p/>
96   * The element <code>someelement</code> must provide a method <code>createAcommandline</code> which returns an instance
97   * of this class.
98   * </p>
99   *
100  * @author thomas.haas@softwired-inc.com
101  * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
102  */
103 public class Commandline
104     implements Cloneable
105 {
106     /**
107      * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
108      */
109     protected static final String OS_NAME = "os.name";
110 
111     /**
112      * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
113      */
114     protected static final String WINDOWS = "Windows";
115 
116     protected Vector<Arg> arguments = new Vector<Arg>();
117 
118     // protected Vector envVars = new Vector();
119     // synchronized added to preserve synchronize of Vector class
120     protected Map<String, String> envVars = Collections.synchronizedMap( new LinkedHashMap<String, String>() );
121 
122     private long pid = -1;
123 
124     private Shell shell;
125 
126     /**
127      * @deprecated Use {@link Commandline#setExecutable(String)} instead.
128      */
129     protected String executable;
130 
131     /**
132      * @deprecated Use {@link Commandline#setWorkingDirectory(File)} or {@link Commandline#setWorkingDirectory(String)}
133      *             instead.
134      */
135     private File workingDir;
136 
137     /**
138      * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
139      * generating code for remote execution.
140      *
141      * @param toProcess
142      */
143     public Commandline( String toProcess, Shell shell )
144     {
145         this.shell = shell;
146 
147         String[] tmp = new String[0];
148         try
149         {
150             tmp = CommandLineUtils.translateCommandline( toProcess );
151         }
152         catch ( Exception e )
153         {
154             System.err.println( "Error translating Commandline." );
155         }
156         if ( ( tmp != null ) && ( tmp.length > 0 ) )
157         {
158             setExecutable( tmp[0] );
159             for ( int i = 1; i < tmp.length; i++ )
160             {
161                 createArgument().setValue( tmp[i] );
162             }
163         }
164     }
165 
166     /**
167      * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
168      * generating code for remote execution.
169      */
170     public Commandline( Shell shell )
171     {
172         this.shell = shell;
173     }
174 
175     /**
176      * Create a new command line object, given a command following POSIX sh quoting rules
177      *
178      * @param toProcess
179      */
180     public Commandline( String toProcess )
181     {
182         setDefaultShell();
183         String[] tmp = new String[0];
184         try
185         {
186             tmp = CommandLineUtils.translateCommandline( toProcess );
187         }
188         catch ( Exception e )
189         {
190             System.err.println( "Error translating Commandline." );
191         }
192         if ( ( tmp != null ) && ( tmp.length > 0 ) )
193         {
194             setExecutable( tmp[0] );
195             for ( int i = 1; i < tmp.length; i++ )
196             {
197                 createArgument().setValue( tmp[i] );
198             }
199         }
200     }
201 
202     /**
203      * Create a new command line object.
204      */
205     public Commandline()
206     {
207         setDefaultShell();
208     }
209 
210     public long getPid()
211     {
212         if ( pid == -1 )
213         {
214             pid = Long.parseLong( String.valueOf( System.currentTimeMillis() ) );
215         }
216 
217         return pid;
218     }
219 
220     public void setPid( long pid )
221     {
222         this.pid = pid;
223     }
224 
225     /**
226      * Class to keep track of the position of an Argument.
227      */
228     // <p>This class is there to support the srcfile and targetfile
229     // elements of &lt;execon&gt; and &lt;transform&gt; - don't know
230     // whether there might be additional use cases.</p> --SB
231     public class Marker
232     {
233 
234         private int position;
235 
236         private int realPos = -1;
237 
238         Marker( int position )
239         {
240             this.position = position;
241         }
242 
243         /**
244          * Return the number of arguments that preceded this marker.
245          * <p/>
246          * <p>
247          * The name of the executable - if set - is counted as the very first argument.
248          * </p>
249          */
250         public int getPosition()
251         {
252             if ( realPos == -1 )
253             {
254                 realPos = ( getLiteralExecutable() == null ? 0 : 1 );
255                 for ( int i = 0; i < position; i++ )
256                 {
257                     Arg arg = (Arg) arguments.elementAt( i );
258                     realPos += arg.getParts().length;
259                 }
260             }
261             return realPos;
262         }
263     }
264 
265     /**
266      * <p>
267      * Sets the shell or command-line interpreter for the detected operating system, and the shell arguments.
268      * </p>
269      */
270     private void setDefaultShell()
271     {
272         // If this is windows set the shell to command.com or cmd.exe with correct arguments.
273         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
274         {
275             if ( Os.isFamily( Os.FAMILY_WIN9X ) )
276             {
277                 setShell( new CommandShell() );
278             }
279             else
280             {
281                 setShell( new CmdShell() );
282             }
283         }
284         else
285         {
286             setShell( new BourneShell() );
287         }
288     }
289 
290     /**
291      * Creates an argument object.
292      * <p/>
293      * <p>
294      * Each commandline object has at most one instance of the argument class. This method calls
295      * <code>this.createArgument(false)</code>.
296      * </p>
297      *
298      * @return the argument object.
299      * @see #createArgument(boolean)
300      * @deprecated Use {@link Commandline#createArg()} instead
301      */
302     public Argument createArgument()
303     {
304         return this.createArgument( false );
305     }
306 
307     /**
308      * Creates an argument object and adds it to our list of args.
309      * <p/>
310      * <p>
311      * Each commandline object has at most one instance of the argument class.
312      * </p>
313      *
314      * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
315      *            appended.
316      * @deprecated Use {@link Commandline#createArg(boolean)} instead
317      */
318     public Argument createArgument( boolean insertAtStart )
319     {
320         Argument argument = new Argument();
321         if ( insertAtStart )
322         {
323             arguments.insertElementAt( argument, 0 );
324         }
325         else
326         {
327             arguments.addElement( argument );
328         }
329         return argument;
330     }
331 
332     /**
333      * Creates an argument object.
334      * <p/>
335      * <p>
336      * Each commandline object has at most one instance of the argument class. This method calls
337      * <code>this.createArgument(false)</code>.
338      * </p>
339      *
340      * @return the argument object.
341      * @see #createArgument(boolean)
342      */
343     public Arg createArg()
344     {
345         return this.createArg( false );
346     }
347 
348     /**
349      * Creates an argument object and adds it to our list of args.
350      * <p/>
351      * <p>
352      * Each commandline object has at most one instance of the argument class.
353      * </p>
354      *
355      * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
356      *            appended.
357      */
358     public Arg createArg( boolean insertAtStart )
359     {
360         Arg argument = new Argument();
361         if ( insertAtStart )
362         {
363             arguments.insertElementAt( argument, 0 );
364         }
365         else
366         {
367             arguments.addElement( argument );
368         }
369         return argument;
370     }
371 
372     /**
373      * Adds an argument object to our list of args.
374      *
375      * @return the argument object.
376      * @see #addArg(Arg,boolean)
377      */
378     public void addArg( Arg argument )
379     {
380         this.addArg( argument, false );
381     }
382 
383     /**
384      * Adds an argument object to our list of args.
385      *
386      * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
387      *            appended.
388      */
389     public void addArg( Arg argument, boolean insertAtStart )
390     {
391         if ( insertAtStart )
392         {
393             arguments.insertElementAt( argument, 0 );
394         }
395         else
396         {
397             arguments.addElement( argument );
398         }
399     }
400 
401     /**
402      * Sets the executable to run.
403      */
404     public void setExecutable( String executable )
405     {
406         shell.setExecutable( executable );
407         this.executable = executable;
408     }
409 
410     /**
411      * @return Executable to be run, as a literal string (no shell quoting/munging)
412      */
413     public String getLiteralExecutable()
414     {
415         return executable;
416     }
417 
418     /**
419      * Return an executable name, quoted for shell use. Shell usage is only desirable when generating code for remote
420      * execution.
421      *
422      * @return Executable to be run, quoted for shell interpretation
423      */
424     public String getExecutable()
425     {
426         String exec = shell.getExecutable();
427 
428         if ( exec == null )
429         {
430             exec = executable;
431         }
432 
433         return exec;
434     }
435 
436     public void addArguments( String[] line )
437     {
438         for ( String aLine : line )
439         {
440             createArgument().setValue( aLine );
441         }
442     }
443 
444     /**
445      * Add an environment variable
446      */
447     public void addEnvironment( String name, String value )
448     {
449         // envVars.add( name + "=" + value );
450         envVars.put( name, value );
451     }
452 
453     /**
454      * Add system environment variables
455      */
456     public void addSystemEnvironment()
457         throws Exception
458     {
459         Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
460 
461         for ( Object o : systemEnvVars.keySet() )
462         {
463             String key = (String) o;
464             if ( !envVars.containsKey( key ) )
465             {
466                 addEnvironment( key, systemEnvVars.getProperty( key ) );
467             }
468         }
469     }
470 
471     /**
472      * Return the list of environment variables
473      */
474     public String[] getEnvironmentVariables()
475         throws CommandLineException
476     {
477         try
478         {
479             addSystemEnvironment();
480         }
481         catch ( Exception e )
482         {
483             throw new CommandLineException( "Error setting up environmental variables", e );
484         }
485         String[] environmentVars = new String[envVars.size()];
486         int i = 0;
487         for ( Object o : envVars.keySet() )
488         {
489             String name = (String) o;
490             String value = envVars.get( name );
491             environmentVars[i] = name + "=" + value;
492             i++;
493         }
494         return environmentVars;
495     }
496 
497     /**
498      * Returns the executable and all defined arguments.<br/>
499      * For Windows Family, {@link Commandline#getShellCommandline()} is returned
500      */
501     public String[] getCommandline()
502     {
503         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
504         {
505             return getShellCommandline();
506         }
507 
508         final String[] args = getArguments();
509         String executable = getLiteralExecutable();
510 
511         if ( executable == null )
512         {
513             return args;
514         }
515         final String[] result = new String[args.length + 1];
516         result[0] = executable;
517         System.arraycopy( args, 0, result, 1, args.length );
518         return result;
519     }
520 
521     /**
522      * Returns the shell, executable and all defined arguments. Shell usage is only desirable when generating code for
523      * remote execution.
524      */
525     public String[] getShellCommandline()
526     {
527         // TODO: Provided only for backward compat. with <= 1.4
528         verifyShellState();
529 
530         return (String[]) getShell().getShellCommandLine( getArguments() ).toArray( new String[0] );
531     }
532 
533     /**
534      * Returns all arguments defined by <code>addLine</code>, <code>addValue</code> or the argument object.
535      */
536     public String[] getArguments()
537     {
538         Vector<String> result = new Vector<String>( arguments.size() * 2 );
539         for ( int i = 0; i < arguments.size(); i++ )
540         {
541             Arg arg = arguments.elementAt( i );
542             String[] s = arg.getParts();
543             if ( s != null )
544             {
545                 for ( String value : s )
546                 {
547                     result.addElement( value );
548                 }
549             }
550         }
551 
552         String[] res = new String[result.size()];
553         result.copyInto( res );
554         return res;
555     }
556 
557     public String toString()
558     {
559         return StringUtils.join( getShellCommandline(), " " );
560     }
561 
562     public int size()
563     {
564         return getCommandline().length;
565     }
566 
567     public Object clone()
568     {
569         Commandline c = new Commandline( (Shell) shell.clone() );
570         c.executable = executable;
571         c.workingDir = workingDir;
572         c.addArguments( getArguments() );
573         return c;
574     }
575 
576     /**
577      * Clear out the whole command line.
578      */
579     public void clear()
580     {
581         executable = null;
582         workingDir = null;
583         shell.setExecutable( null );
584         shell.clearArguments();
585         arguments.removeAllElements();
586     }
587 
588     /**
589      * Clear out the arguments but leave the executable in place for another operation.
590      */
591     public void clearArgs()
592     {
593         arguments.removeAllElements();
594     }
595 
596     /**
597      * Return a marker.
598      * <p/>
599      * <p>
600      * This marker can be used to locate a position on the commandline - to insert something for example - when all
601      * parameters have been set.
602      * </p>
603      */
604     public Marker createMarker()
605     {
606         return new Marker( arguments.size() );
607     }
608 
609     /**
610      * Sets execution directory.
611      */
612     public void setWorkingDirectory( String path )
613     {
614         shell.setWorkingDirectory( path );
615         workingDir = new File( path );
616     }
617 
618     /**
619      * Sets execution directory.
620      */
621     public void setWorkingDirectory( File workingDirectory )
622     {
623         shell.setWorkingDirectory( workingDirectory );
624         workingDir = workingDirectory;
625     }
626 
627     public File getWorkingDirectory()
628     {
629         File workDir = shell.getWorkingDirectory();
630 
631         if ( workDir == null )
632         {
633             workDir = workingDir;
634         }
635 
636         return workDir;
637     }
638 
639     /**
640      * Executes the command.
641      */
642     public Process execute()
643         throws CommandLineException
644     {
645         // TODO: Provided only for backward compat. with <= 1.4
646         verifyShellState();
647 
648         Process process;
649 
650         // addEnvironment( "MAVEN_TEST_ENVAR", "MAVEN_TEST_ENVAR_VALUE" );
651 
652         String[] environment = getEnvironmentVariables();
653 
654         File workingDir = shell.getWorkingDirectory();
655 
656         try
657         {
658             if ( workingDir == null )
659             {
660                 process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
661             }
662             else
663             {
664                 if ( !workingDir.exists() )
665                 {
666                     throw new CommandLineException( "Working directory \"" + workingDir.getPath()
667                         + "\" does not exist!" );
668                 }
669                 else if ( !workingDir.isDirectory() )
670                 {
671                     throw new CommandLineException( "Path \"" + workingDir.getPath()
672                         + "\" does not specify a directory." );
673                 }
674 
675                 process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
676             }
677         }
678         catch ( IOException ex )
679         {
680             throw new CommandLineException( "Error while executing process.", ex );
681         }
682 
683         return process;
684     }
685 
686     /**
687      * @deprecated Remove once backward compat with plexus-utils <= 1.4 is no longer a consideration
688      */
689     private void verifyShellState()
690     {
691         if ( shell.getWorkingDirectory() == null )
692         {
693             shell.setWorkingDirectory( workingDir );
694         }
695 
696         if ( shell.getOriginalExecutable() == null )
697         {
698             shell.setExecutable( executable );
699         }
700     }
701 
702     public Properties getSystemEnvVars()
703         throws Exception
704     {
705         return CommandLineUtils.getSystemEnvVars();
706     }
707 
708     /**
709      * Allows to set the shell to be used in this command line. Shell usage is only desirable when generating code for
710      * remote execution.
711      *
712      * @param shell
713      * @since 1.2
714      */
715     public void setShell( Shell shell )
716     {
717         this.shell = shell;
718     }
719 
720     /**
721      * Get the shell to be used in this command line. Shell usage is only desirable when generating code for remote
722      * execution.
723      * 
724      * @since 1.2
725      */
726     public Shell getShell()
727     {
728         return shell;
729     }
730 
731     /**
732      * @deprecated Use {@link CommandLineUtils#translateCommandline(String)} instead.
733      */
734     public static String[] translateCommandline( String toProcess )
735         throws Exception
736     {
737         return CommandLineUtils.translateCommandline( toProcess );
738     }
739 
740     /**
741      * @deprecated Use {@link CommandLineUtils#quote(String)} instead.
742      */
743     public static String quoteArgument( String argument )
744         throws CommandLineException
745     {
746         return CommandLineUtils.quote( argument );
747     }
748 
749     /**
750      * @deprecated Use {@link CommandLineUtils#toString(String[])} instead.
751      */
752     public static String toString( String[] line )
753     {
754         return CommandLineUtils.toString( line );
755     }
756 
757     public static class Argument
758         implements Arg
759     {
760         private String[] parts;
761 
762         /*
763          * (non-Javadoc)
764          * @see org.codehaus.plexus.util.cli.Argument#setValue(java.lang.String)
765          */
766         public void setValue( String value )
767         {
768             if ( value != null )
769             {
770                 parts = new String[] { value };
771             }
772         }
773 
774         /*
775          * (non-Javadoc)
776          * @see org.codehaus.plexus.util.cli.Argument#setLine(java.lang.String)
777          */
778         public void setLine( String line )
779         {
780             if ( line == null )
781             {
782                 return;
783             }
784             try
785             {
786                 parts = CommandLineUtils.translateCommandline( line );
787             }
788             catch ( Exception e )
789             {
790                 System.err.println( "Error translating Commandline." );
791             }
792         }
793 
794         /*
795          * (non-Javadoc)
796          * @see org.codehaus.plexus.util.cli.Argument#setFile(java.io.File)
797          */
798         public void setFile( File value )
799         {
800             parts = new String[] { value.getAbsolutePath() };
801         }
802 
803         /*
804          * (non-Javadoc)
805          * @see org.codehaus.plexus.util.cli.Argument#getParts()
806          */
807         public String[] getParts()
808         {
809             return parts;
810         }
811     }
812 }