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 * <someelement><br>
92 * <acommandline executable="/executable/to/run"><br>
93 * <argument value="argument 1" /><br>
94 * <argument line="argument_1 argument_2 argument_3" /><br>
95 * <argument value="argument 4" /><br>
96 * </acommandline><br>
97 * </someelement><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 <execon> and <transform> - 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 }