View Javadoc
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   * &lt;someelement&gt;<br>
83   * &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
84   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
85   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
86   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
87   * &nbsp;&nbsp;&lt;/acommandline&gt;<br>
88   * &lt;/someelement&gt;<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 &lt;execon&gt; and &lt;transform&gt; - 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 }