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