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