View Javadoc

1   package org.codehaus.mojo.fitnesse.plexus;
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 java.io.File;
82  import java.io.IOException;
83  import java.util.StringTokenizer;
84  import java.util.Vector;
85  
86  /**
87   * Commandline objects help handling command lines specifying processes to execute. The class can be used to define a
88   * command line as nested elements or as a helper to define a command line by an application.
89   * <p>
90   * <code>
91   * &lt;someelement&gt;<br>
92   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
93   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
94   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
95   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
96   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
97   * &lt;/someelement&gt;<br>
98   * </code> The element <code>someelement</code> must provide a method <code>createAcommandline</code> which returns
99   * an instance of this class.
100  * 
101  * @author thomas.haas@softwired-inc.com
102  * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
103  */
104 public class FCommandline
105     implements Cloneable
106 {
107 
108     protected static final String OS_NAME = "os.name";
109 
110     protected static final String WINDOWS = "Windows";
111 
112     private String shell = null;
113 
114     private Vector shellArgs = new Vector();
115 
116     protected String executable = null;
117 
118     protected Vector arguments = new Vector();
119 
120     protected Vector envVars = new Vector();
121 
122     private boolean newEnvironment = false;
123 
124     private File workingDir = null;
125 
126     public FCommandline( String toProcess )
127     {
128         super();
129         setDefaultShell();
130         String[] tmp = new String[0];
131         try
132         {
133             tmp = translateCommandline( toProcess );
134         }
135         catch ( Exception e )
136         {
137             System.err.println( "Error translating Commandline." );
138         }
139         if ( tmp != null && tmp.length > 0 )
140         {
141             setExecutable( tmp[0] );
142             for ( int i = 1; i < tmp.length; i++ )
143             {
144                 createArgument().setValue( tmp[i] );
145             }
146         }
147     }
148 
149     public FCommandline()
150     {
151         super();
152         setDefaultShell();
153     }
154 
155     /**
156      * Used for nested xml command line definitions.
157      */
158     public static class Argument
159     {
160 
161         private String[] parts;
162 
163         /**
164          * Sets a single commandline argument.
165          * 
166          * @param value a single commandline argument.
167          */
168         public void setValue( String value )
169         {
170             parts = new String[] { value };
171         }
172 
173         /**
174          * Line to split into several commandline arguments.
175          * 
176          * @param line line to split into several commandline arguments
177          */
178         public void setLine( String line )
179         {
180             if ( line == null )
181             {
182                 return;
183             }
184             try
185             {
186                 parts = translateCommandline( line );
187             }
188             catch ( Exception e )
189             {
190                 System.err.println( "Error translating Commandline." );
191             }
192         }
193 
194         /**
195          * Sets a single commandline argument to the absolute filename of the given file.
196          * 
197          * @param value a single commandline argument.
198          */
199         public void setFile( File value )
200         {
201             parts = new String[] { value.getAbsolutePath() };
202         }
203 
204         /**
205          * Returns the parts this Argument consists of.
206          */
207         public String[] getParts()
208         {
209             return parts;
210         }
211     }
212 
213     /**
214      * Class to keep track of the position of an Argument.
215      */
216     // <p>This class is there to support the srcfile and targetfile
217     // elements of &lt;execon&gt; and &lt;transform&gt; - don't know
218     // whether there might be additional use cases.</p> --SB
219     public class Marker
220     {
221 
222         private int position;
223 
224         private int realPos = -1;
225 
226         Marker( int position )
227         {
228             this.position = position;
229         }
230 
231         /**
232          * Return the number of arguments that preceeded this marker.
233          * <p>
234          * The name of the executable - if set - is counted as the very first argument.
235          * </p>
236          */
237         public int getPosition()
238         {
239             if ( realPos == -1 )
240             {
241                 realPos = ( executable == null ? 0 : 1 );
242                 for ( int i = 0; i < position; i++ )
243                 {
244                     Argument arg = (Argument) arguments.elementAt( i );
245                     realPos += arg.getParts().length;
246                 }
247             }
248             return realPos;
249         }
250     }
251 
252     /**
253      * <p>
254      * Sets the shell or command-line interpretor for the detected operating system, and the shell arguments.
255      * </p>
256      */
257     private void setDefaultShell()
258     {
259         String os = System.getProperty( OS_NAME );
260 
261         // If this is windows set the shell to command.com or cmd.exe with correct arguments.
262         if ( os.indexOf( WINDOWS ) != -1 )
263         {
264             if ( os.indexOf( "95" ) != -1 || os.indexOf( "98" ) != -1 || os.indexOf( "Me" ) != -1 )
265             {
266                 shell = "COMMAND.COM";
267                 shellArgs.add( "/C" );
268             }
269             else
270             {
271                 shell = "CMD.EXE";
272                 shellArgs.add( "/X" );
273                 shellArgs.add( "/C" );
274             }
275         }
276     }
277 
278     /**
279      * Creates an argument object.
280      * <p>
281      * Each commandline object has at most one instance of the argument class. This method calls
282      * <code>this.createArgument(false)</code>.
283      * </p>
284      * 
285      * @see #createArgument(boolean)
286      * @return the argument object.
287      */
288     public Argument createArgument()
289     {
290         return this.createArgument( false );
291     }
292 
293     /**
294      * Creates an argument object and adds it to our list of args.
295      * <p>
296      * Each commandline object has at most one instance of the argument class.
297      * </p>
298      * 
299      * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
300      *            appended.
301      */
302     public Argument createArgument( boolean insertAtStart )
303     {
304         Argument argument = new Argument();
305         if ( insertAtStart )
306         {
307             arguments.insertElementAt( argument, 0 );
308         }
309         else
310         {
311             arguments.addElement( argument );
312         }
313         return argument;
314     }
315 
316     /**
317      * Sets the executable to run.
318      */
319     public void setExecutable( String executable )
320     {
321         if ( executable == null || executable.length() == 0 )
322         {
323             return;
324         }
325         this.executable = executable.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
326     }
327 
328     public String getExecutable()
329     {
330         return executable;
331     }
332 
333     public void addArguments( String[] line )
334     {
335         for ( int i = 0; i < line.length; i++ )
336         {
337             createArgument().setValue( line[i] );
338         }
339     }
340 
341     /**
342      * Add an environment variable
343      */
344     public void addEnvironment( String name, String value )
345     {
346         envVars.add( name + "=" + value );
347         newEnvironment = true;
348     }
349 
350     /**
351      * Return the list of environment variables
352      */
353     public String[] getEnvironments()
354     {
355         return (String[]) envVars.toArray( new String[envVars.size()] );
356     }
357 
358     /**
359      * Return the current list of environment variables or null if user doesn't have add any variable.
360      * 
361      * @todo return the list of proc env variables with user env variables if user add some var.
362      */
363     public String[] getCurrentEnvironment()
364     {
365         if ( !newEnvironment )
366         {
367             return null;
368         }
369 
370         return getEnvironments();
371     }
372 
373     /**
374      * Returns the executable and all defined arguments.
375      */
376     public String[] getCommandline()
377     {
378         final String[] args = getArguments();
379         if ( executable == null )
380         {
381             return args;
382         }
383         final String[] result = new String[args.length + 1];
384         result[0] = executable;
385         System.arraycopy( args, 0, result, 1, args.length );
386         return result;
387     }
388 
389     /**
390      * Returns the shell, executable and all defined arguments.
391      */
392     public String[] getShellCommandline()
393     {
394         int shellCount = 0;
395         int arrayPos = 0;
396         if ( shell != null )
397         {
398             shellCount = 1;
399         }
400         shellCount += shellArgs.size();
401         final String[] args = getArguments();
402 
403         String[] result = new String[shellCount + args.length + ( ( executable == null ) ? 0 : 1 )];
404         // Build shell and arguments into result
405         if ( shell != null )
406         {
407             result[0] = shell;
408             arrayPos++;
409         }
410         System.arraycopy( shellArgs.toArray(), 0, result, arrayPos, shellArgs.size() );
411         arrayPos += shellArgs.size();
412         // Build excutable and arguments into result
413         if ( executable != null )
414         {
415             result[arrayPos] = executable;
416             arrayPos++;
417         }
418         System.arraycopy( args, 0, result, arrayPos, args.length );
419         return result;
420     }
421 
422     /**
423      * Returns all arguments defined by <code>addLine</code>, <code>addValue</code> or the argument object.
424      */
425     public String[] getArguments()
426     {
427         Vector result = new Vector( arguments.size() * 2 );
428         for ( int i = 0; i < arguments.size(); i++ )
429         {
430             Argument arg = (Argument) arguments.elementAt( i );
431             String[] s = arg.getParts();
432             if ( s != null )
433             {
434                 for ( int j = 0; j < s.length; j++ )
435                 {
436                     result.addElement( s[j] );
437                 }
438             }
439         }
440 
441         String[] res = new String[result.size()];
442         result.copyInto( res );
443         return res;
444     }
445 
446     public String toString()
447     {
448         return toString( getCommandline() );
449     }
450 
451     /**
452      * Put quotes around the given String if necessary.
453      * <p>
454      * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single
455      * quotes - else surround the argument by double quotes.
456      * </p>
457      * 
458      * @exception FCommandLineException if the argument contains both, single and double quotes.
459      */
460     public static String quoteArgument( String argument )
461         throws FCommandLineException
462     {
463         if ( argument.indexOf( "\"" ) > -1 )
464         {
465             if ( argument.indexOf( "\'" ) > -1 )
466             {
467                 throw new FCommandLineException( "Can't handle single and double quotes in same argument" );
468             }
469             else
470             {
471                 return '\'' + argument + '\'';
472             }
473         }
474         else if ( argument.indexOf( "\'" ) > -1 || argument.indexOf( " " ) > -1 )
475         {
476             return '\"' + argument + '\"';
477         }
478         else
479         {
480             return argument;
481         }
482     }
483 
484     public static String toString( String[] line )
485     {
486         // empty path return empty string
487         if ( line == null || line.length == 0 )
488         {
489             return "";
490         }
491 
492         // path containing one or more elements
493         final StringBuffer result = new StringBuffer();
494         for ( int i = 0; i < line.length; i++ )
495         {
496             if ( i > 0 )
497             {
498                 result.append( ' ' );
499             }
500             try
501             {
502                 result.append( quoteArgument( line[i] ) );
503             }
504             catch ( Exception e )
505             {
506                 System.err.println( "Error quoting argument." );
507             }
508         }
509         return result.toString();
510     }
511 
512     public static String[] translateCommandline( String toProcess )
513         throws Exception
514     {
515         if ( toProcess == null || toProcess.length() == 0 )
516         {
517             return new String[0];
518         }
519 
520         // parse with a simple finite state machine
521 
522         final int normal = 0;
523         final int inQuote = 1;
524         final int inDoubleQuote = 2;
525         int state = normal;
526         StringTokenizer tok = new StringTokenizer( toProcess, "\"\' ", true );
527         Vector v = new Vector();
528         StringBuffer current = new StringBuffer();
529 
530         while ( tok.hasMoreTokens() )
531         {
532             String nextTok = tok.nextToken();
533             switch ( state )
534             {
535                 case inQuote:
536                     if ( "\'".equals( nextTok ) )
537                     {
538                         state = normal;
539                     }
540                     else
541                     {
542                         current.append( nextTok );
543                     }
544                     break;
545                 case inDoubleQuote:
546                     if ( "\"".equals( nextTok ) )
547                     {
548                         state = normal;
549                     }
550                     else
551                     {
552                         current.append( nextTok );
553                     }
554                     break;
555                 default:
556                     if ( "\'".equals( nextTok ) )
557                     {
558                         state = inQuote;
559                     }
560                     else if ( "\"".equals( nextTok ) )
561                     {
562                         state = inDoubleQuote;
563                     }
564                     else if ( " ".equals( nextTok ) )
565                     {
566                         if ( current.length() != 0 )
567                         {
568                             v.addElement( current.toString() );
569                             current.setLength( 0 );
570                         }
571                     }
572                     else
573                     {
574                         current.append( nextTok );
575                     }
576                     break;
577             }
578         }
579 
580         if ( current.length() != 0 )
581         {
582             v.addElement( current.toString() );
583         }
584 
585         if ( state == inQuote || state == inDoubleQuote )
586         {
587             throw new FCommandLineException( "unbalanced quotes in " + toProcess );
588         }
589 
590         String[] args = new String[v.size()];
591         v.copyInto( args );
592         return args;
593     }
594 
595     public int size()
596     {
597         return getCommandline().length;
598     }
599 
600     public Object clone()
601     {
602         FCommandline c = new FCommandline();
603         c.setExecutable( executable );
604         c.addArguments( getArguments() );
605         return c;
606     }
607 
608     /**
609      * Clear out the whole command line.
610      */
611     public void clear()
612     {
613         executable = null;
614         arguments.removeAllElements();
615     }
616 
617     /**
618      * Clear out the arguments but leave the executable in place for another operation.
619      */
620     public void clearArgs()
621     {
622         arguments.removeAllElements();
623     }
624 
625     /**
626      * Return a marker.
627      * <p>
628      * This marker can be used to locate a position on the commandline - to insert something for example - when all
629      * parameters have been set.
630      * </p>
631      */
632     public Marker createMarker()
633     {
634         return new Marker( arguments.size() );
635     }
636 
637     /**
638      * Sets execution directory.
639      */
640     public void setWorkingDirectory( String path )
641     {
642         if ( path != null )
643         {
644             workingDir = new File( path );
645         }
646     }
647 
648     public File getWorkingDirectory()
649     {
650         return workingDir;
651     }
652 
653     /**
654      * Executes the command.
655      */
656     public Process execute()
657         throws FCommandLineException
658     {
659         Process process = null;
660 
661         try
662         {
663             if ( workingDir == null )
664             {
665                 process = Runtime.getRuntime().exec( getShellCommandline(), getCurrentEnvironment() );
666             }
667             else
668             {
669                 if ( !workingDir.exists() )
670                 {
671                     throw new FCommandLineException( "Working directory \"" + workingDir.getPath()
672                         + "\" does not exist!" );
673                 }
674                 else if ( !workingDir.isDirectory() )
675                 {
676                     throw new FCommandLineException( "Path \"" + workingDir.getPath()
677                         + "\" does not specify a directory." );
678                 }
679 
680                 process = Runtime.getRuntime().exec( getShellCommandline(), getCurrentEnvironment(), workingDir );
681             }
682         }
683         catch ( IOException ex )
684         {
685             throw new FCommandLineException( "Error while executing process.", ex );
686         }
687 
688         return process;
689     }
690 }