View Javadoc

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