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