Coverage Report - org.codehaus.plexus.util.cli.CommandLineUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
CommandLineUtils
73%
75/102
64%
43/67
4.684
CommandLineUtils$1
100%
6/6
N/A
4.684
CommandLineUtils$2
75%
48/64
53%
17/32
4.684
CommandLineUtils$StringStreamConsumer
83%
5/6
N/A
4.684
 
 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  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.util.Locale;
 22  
 import java.util.Map;
 23  
 import java.util.Properties;
 24  
 import java.util.StringTokenizer;
 25  
 import java.util.Vector;
 26  
 
 27  
 import org.codehaus.plexus.util.Os;
 28  
 import org.codehaus.plexus.util.StringUtils;
 29  
 
 30  
 /**
 31  
  * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l </a>
 32  
  * @version $Id$
 33  
  */
 34  40
 public abstract class CommandLineUtils
 35  
 {
 36  
 
 37  
     /**
 38  
      * A {@code StreamConsumer} providing consumed lines as a {@code String}.
 39  
      *
 40  
      * @see #getOutput()
 41  
      */
 42  0
     public static class StringStreamConsumer
 43  
         implements StreamConsumer
 44  
     {
 45  
 
 46  10
         private StringBuffer string = new StringBuffer();
 47  
 
 48  10
         private String ls = System.getProperty( "line.separator" );
 49  
 
 50  
         public void consumeLine( String line )
 51  
         {
 52  6
             string.append( line ).append( ls );
 53  6
         }
 54  
 
 55  
         public String getOutput()
 56  
         {
 57  0
             return string.toString();
 58  
         }
 59  
 
 60  
     }
 61  
 
 62  
     /**
 63  
      * Number of milliseconds per second.
 64  
      */
 65  
     private static final long MILLIS_PER_SECOND = 1000L;
 66  
 
 67  
     /**
 68  
      * Number of nanoseconds per second.
 69  
      */
 70  
     private static final long NANOS_PER_SECOND = 1000000000L;
 71  
 
 72  
     public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr )
 73  
         throws CommandLineException
 74  
     {
 75  9
         return executeCommandLine( cl, null, systemOut, systemErr, 0 );
 76  
     }
 77  
 
 78  
     public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr,
 79  
                                           int timeoutInSeconds )
 80  
         throws CommandLineException
 81  
     {
 82  1
         return executeCommandLine( cl, null, systemOut, systemErr, timeoutInSeconds );
 83  
     }
 84  
 
 85  
     public static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
 86  
                                           StreamConsumer systemErr )
 87  
         throws CommandLineException
 88  
     {
 89  0
         return executeCommandLine( cl, systemIn, systemOut, systemErr, 0 );
 90  
     }
 91  
 
 92  
     /**
 93  
      * @param cl The command line to execute
 94  
      * @param systemIn The input to read from, must be thread safe
 95  
      * @param systemOut A consumer that receives output, must be thread safe
 96  
      * @param systemErr A consumer that receives system error stream output, must be thread safe
 97  
      * @param timeoutInSeconds Positive integer to specify timeout, zero and negative integers for no timeout.
 98  
      * @return A return value, see {@link Process#exitValue()}
 99  
      * @throws CommandLineException or CommandLineTimeOutException if time out occurs
 100  
      * @noinspection ThrowableResultOfMethodCallIgnored
 101  
      */
 102  
     public static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
 103  
                                           StreamConsumer systemErr, int timeoutInSeconds )
 104  
         throws CommandLineException
 105  
     {
 106  10
         final CommandLineCallable future =
 107  
             executeCommandLineAsCallable( cl, systemIn, systemOut, systemErr, timeoutInSeconds );
 108  10
         return future.call();
 109  
     }
 110  
 
 111  
     /**
 112  
      * Immediately forks a process, returns a callable that will block until process is complete.
 113  
      * 
 114  
      * @param cl The command line to execute
 115  
      * @param systemIn The input to read from, must be thread safe
 116  
      * @param systemOut A consumer that receives output, must be thread safe
 117  
      * @param systemErr A consumer that receives system error stream output, must be thread safe
 118  
      * @param timeoutInSeconds Positive integer to specify timeout, zero and negative integers for no timeout.
 119  
      * @return A CommandLineCallable that provides the process return value, see {@link Process#exitValue()}. "call"
 120  
      *         must be called on this to be sure the forked process has terminated, no guarantees is made about any
 121  
      *         internal state before after the completion of the call statements
 122  
      * @throws CommandLineException or CommandLineTimeOutException if time out occurs
 123  
      * @noinspection ThrowableResultOfMethodCallIgnored
 124  
      */
 125  
     public static CommandLineCallable executeCommandLineAsCallable( final Commandline cl, final InputStream systemIn,
 126  
                                                                     final StreamConsumer systemOut,
 127  
                                                                     final StreamConsumer systemErr,
 128  
                                                                     final int timeoutInSeconds )
 129  
         throws CommandLineException
 130  
     {
 131  10
         if ( cl == null )
 132  
         {
 133  0
             throw new IllegalArgumentException( "cl cannot be null." );
 134  
         }
 135  
 
 136  10
         final Process p = cl.execute();
 137  
 
 138  10
         final Thread processHook = new Thread()
 139  10
         {
 140  
 
 141  
             {
 142  10
                 this.setName( "CommandLineUtils process shutdown hook" );
 143  10
                 this.setContextClassLoader( null );
 144  10
             }
 145  
 
 146  
             @Override
 147  
             public void run()
 148  
             {
 149  10
                 p.destroy();
 150  10
             }
 151  
 
 152  
         };
 153  
 
 154  10
         ShutdownHookUtils.addShutDownHook( processHook );
 155  
 
 156  10
         return new CommandLineCallable()
 157  10
         {
 158  
 
 159  
             public Integer call()
 160  
                 throws CommandLineException
 161  
             {
 162  10
                 StreamFeeder inputFeeder = null;
 163  10
                 StreamPumper outputPumper = null;
 164  10
                 StreamPumper errorPumper = null;
 165  10
                 boolean success = false;
 166  
                 try
 167  
                 {
 168  10
                     if ( systemIn != null )
 169  
                     {
 170  0
                         inputFeeder = new StreamFeeder( systemIn, p.getOutputStream() );
 171  0
                         inputFeeder.start();
 172  
                     }
 173  
 
 174  10
                     outputPumper = new StreamPumper( p.getInputStream(), systemOut );
 175  10
                     outputPumper.start();
 176  
 
 177  10
                     errorPumper = new StreamPumper( p.getErrorStream(), systemErr );
 178  10
                     errorPumper.start();
 179  
 
 180  
                     int returnValue;
 181  10
                     if ( timeoutInSeconds <= 0 )
 182  
                     {
 183  9
                         returnValue = p.waitFor();
 184  
                     }
 185  
                     else
 186  
                     {
 187  1
                         final long now = System.nanoTime();
 188  1
                         final long timeout = now + NANOS_PER_SECOND * timeoutInSeconds;
 189  
 
 190  2
                         while ( isAlive( p ) && ( System.nanoTime() < timeout ) )
 191  
                         {
 192  
                             // The timeout is specified in seconds. Therefore we must not sleep longer than one second
 193  
                             // but we should sleep as long as possible to reduce the number of iterations performed.
 194  1
                             Thread.sleep( MILLIS_PER_SECOND - 1L );
 195  
                         }
 196  
 
 197  1
                         if ( isAlive( p ) )
 198  
                         {
 199  0
                             throw new InterruptedException( String.format( "Process timed out after %d seconds.",
 200  
                                                                            timeoutInSeconds ) );
 201  
                         }
 202  
 
 203  1
                         returnValue = p.exitValue();
 204  
                     }
 205  
 
 206  
                     // TODO Find out if waitUntilDone needs to be called using a try-finally construct. The method may
 207  
                     // throw an
 208  
                     // InterruptedException so that calls to waitUntilDone may be skipped.
 209  
                     // try
 210  
                     // {
 211  
                     // if ( inputFeeder != null )
 212  
                     // {
 213  
                     // inputFeeder.waitUntilDone();
 214  
                     // }
 215  
                     // }
 216  
                     // finally
 217  
                     // {
 218  
                     // try
 219  
                     // {
 220  
                     // outputPumper.waitUntilDone();
 221  
                     // }
 222  
                     // finally
 223  
                     // {
 224  
                     // errorPumper.waitUntilDone();
 225  
                     // }
 226  
                     // }
 227  10
                     if ( inputFeeder != null )
 228  
                     {
 229  0
                         inputFeeder.waitUntilDone();
 230  
                     }
 231  
 
 232  10
                     outputPumper.waitUntilDone();
 233  10
                     errorPumper.waitUntilDone();
 234  
 
 235  10
                     if ( inputFeeder != null )
 236  
                     {
 237  0
                         inputFeeder.close();
 238  0
                         handleException( inputFeeder, "stdin" );
 239  
                     }
 240  
 
 241  10
                     outputPumper.close();
 242  10
                     handleException( outputPumper, "stdout" );
 243  
 
 244  10
                     errorPumper.close();
 245  10
                     handleException( errorPumper, "stderr" );
 246  
 
 247  10
                     success = true;
 248  10
                     return returnValue;
 249  
                 }
 250  0
                 catch ( InterruptedException ex )
 251  
                 {
 252  0
                     throw new CommandLineTimeOutException( "Error while executing external command, process killed.",
 253  
                                                            ex );
 254  
 
 255  
                 }
 256  
                 finally
 257  
                 {
 258  10
                     if ( inputFeeder != null )
 259  
                     {
 260  0
                         inputFeeder.disable();
 261  
                     }
 262  10
                     if ( outputPumper != null )
 263  
                     {
 264  10
                         outputPumper.disable();
 265  
                     }
 266  10
                     if ( errorPumper != null )
 267  
                     {
 268  10
                         errorPumper.disable();
 269  
                     }
 270  
 
 271  
                     try
 272  
                     {
 273  10
                         ShutdownHookUtils.removeShutdownHook( processHook );
 274  10
                         processHook.run();
 275  
                     }
 276  
                     finally
 277  
                     {
 278  0
                         try
 279  
                         {
 280  10
                             if ( inputFeeder != null )
 281  
                             {
 282  0
                                 inputFeeder.close();
 283  
 
 284  0
                                 if ( success )
 285  
                                 {
 286  0
                                     success = false;
 287  0
                                     handleException( inputFeeder, "stdin" );
 288  0
                                     success = true; // Only reached when no exception has been thrown.
 289  
                                 }
 290  
                             }
 291  
                         }
 292  
                         finally
 293  
                         {
 294  0
                             try
 295  
                             {
 296  10
                                 if ( outputPumper != null )
 297  
                                 {
 298  10
                                     outputPumper.close();
 299  
 
 300  10
                                     if ( success )
 301  
                                     {
 302  10
                                         success = false;
 303  10
                                         handleException( outputPumper, "stdout" );
 304  10
                                         success = true; // Only reached when no exception has been thrown.
 305  
                                     }
 306  
                                 }
 307  
                             }
 308  
                             finally
 309  
                             {
 310  10
                                 if ( errorPumper != null )
 311  
                                 {
 312  10
                                     errorPumper.close();
 313  
 
 314  10
                                     if ( success )
 315  
                                     {
 316  10
                                         handleException( errorPumper, "stderr" );
 317  
                                     }
 318  
                                 }
 319  
                             }
 320  10
                         }
 321  10
                     }
 322  
                 }
 323  
             }
 324  
 
 325  
         };
 326  
     }
 327  
 
 328  
     private static void handleException( final StreamPumper streamPumper, final String streamName )
 329  
         throws CommandLineException
 330  
     {
 331  40
         if ( streamPumper.getException() != null )
 332  
         {
 333  0
             throw new CommandLineException( String.format( "Failure processing %s.", streamName ),
 334  
                                             streamPumper.getException() );
 335  
 
 336  
         }
 337  40
     }
 338  
 
 339  
     private static void handleException( final StreamFeeder streamFeeder, final String streamName )
 340  
         throws CommandLineException
 341  
     {
 342  0
         if ( streamFeeder.getException() != null )
 343  
         {
 344  0
             throw new CommandLineException( String.format( "Failure processing %s.", streamName ),
 345  
                                             streamFeeder.getException() );
 346  
 
 347  
         }
 348  0
     }
 349  
 
 350  
     /**
 351  
      * Gets the shell environment variables for this process. Note that the returned mapping from variable names to
 352  
      * values will always be case-sensitive regardless of the platform, i.e. <code>getSystemEnvVars().get("path")</code>
 353  
      * and <code>getSystemEnvVars().get("PATH")</code> will in general return different values. However, on platforms
 354  
      * with case-insensitive environment variables like Windows, all variable names will be normalized to upper case.
 355  
      *
 356  
      * @return The shell environment variables, can be empty but never <code>null</code>.
 357  
      * @throws IOException If the environment variables could not be queried from the shell.
 358  
      * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result <b>since 2.0.2 System#getenv()
 359  
      *      will be used if available in the current running jvm.</b>
 360  
      */
 361  
     public static Properties getSystemEnvVars()
 362  
         throws IOException
 363  
     {
 364  15
         return getSystemEnvVars( !Os.isFamily( Os.FAMILY_WINDOWS ) );
 365  
     }
 366  
 
 367  
     /**
 368  
      * Return the shell environment variables. If <code>caseSensitive == true</code>, then envar keys will all be
 369  
      * upper-case.
 370  
      *
 371  
      * @param caseSensitive Whether environment variable keys should be treated case-sensitively.
 372  
      * @return Properties object of (possibly modified) envar keys mapped to their values.
 373  
      * @throws IOException .
 374  
      * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result <b>since 2.0.2 System#getenv()
 375  
      *      will be used if available in the current running jvm.</b>
 376  
      */
 377  
     public static Properties getSystemEnvVars( boolean caseSensitive )
 378  
         throws IOException
 379  
     {
 380  16
         Properties envVars = new Properties();
 381  16
         Map<String, String> envs = System.getenv();
 382  16
         for ( String key : envs.keySet() )
 383  
         {
 384  640
             String value = envs.get( key );
 385  640
             if ( !caseSensitive )
 386  
             {
 387  40
                 key = key.toUpperCase( Locale.ENGLISH );
 388  
             }
 389  640
             envVars.put( key, value );
 390  640
         }
 391  16
         return envVars;
 392  
     }
 393  
 
 394  
     public static boolean isAlive( Process p )
 395  
     {
 396  3
         if ( p == null )
 397  
         {
 398  0
             return false;
 399  
         }
 400  
 
 401  
         try
 402  
         {
 403  3
             p.exitValue();
 404  2
             return false;
 405  
         }
 406  1
         catch ( IllegalThreadStateException e )
 407  
         {
 408  1
             return true;
 409  
         }
 410  
     }
 411  
 
 412  
     public static String[] translateCommandline( String toProcess )
 413  
         throws Exception
 414  
     {
 415  12
         if ( ( toProcess == null ) || ( toProcess.length() == 0 ) )
 416  
         {
 417  2
             return new String[0];
 418  
         }
 419  
 
 420  
         // parse with a simple finite state machine
 421  
 
 422  10
         final int normal = 0;
 423  10
         final int inQuote = 1;
 424  10
         final int inDoubleQuote = 2;
 425  10
         int state = normal;
 426  10
         StringTokenizer tok = new StringTokenizer( toProcess, "\"\' ", true );
 427  10
         Vector<String> v = new Vector<String>();
 428  10
         StringBuilder current = new StringBuilder();
 429  
 
 430  70
         while ( tok.hasMoreTokens() )
 431  
         {
 432  60
             String nextTok = tok.nextToken();
 433  60
             switch ( state )
 434  
             {
 435  
                 case inQuote:
 436  10
                     if ( "\'".equals( nextTok ) )
 437  
                     {
 438  2
                         state = normal;
 439  
                     }
 440  
                     else
 441  
                     {
 442  8
                         current.append( nextTok );
 443  
                     }
 444  8
                     break;
 445  
                 case inDoubleQuote:
 446  10
                     if ( "\"".equals( nextTok ) )
 447  
                     {
 448  2
                         state = normal;
 449  
                     }
 450  
                     else
 451  
                     {
 452  8
                         current.append( nextTok );
 453  
                     }
 454  8
                     break;
 455  
                 default:
 456  40
                     if ( "\'".equals( nextTok ) )
 457  
                     {
 458  2
                         state = inQuote;
 459  
                     }
 460  38
                     else if ( "\"".equals( nextTok ) )
 461  
                     {
 462  2
                         state = inDoubleQuote;
 463  
                     }
 464  36
                     else if ( " ".equals( nextTok ) )
 465  
                     {
 466  19
                         if ( current.length() != 0 )
 467  
                         {
 468  12
                             v.addElement( current.toString() );
 469  12
                             current.setLength( 0 );
 470  
                         }
 471  
                     }
 472  
                     else
 473  
                     {
 474  17
                         current.append( nextTok );
 475  
                     }
 476  
                     break;
 477  
             }
 478  60
         }
 479  
 
 480  10
         if ( current.length() != 0 )
 481  
         {
 482  9
             v.addElement( current.toString() );
 483  
         }
 484  
 
 485  10
         if ( ( state == inQuote ) || ( state == inDoubleQuote ) )
 486  
         {
 487  0
             throw new CommandLineException( "unbalanced quotes in " + toProcess );
 488  
         }
 489  
 
 490  10
         String[] args = new String[v.size()];
 491  10
         v.copyInto( args );
 492  10
         return args;
 493  
     }
 494  
 
 495  
     /**
 496  
      * <p>
 497  
      * Put quotes around the given String if necessary.
 498  
      * </p>
 499  
      * <p>
 500  
      * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single
 501  
      * quotes - else surround the argument by double quotes.
 502  
      * </p>
 503  
      *
 504  
      * @throws CommandLineException if the argument contains both, single and double quotes.
 505  
      * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)},
 506  
      *             {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
 507  
      *             {@link StringUtils#quoteAndEscape(String, char)} instead.
 508  
      */
 509  
     @SuppressWarnings( { "JavaDoc", "deprecation" } )
 510  
     public static String quote( String argument )
 511  
         throws CommandLineException
 512  
     {
 513  4
         return quote( argument, false, false, true );
 514  
     }
 515  
 
 516  
     /**
 517  
      * <p>
 518  
      * Put quotes around the given String if necessary.
 519  
      * </p>
 520  
      * <p>
 521  
      * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single
 522  
      * quotes - else surround the argument by double quotes.
 523  
      * </p>
 524  
      *
 525  
      * @throws CommandLineException if the argument contains both, single and double quotes.
 526  
      * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)},
 527  
      *             {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
 528  
      *             {@link StringUtils#quoteAndEscape(String, char)} instead.
 529  
      */
 530  
     @SuppressWarnings( { "JavaDoc", "UnusedDeclaration", "deprecation" } )
 531  
     public static String quote( String argument, boolean wrapExistingQuotes )
 532  
         throws CommandLineException
 533  
     {
 534  0
         return quote( argument, false, false, wrapExistingQuotes );
 535  
     }
 536  
 
 537  
     /**
 538  
      * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)},
 539  
      *             {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
 540  
      *             {@link StringUtils#quoteAndEscape(String, char)} instead.
 541  
      */
 542  
     @SuppressWarnings( { "JavaDoc" } )
 543  
     public static String quote( String argument, boolean escapeSingleQuotes, boolean escapeDoubleQuotes,
 544  
                                 boolean wrapExistingQuotes )
 545  
         throws CommandLineException
 546  
     {
 547  4
         if ( argument.contains( "\"" ) )
 548  
         {
 549  2
             if ( argument.contains( "\'" ) )
 550  
             {
 551  1
                 throw new CommandLineException( "Can't handle single and double quotes in same argument" );
 552  
             }
 553  
             else
 554  
             {
 555  1
                 if ( escapeSingleQuotes )
 556  
                 {
 557  0
                     return "\\\'" + argument + "\\\'";
 558  
                 }
 559  1
                 else if ( wrapExistingQuotes )
 560  
                 {
 561  1
                     return '\'' + argument + '\'';
 562  
                 }
 563  
             }
 564  
         }
 565  2
         else if ( argument.contains( "\'" ) )
 566  
         {
 567  0
             if ( escapeDoubleQuotes )
 568  
             {
 569  0
                 return "\\\"" + argument + "\\\"";
 570  
             }
 571  0
             else if ( wrapExistingQuotes )
 572  
             {
 573  0
                 return '\"' + argument + '\"';
 574  
             }
 575  
         }
 576  2
         else if ( argument.contains( " " ) )
 577  
         {
 578  1
             if ( escapeDoubleQuotes )
 579  
             {
 580  0
                 return "\\\"" + argument + "\\\"";
 581  
             }
 582  
             else
 583  
             {
 584  1
                 return '\"' + argument + '\"';
 585  
             }
 586  
         }
 587  
 
 588  1
         return argument;
 589  
     }
 590  
 
 591  
     public static String toString( String[] line )
 592  
     {
 593  
         // empty path return empty string
 594  0
         if ( ( line == null ) || ( line.length == 0 ) )
 595  
         {
 596  0
             return "";
 597  
         }
 598  
 
 599  
         // path containing one or more elements
 600  0
         final StringBuilder result = new StringBuilder();
 601  0
         for ( int i = 0; i < line.length; i++ )
 602  
         {
 603  0
             if ( i > 0 )
 604  
             {
 605  0
                 result.append( ' ' );
 606  
             }
 607  
             try
 608  
             {
 609  0
                 result.append( StringUtils.quoteAndEscape( line[i], '\"' ) );
 610  
             }
 611  0
             catch ( Exception e )
 612  
             {
 613  0
                 System.err.println( "Error quoting argument: " + e.getMessage() );
 614  0
             }
 615  
         }
 616  0
         return result.toString();
 617  
     }
 618  
 
 619  
 }