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 import java.util.Collections;
70 import java.util.LinkedHashMap;
71 import java.util.Map;
72 import java.util.Properties;
73 import java.util.Vector;
74
75 /**
76 * <p>Commandline objects help handling command lines specifying processes to execute.</p>
77 *
78 * <p>The class can be used to define a command line as nested elements or as a helper to define a command line by an
79 * application.</p>
80 *
81 * <code>
82 * <someelement><br>
83 * <acommandline executable="/executable/to/run"><br>
84 * <argument value="argument 1" /><br>
85 * <argument line="argument_1 argument_2 argument_3" /><br>
86 * <argument value="argument 4" /><br>
87 * </acommandline><br>
88 * </someelement><br>
89 * </code>
90 *
91 * <p>The element <code>someelement</code> must provide a method <code>createAcommandline</code> which returns an instance
92 * of this class.</p>
93 *
94 * @author thomas.haas@softwired-inc.com
95 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
96 */
97 public class Commandline
98 implements Cloneable
99 {
100 /**
101 * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
102 */
103 @Deprecated
104 protected static final String OS_NAME = "os.name";
105
106 /**
107 * @deprecated Use {@link org.codehaus.plexus.util.Os} class instead.
108 */
109 @Deprecated
110 protected static final String WINDOWS = "Windows";
111
112 protected Vector<Arg> arguments = new Vector<>();
113
114 // protected Vector envVars = new Vector();
115 // synchronized added to preserve synchronize of Vector class
116 protected Map<String, String> envVars = Collections.synchronizedMap( new LinkedHashMap<String, String>() );
117
118 private long pid = -1;
119
120 private Shell shell;
121
122 /**
123 * @deprecated Use {@link Commandline#setExecutable(String)} instead.
124 */
125 @Deprecated
126 protected String executable;
127
128 /**
129 * @deprecated Use {@link Commandline#setWorkingDirectory(File)} or {@link Commandline#setWorkingDirectory(String)}
130 * instead.
131 */
132 @Deprecated
133 private File workingDir;
134
135 /**
136 * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
137 * generating code for remote execution.
138 *
139 * @param toProcess sh to process
140 * @param shell Shell to use
141 */
142 public Commandline( String toProcess, Shell shell )
143 {
144 this.shell = shell;
145
146 String[] tmp = new String[0];
147 try
148 {
149 tmp = CommandLineUtils.translateCommandline( toProcess );
150 }
151 catch ( Exception e )
152 {
153 System.err.println( "Error translating Commandline." );
154 }
155 if ( ( tmp != null ) && ( tmp.length > 0 ) )
156 {
157 setExecutable( tmp[0] );
158 for ( int i = 1; i < tmp.length; i++ )
159 {
160 createArgument().setValue( tmp[i] );
161 }
162 }
163 }
164
165 /**
166 * Create a new command line object. Shell is autodetected from operating system Shell usage is only desirable when
167 * generating code for remote execution.
168 * @param shell the Shell
169 */
170 public Commandline( Shell shell )
171 {
172 this.shell = shell;
173 }
174
175 /**
176 * Create a new command line object, given a command following POSIX sh quoting rules
177 *
178 * @param toProcess the process
179 */
180 public Commandline( String toProcess )
181 {
182 setDefaultShell();
183 String[] tmp = new String[0];
184 try
185 {
186 tmp = CommandLineUtils.translateCommandline( toProcess );
187 }
188 catch ( Exception e )
189 {
190 System.err.println( "Error translating Commandline." );
191 }
192 if ( ( tmp != null ) && ( tmp.length > 0 ) )
193 {
194 setExecutable( tmp[0] );
195 for ( int i = 1; i < tmp.length; i++ )
196 {
197 createArgument().setValue( tmp[i] );
198 }
199 }
200 }
201
202 /**
203 * Create a new command line object.
204 */
205 public Commandline()
206 {
207 setDefaultShell();
208 }
209
210 public long getPid()
211 {
212 if ( pid == -1 )
213 {
214 pid = Long.parseLong( String.valueOf( System.currentTimeMillis() ) );
215 }
216
217 return pid;
218 }
219
220 public void setPid( long pid )
221 {
222 this.pid = pid;
223 }
224
225 /**
226 * Class to keep track of the position of an Argument.
227 */
228 // <p>This class is there to support the srcfile and targetfile
229 // elements of <execon> and <transform> - don't know
230 // whether there might be additional use cases.</p> --SB
231 public class Marker
232 {
233
234 private int position;
235
236 private int realPos = -1;
237
238 Marker( int position )
239 {
240 this.position = position;
241 }
242
243 /**
244 * @return the number of arguments that preceded this marker.
245 *
246 * <p>The name of the executable - if set - is counted as the very first argument.</p>
247 */
248 public int getPosition()
249 {
250 if ( realPos == -1 )
251 {
252 realPos = ( getLiteralExecutable() == null ? 0 : 1 );
253 for ( int i = 0; i < position; i++ )
254 {
255 Arg arg = arguments.elementAt( i );
256 realPos += arg.getParts().length;
257 }
258 }
259 return realPos;
260 }
261 }
262
263 /**
264 * <p>
265 * Sets the shell or command-line interpreter for the detected operating system, and the shell arguments.
266 * </p>
267 */
268 private void setDefaultShell()
269 {
270 // If this is windows set the shell to command.com or cmd.exe with correct arguments.
271 if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
272 {
273 if ( Os.isFamily( Os.FAMILY_WIN9X ) )
274 {
275 setShell( new CommandShell() );
276 }
277 else
278 {
279 setShell( new CmdShell() );
280 }
281 }
282 else
283 {
284 setShell( new BourneShell() );
285 }
286 }
287
288 /**
289 * <p>Creates an argument object.</p>
290 *
291 * <p>Each commandline object has at most one instance of the argument class. This method calls
292 * <code>this.createArgument(false)</code>.</p>
293 *
294 * @return the argument object.
295 * @see #createArgument(boolean)
296 * @deprecated Use {@link Commandline#createArg()} instead
297 */
298 @Deprecated
299 public Argument createArgument()
300 {
301 return this.createArgument( false );
302 }
303
304 /**
305 * <p>Creates an argument object and adds it to our list of args.</p>
306 *
307 * <p>Each commandline object has at most one instance of the argument class.</p>
308 *
309 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
310 * appended.
311 * @deprecated Use {@link Commandline#createArg(boolean)} instead
312 * @return Argument the argument Object
313 */
314 @Deprecated
315 public Argument createArgument( boolean insertAtStart )
316 {
317 Argument argument = new Argument();
318 if ( insertAtStart )
319 {
320 arguments.insertElementAt( argument, 0 );
321 }
322 else
323 {
324 arguments.addElement( argument );
325 }
326 return argument;
327 }
328
329 /**
330 * <p>Creates an argument object.</p>
331 *
332 * <p>Each commandline object has at most one instance of the argument class. This method calls
333 * <code>this.createArgument(false)</code>.</p>
334 *
335 * @return the argument object.
336 * @see #createArgument(boolean)
337 */
338 public Arg createArg()
339 {
340 return this.createArg( false );
341 }
342
343 /**
344 * @return Creates an argument object and adds it to our list of args.
345 *
346 * <p>Each commandline object has at most one instance of the argument class.</p>
347 *
348 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
349 * appended.
350 */
351 public Arg createArg( boolean insertAtStart )
352 {
353 Arg argument = new Argument();
354 if ( insertAtStart )
355 {
356 arguments.insertElementAt( argument, 0 );
357 }
358 else
359 {
360 arguments.addElement( argument );
361 }
362 return argument;
363 }
364
365 /**
366 * @param argument the argument
367 * @see #addArg(Arg,boolean)
368 */
369 public void addArg( Arg argument )
370 {
371 this.addArg( argument, false );
372 }
373
374 /**
375 * Adds an argument object to our list of args.
376 * @param argument the argument
377 * @param insertAtStart if true, the argument is inserted at the beginning of the list of args, otherwise it is
378 * appended.
379 */
380 public void addArg( Arg argument, boolean insertAtStart )
381 {
382 if ( insertAtStart )
383 {
384 arguments.insertElementAt( argument, 0 );
385 }
386 else
387 {
388 arguments.addElement( argument );
389 }
390 }
391
392 /**
393 * Sets the executable to run.
394 * @param executable the executable
395 */
396 public void setExecutable( String executable )
397 {
398 shell.setExecutable( executable );
399 this.executable = executable;
400 }
401
402 /**
403 * @return Executable to be run, as a literal string (no shell quoting/munging)
404 */
405 public String getLiteralExecutable()
406 {
407 return executable;
408 }
409
410 /**
411 * Return an executable name, quoted for shell use. Shell usage is only desirable when generating code for remote
412 * execution.
413 *
414 * @return Executable to be run, quoted for shell interpretation
415 */
416 public String getExecutable()
417 {
418 String exec = shell.getExecutable();
419
420 if ( exec == null )
421 {
422 exec = executable;
423 }
424
425 return exec;
426 }
427
428 public void addArguments( String[] line )
429 {
430 for ( String aLine : line )
431 {
432 createArgument().setValue( aLine );
433 }
434 }
435
436 /**
437 * Add an environment variable
438 * @param name name
439 * @param value value
440 */
441 public void addEnvironment( String name, String value )
442 {
443 // envVars.add( name + "=" + value );
444 envVars.put( name, value );
445 }
446
447 /**
448 * Add system environment variables
449 * @throws Exception if error
450 */
451 public void addSystemEnvironment()
452 throws Exception
453 {
454 Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
455
456 for ( Object o : systemEnvVars.keySet() )
457 {
458 String key = (String) o;
459 if ( !envVars.containsKey( key ) )
460 {
461 addEnvironment( key, systemEnvVars.getProperty( key ) );
462 }
463 }
464 }
465
466 /**
467 * @return String[] Return the list of environment variables
468 * @throws CommandLineException if error
469 */
470 public String[] getEnvironmentVariables()
471 throws CommandLineException
472 {
473 try
474 {
475 addSystemEnvironment();
476 }
477 catch ( Exception e )
478 {
479 throw new CommandLineException( "Error setting up environmental variables", e );
480 }
481 String[] environmentVars = new String[envVars.size()];
482 int i = 0;
483 for ( Object o : envVars.keySet() )
484 {
485 String name = (String) o;
486 String value = envVars.get( name );
487 environmentVars[i] = name + "=" + value;
488 i++;
489 }
490 return environmentVars;
491 }
492
493 /**
494 * @return Returns the executable and all defined arguments.
495 * For Windows Family, {@link Commandline#getShellCommandline()} is returned
496 */
497 public String[] getCommandline()
498 {
499 if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
500 {
501 return getShellCommandline();
502 }
503
504 return getRawCommandline();
505 }
506
507 /**
508 * Returns the executable and all defined arguments.
509 * @return the command line as array not escaped neither quoted
510 */
511 public String[] getRawCommandline()
512 {
513 final String[] args = getArguments();
514 String executable = getLiteralExecutable();
515
516 if ( executable == null )
517 {
518 return args;
519 }
520 final String[] result = new String[args.length + 1];
521 result[0] = executable;
522 System.arraycopy( args, 0, result, 1, args.length );
523 return result;
524 }
525
526 /**
527 * Returns the shell, executable and all defined arguments. Shell usage is only desirable when generating code for
528 * remote execution.
529 * @return the command line as array
530 */
531 public String[] getShellCommandline()
532 {
533 // TODO: Provided only for backward compat. with <= 1.4
534 verifyShellState();
535
536 return getShell().getShellCommandLine( getArguments() ).toArray( new String[0] );
537 }
538
539 /**
540 * @return Returns all arguments defined by <code>addLine</code>, <code>addValue</code> or the argument object.
541 */
542 public String[] getArguments()
543 {
544 Vector<String> result = new Vector<>( arguments.size() * 2 );
545 for ( int i = 0; i < arguments.size(); i++ )
546 {
547 Arg arg = arguments.elementAt( i );
548 String[] s = arg.getParts();
549 if ( s != null )
550 {
551 for ( String value : s )
552 {
553 result.addElement( value );
554 }
555 }
556 }
557
558 String[] res = new String[result.size()];
559 result.copyInto( res );
560 return res;
561 }
562
563 @Override
564 public String toString()
565 {
566 return StringUtils.join( getShellCommandline(), " " );
567 }
568
569 public int size()
570 {
571 return getCommandline().length;
572 }
573
574 @Override
575 public Object clone()
576 {
577 Commandline c = new Commandline( (Shell) shell.clone() );
578 c.executable = executable;
579 c.workingDir = workingDir;
580 c.addArguments( getArguments() );
581 return c;
582 }
583
584 /**
585 * Clear out the whole command line.
586 */
587 public void clear()
588 {
589 executable = null;
590 workingDir = null;
591 shell.setExecutable( null );
592 shell.clearArguments();
593 arguments.removeAllElements();
594 }
595
596 /**
597 * Clear out the arguments but leave the executable in place for another operation.
598 */
599 public void clearArgs()
600 {
601 arguments.removeAllElements();
602 }
603
604 /**
605 *
606 * <p>This marker can be used to locate a position on the commandline - to insert something for example - when all
607 * parameters have been set.
608 * </p>
609 * @return Return a marker.
610 */
611 public Marker createMarker()
612 {
613 return new Marker( arguments.size() );
614 }
615
616 /**
617 * Sets execution directory.
618 * @param path the working directory as String
619 */
620 public void setWorkingDirectory( String path )
621 {
622 shell.setWorkingDirectory( path );
623 workingDir = new File( path );
624 }
625
626 /**
627 * Sets execution directory.
628 * @param workingDirectory the File used as working directory
629 */
630 public void setWorkingDirectory( File workingDirectory )
631 {
632 shell.setWorkingDirectory( workingDirectory );
633 workingDir = workingDirectory;
634 }
635
636 public File getWorkingDirectory()
637 {
638 File workDir = shell.getWorkingDirectory();
639
640 if ( workDir == null )
641 {
642 workDir = workingDir;
643 }
644
645 return workDir;
646 }
647
648 /**
649 * Executes the command.
650 * @return the Process
651 * @throws CommandLineException if error
652 */
653 public Process execute()
654 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 {
669 if ( workingDir == null )
670 {
671 process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
672 }
673 else
674 {
675 if ( !workingDir.exists() )
676 {
677 throw new CommandLineException( "Working directory \"" + workingDir.getPath()
678 + "\" does not exist!" );
679 }
680 else if ( !workingDir.isDirectory() )
681 {
682 throw new CommandLineException( "Path \"" + workingDir.getPath()
683 + "\" does not specify a directory." );
684 }
685
686 process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
687 }
688 }
689 catch ( IOException ex )
690 {
691 throw new CommandLineException( "Error while executing process.", ex );
692 }
693
694 return process;
695 }
696
697 /**
698 * @deprecated Remove once backward compat with plexus-utils <= 1.4 is no longer a consideration
699 */
700 @Deprecated
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 throws Exception
716 {
717 return CommandLineUtils.getSystemEnvVars();
718 }
719
720 /**
721 * Allows to set the shell to be used in this command line. Shell usage is only desirable when generating code for
722 * remote execution.
723 *
724 * @param shell Shell to use
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. Shell usage is only desirable when generating code for remote
734 * execution.
735 *
736 * @since 1.2
737 * @return the Shell
738 */
739 public Shell getShell()
740 {
741 return shell;
742 }
743
744 /**
745 * @param toProcess the process
746 * @return the command line arguments
747 * @throws Exception if error happen
748 * @deprecated Use {@link CommandLineUtils#translateCommandline(String)} instead.
749 */
750 @Deprecated
751 public static String[] translateCommandline( String toProcess )
752 throws Exception
753 {
754 return CommandLineUtils.translateCommandline( toProcess );
755 }
756
757 /**
758 * @param argument the argument
759 * @return the quote arg
760 * @throws CommandLineException if error happen
761 * @deprecated Use {@link CommandLineUtils#quote(String)} instead.
762 */
763 @Deprecated
764 public static String quoteArgument( String argument )
765 throws CommandLineException
766 {
767 return CommandLineUtils.quote( argument );
768 }
769
770 /**
771 * @deprecated Use {@link CommandLineUtils#toString(String[])} instead.
772 * @param line the lines
773 * @return lines as single String
774 */
775 @Deprecated
776 public static String toString( String[] line )
777 {
778 return CommandLineUtils.toString( line );
779 }
780
781 public static class Argument
782 implements Arg
783 {
784 private String[] parts;
785
786 /*
787 * (non-Javadoc)
788 * @see org.codehaus.plexus.util.cli.Argument#setValue(java.lang.String)
789 */
790 @Override
791 public void setValue( String value )
792 {
793 if ( value != null )
794 {
795 parts = new String[] { value };
796 }
797 }
798
799 /*
800 * (non-Javadoc)
801 * @see org.codehaus.plexus.util.cli.Argument#setLine(java.lang.String)
802 */
803 @Override
804 public void setLine( String line )
805 {
806 if ( line == null )
807 {
808 return;
809 }
810 try
811 {
812 parts = CommandLineUtils.translateCommandline( line );
813 }
814 catch ( Exception e )
815 {
816 System.err.println( "Error translating Commandline." );
817 }
818 }
819
820 /*
821 * (non-Javadoc)
822 * @see org.codehaus.plexus.util.cli.Argument#setFile(java.io.File)
823 */
824 @Override
825 public void setFile( File value )
826 {
827 parts = new String[] { value.getAbsolutePath() };
828 }
829
830 /*
831 * (non-Javadoc)
832 * @see org.codehaus.plexus.util.cli.Argument#getParts()
833 */
834 @Override
835 public String[] getParts()
836 {
837 return parts;
838 }
839 }
840 }