1 package org.apache.maven.shared.utils.cli;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.Vector;
32
33 import org.apache.maven.shared.utils.Os;
34 import org.apache.maven.shared.utils.StringUtils;
35 import org.apache.maven.shared.utils.cli.shell.BourneShell;
36 import org.apache.maven.shared.utils.cli.shell.CmdShell;
37 import org.apache.maven.shared.utils.cli.shell.CommandShell;
38 import org.apache.maven.shared.utils.cli.shell.Shell;
39
40 /**
41 * <p/>
42 * Commandline objects help handling command lines specifying processes to
43 * execute.
44 * </p>
45 * <p/>
46 * The class can be used to define a command line as nested elements or as a
47 * helper to define a command line by an application.
48 * </p>
49 * <p/>
50 * <code>
51 * <someelement><br>
52 * <acommandline executable="/executable/to/run"><br>
53 * <argument value="argument 1" /><br>
54 * <argument line="argument_1 argument_2 argument_3" /><br>
55 * <argument value="argument 4" /><br>
56 * </acommandline><br>
57 * </someelement><br>
58 * </code>
59 * </p>
60 * <p/>
61 * The element <code>someelement</code> must provide a method
62 * <code>createAcommandline</code> which returns an instance of this class.
63 * </p>
64 *
65 * @author thomas.haas@softwired-inc.com
66 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
67 */
68 public class Commandline
69 implements Cloneable
70 {
71 private final List<Arg> arguments = new Vector<Arg>();
72
73 //protected Vector envVars = new Vector();
74 // synchronized added to preserve synchronize of Vector class
75 private final Map<String, String> envVars = Collections.synchronizedMap( new LinkedHashMap<String, String>() );
76
77 private Shell shell;
78
79 /**
80 * Create a new command line object.
81 * Shell is autodetected from operating system
82 * @param shell The shell instance.
83 */
84 public Commandline( Shell shell )
85 {
86 this.shell = shell;
87 }
88
89 /**
90 * Create a new command line object.
91 * Shell is autodetected from operating system
92 *
93 * @param toProcess The command to process
94 */
95 public Commandline( String toProcess )
96 {
97 setDefaultShell();
98 String[] tmp = new String[0];
99 try
100 {
101 tmp = CommandLineUtils.translateCommandline( toProcess );
102 }
103 catch ( Exception e )
104 {
105 System.err.println( "Error translating Commandline." );
106 }
107 if ( ( tmp != null ) && ( tmp.length > 0 ) )
108 {
109 setExecutable( tmp[0] );
110 for ( int i = 1; i < tmp.length; i++ )
111 {
112 createArg().setValue( tmp[i] );
113 }
114 }
115 }
116
117 /**
118 * Create a new command line object.
119 * Shell is autodetected from operating system
120 */
121 public Commandline()
122 {
123 setDefaultShell();
124 }
125
126 /**
127 * <p>Sets the shell or command-line interpretor for the detected operating system,
128 * and the shell arguments.</p>
129 */
130 private void setDefaultShell()
131 {
132 //If this is windows set the shell to command.com or cmd.exe with correct arguments.
133 if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
134 {
135 if ( Os.isFamily( Os.FAMILY_WIN9X ) )
136 {
137 setShell( new CommandShell() );
138 }
139 else
140 {
141 setShell( new CmdShell() );
142 }
143 }
144 else
145 {
146 setShell( new BourneShell() );
147 }
148 }
149
150 /**
151 * Creates an argument object.
152 * <p/>
153 * <p>Each commandline object has at most one instance of the
154 * argument class. This method calls
155 * <code>this.createArgument(false)</code>.</p>
156 *
157 * @return the argument object.
158 */
159 public Arg createArg()
160 {
161 return this.createArg( false );
162 }
163
164 /**
165 * Creates an argument object and adds it to our list of args.
166 * <p/>
167 * <p>Each commandline object has at most one instance of the
168 * argument class.</p>
169 *
170 * @param insertAtStart if true, the argument is inserted at the
171 * beginning of the list of args, otherwise it is appended.
172 * @return The arguments.
173 */
174 public Arg createArg( boolean insertAtStart )
175 {
176 Arg argument = new Argument();
177 if ( insertAtStart )
178 {
179 arguments.add( 0, argument );
180 }
181 else
182 {
183 arguments.add( argument );
184 }
185 return argument;
186 }
187
188 /**
189 * Sets the executable to run.
190 * @param executable The executable.
191 */
192 public void setExecutable( String executable )
193 {
194 shell.setExecutable( executable );
195 }
196
197 /**
198 * @return The executable.
199 */
200 public String getExecutable()
201 {
202
203 return shell.getExecutable();
204 }
205
206 /**
207 * @param line The arguments.
208 */
209 public void addArguments( String... line )
210 {
211 for ( String aLine : line )
212 {
213 createArg().setValue( aLine );
214 }
215 }
216
217 /**
218 * Add an environment variable
219 * @param name The name of the environment variable.
220 * @param value The appropriate value.
221 */
222 public void addEnvironment( String name, String value )
223 {
224 //envVars.add( name + "=" + value );
225 envVars.put( name, value );
226 }
227
228 /**
229 * Add system environment variables
230 */
231 public void addSystemEnvironment()
232 {
233 Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
234
235 for ( Object o : systemEnvVars.keySet() )
236 {
237 String key = (String) o;
238 if ( !envVars.containsKey( key ) )
239 {
240 addEnvironment( key, systemEnvVars.getProperty( key ) );
241 }
242 }
243 }
244
245 /**
246 * Return the list of environment variables
247 * @return an array of all environment variables.
248 */
249 public String[] getEnvironmentVariables()
250 {
251 addSystemEnvironment();
252 String[] environmentVars = new String[envVars.size()];
253 int i = 0;
254 for ( String name : envVars.keySet() )
255 {
256 String value = envVars.get( name );
257 environmentVars[i] = name + "=" + value;
258 i++;
259 }
260 return environmentVars;
261 }
262
263 /**
264 * Returns the executable and all defined arguments.
265 * @return an array of all arguments incl. executable.
266 */
267 public String[] getCommandline()
268 {
269 final String[] args = getArguments();
270 String executable = getExecutable();
271
272 if ( executable == null )
273 {
274 return args;
275 }
276 final String[] result = new String[args.length + 1];
277 result[0] = executable;
278 System.arraycopy( args, 0, result, 1, args.length );
279 return result;
280 }
281
282 /**
283 * @return the shell, executable and all defined arguments without masking any arguments.
284 */
285 private String[] getShellCommandline()
286 {
287 return getShellCommandline( false ) ;
288 }
289
290 /**
291 * @param mask flag to mask any arguments (having his {@code mask} field to {@code true}).
292 * @return the shell, executable and all defined arguments with masking some arguments if
293 * {@code mask} parameter is on
294 */
295 private String[] getShellCommandline( boolean mask )
296 {
297 List<String> shellCommandLine = getShell().getShellCommandLine( getArguments( mask ) );
298 return shellCommandLine.toArray( new String[shellCommandLine.size()] );
299 }
300
301 /**
302 * Returns all arguments defined by <code>addLine</code>,
303 * <code>addValue</code> or the argument object.
304 * @return an array of arguments.
305 */
306 public String[] getArguments()
307 {
308 return getArguments( false );
309 }
310
311 /**
312 * Returns all arguments defined by <code>addLine</code>,
313 * <code>addValue</code> or the argument object.
314 *
315 * @param mask flag to mask any arguments (having his {@code mask} field to {@code true}).
316 * @return an array of arguments.
317 */
318 public String[] getArguments( boolean mask )
319 {
320 List<String> result = new ArrayList<String>( arguments.size() * 2 );
321 for ( Arg argument : arguments )
322 {
323 Argument arg = (Argument) argument;
324 String[] s = arg.getParts();
325 if ( s != null )
326 {
327 if ( mask && ( arg.isMask() ) )
328 {
329 // should be a key-pair argument
330 if ( s.length > 0 )
331 {
332
333 // use a masked copy
334 String[] copy = new String[s.length];
335 Arrays.fill( copy, "*****" );
336 s = copy;
337 }
338 }
339 Collections.addAll( result, s );
340 }
341 }
342
343 return result.toArray( new String[result.size()] );
344 }
345
346 /** {@inheritDoc}
347 */
348 public String toString()
349 {
350 return StringUtils.join( getShellCommandline( true ), " " );
351 }
352
353
354 /** {@inheritDoc}
355 */
356 public Object clone()
357 {
358 throw new RuntimeException( "Do we ever clone a commandline?" );
359 /* Commandline c = new Commandline( (Shell) shell.clone() );
360 c.addArguments( getArguments() );
361 return c;*/
362 }
363
364 /**
365 * Sets working directory.
366 * @param path The to be set as working directory.
367 */
368 public void setWorkingDirectory( String path )
369 {
370 shell.setWorkingDirectory( path );
371 }
372
373 /**
374 * Sets execution directory.
375 * @param workingDirectory The working directory.
376 */
377 public void setWorkingDirectory( File workingDirectory )
378 {
379 shell.setWorkingDirectory( workingDirectory );
380 }
381
382 /**
383 * @return The working directory.
384 */
385 public File getWorkingDirectory()
386 {
387 return shell.getWorkingDirectory();
388 }
389
390 /**
391 * Clear out the arguments but leave the executable in place for another operation.
392 */
393 public void clearArgs()
394 {
395 arguments.clear();
396 }
397
398 /**
399 * Executes the command.
400 * @return The process.
401 * @throws CommandLineException in case of errors.
402 */
403 public Process execute()
404 throws CommandLineException
405 {
406 Process process;
407
408 //addEnvironment( "MAVEN_TEST_ENVAR", "MAVEN_TEST_ENVAR_VALUE" );
409
410 String[] environment = getEnvironmentVariables();
411
412 File workingDir = shell.getWorkingDirectory();
413
414 try
415 {
416 if ( workingDir == null )
417 {
418 process = Runtime.getRuntime().exec( getShellCommandline(), environment );
419 }
420 else
421 {
422 if ( !workingDir.exists() )
423 {
424 throw new CommandLineException(
425 "Working directory \"" + workingDir.getPath() + "\" does not exist!" );
426 }
427 else if ( !workingDir.isDirectory() )
428 {
429 throw new CommandLineException(
430 "Path \"" + workingDir.getPath() + "\" does not specify a directory." );
431 }
432
433 process = Runtime.getRuntime().exec( getShellCommandline(), environment, workingDir );
434 }
435 }
436 catch ( IOException ex )
437 {
438 throw new CommandLineException( "Error while executing process.", ex );
439 }
440
441 return process;
442 }
443
444 /**
445 * Allows to set the shell to be used in this command line.
446 *
447 * @param shell the shell
448 */
449 void setShell( Shell shell )
450 {
451 this.shell = shell;
452 }
453
454 /**
455 * Get the shell to be used in this command line.
456 * @return the shell.
457 */
458 public Shell getShell()
459 {
460 return shell;
461 }
462
463 /**
464 *
465 */
466 public static class Argument
467 implements Arg
468 {
469 private String[] parts;
470
471 private boolean mask;
472
473 /**
474 * {@inheritDoc}
475 */
476 public void setValue( String value )
477 {
478 if ( value != null )
479 {
480 parts = new String[]{ value };
481 }
482 }
483
484 /**
485 * {@inheritDoc}
486 */
487 public void setLine( String line )
488 {
489 if ( line == null )
490 {
491 return;
492 }
493 try
494 {
495 parts = CommandLineUtils.translateCommandline( line );
496 }
497 catch ( Exception e )
498 {
499 System.err.println( "Error translating Commandline." );
500 }
501 }
502
503 /**
504 * {@inheritDoc}
505 */
506 public void setFile( File value )
507 {
508 parts = new String[]{ value.getAbsolutePath() };
509 }
510
511 /**
512 * {@inheritDoc}
513 */
514 public void setMask( boolean mask )
515 {
516 this.mask = mask;
517 }
518
519 /**
520 * @return The parts.
521 */
522 private String[] getParts()
523 {
524 return parts;
525 }
526
527 /**
528 * @return true/false
529 */
530 public boolean isMask()
531 {
532 return mask;
533 }
534 }
535 }