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