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