All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.util.cli.CommandLineUtils Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
package org.codehaus.plexus.util.cli;

/*
 * Copyright The Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;

/**
 * @author Trygve Laugstøl 
 * @version $Id$
 */
public abstract class CommandLineUtils
{
    private static Map processes = Collections.synchronizedMap( new HashMap() );

    private static Thread shutdownHook = new Thread( "CommandlineUtil shutdown" )
    {
        public void run()
        {
            if ( ( processes != null ) && ( processes.size() > 0 ) )
            {
                System.err.println( "Destroying " + processes.size() + " processes" );
                for ( Iterator it = processes.values().iterator(); it.hasNext(); )
                {
                    System.err.println( "Destroying process.." );
                    ( (Process) it.next() ).destroy();

                }
                System.err.println( "Destroyed " + processes.size() + " processes" );
            }
        }
    };

    static
    {
        shutdownHook.setContextClassLoader( null );
        addShutdownHook();
    }

    public static void addShutdownHook()
    {
        Runtime.getRuntime().addShutdownHook( shutdownHook );
    }

    public static void removeShutdownHook( boolean execute )
    {
        Runtime.getRuntime().removeShutdownHook( shutdownHook );

        if ( execute )
        {
            shutdownHook.run();
        }
    }

    public static class StringStreamConsumer
        implements StreamConsumer
    {
        private StringBuffer string = new StringBuffer();

        private String ls = System.getProperty( "line.separator" );

        public void consumeLine( String line )
        {
            string.append( line ).append( ls );
        }

        public String getOutput()
        {
            return string.toString();
        }
    }

    public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr )
        throws CommandLineException
    {
        return executeCommandLine( cl, null, systemOut, systemErr, 0 );
    }

    public static int executeCommandLine( Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr,
                                          int timeoutInSeconds )
        throws CommandLineException
    {
        return executeCommandLine( cl, null, systemOut, systemErr, timeoutInSeconds );
    }

    public static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
                                          StreamConsumer systemErr )
        throws CommandLineException
    {
        return executeCommandLine( cl, systemIn, systemOut, systemErr, 0 );
    }

    /**
     * @param cl  The command line to execute
     * @param systemIn  The input to read from, must be thread safe
     * @param systemOut  A consumer that receives output, must be thread safe
     * @param systemErr  A consumer that receives system error stream output, must be thread safe
     * @param timeoutInSeconds Positive integer to specify timeout, zero and negative integers for no timeout.
     * @return A return value, see {@link Process#exitValue()}
     * @throws CommandLineException or CommandLineTimeOutException if time out occurs
     */
    public static int executeCommandLine( Commandline cl, InputStream systemIn, StreamConsumer systemOut,
                                          StreamConsumer systemErr, int timeoutInSeconds )
        throws CommandLineException
    {
        if ( cl == null )
        {
            throw new IllegalArgumentException( "cl cannot be null." );
        }

        Process p;

        p = cl.execute();

        processes.put( new Long( cl.getPid() ), p );

        StreamFeeder inputFeeder = null;

        if ( systemIn != null )
        {
            inputFeeder = new StreamFeeder( systemIn, p.getOutputStream() );
        }

        StreamPumper outputPumper = new StreamPumper( p.getInputStream(), systemOut );

        StreamPumper errorPumper = new StreamPumper( p.getErrorStream(), systemErr );

        if ( inputFeeder != null )
        {
            inputFeeder.start();
        }

        outputPumper.start();

        errorPumper.start();

        try
        {
            int returnValue;
            if ( timeoutInSeconds <= 0 )
            {
                returnValue = p.waitFor();
            }
            else
            {
                long now = System.currentTimeMillis();
                long timeoutInMillis = 1000L * timeoutInSeconds;
                long finish = now + timeoutInMillis;
                while ( isAlive( p ) && ( System.currentTimeMillis() < finish ) )
                {
                    Thread.sleep( 10 );
                }
                if ( isAlive( p ) )
                {
                    throw new InterruptedException( "Process timeout out after " + timeoutInSeconds + " seconds" );
                }
                returnValue = p.exitValue();
            }

            if ( inputFeeder != null )
            {
                synchronized ( inputFeeder )
                {
                    while ( !inputFeeder.isDone() )
                    {
                        inputFeeder.wait();
                    }
                }
            }

            synchronized ( outputPumper )
            {
                while ( !outputPumper.isDone() )
                {
                    outputPumper.wait();
                }
            }

            synchronized ( errorPumper )
            {
                while ( !errorPumper.isDone() )
                {
                    errorPumper.wait();
                }
            }

            processes.remove( new Long( cl.getPid() ) );

            if ( outputPumper.getException() != null )
            {
                throw new CommandLineException( "Error inside systemOut parser", outputPumper.getException() );
            }

            if ( errorPumper.getException() != null )
            {
                throw new CommandLineException( "Error inside systemErr parser", errorPumper.getException() );
            }

            return returnValue;
        }
        catch ( InterruptedException ex )
        {
            killProcess( cl.getPid() );
            throw new CommandLineTimeOutException( "Error while executing external command, process killed.", ex );
        }
        finally
        {
            if ( inputFeeder != null )
            {
                inputFeeder.close();
            }

            outputPumper.close();

            errorPumper.close();

            p.destroy();

            if ( processes.get( new Long( cl.getPid() ) ) != null )
            {
                processes.remove( new Long( cl.getPid() ) );
            }
        }
    }

    /**
     * Gets the shell environment variables for this process. Note that the returned mapping from variable names to
     * values will always be case-sensitive regardless of the platform, i.e. getSystemEnvVars().get("path")
     * and getSystemEnvVars().get("PATH") will in general return different values. However, on platforms
     * with case-insensitive environment variables like Windows, all variable names will be normalized to upper case.
     *
     * @return The shell environment variables, can be empty but never null.
     * @throws IOException If the environment variables could not be queried from the shell.
     * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result
     * since 2.0.2 System#getenv() will be used if available in the current running jvm.
     */
    public static Properties getSystemEnvVars()
        throws IOException
    {
        return getSystemEnvVars( !Os.isFamily( Os.FAMILY_WINDOWS ) );
    }

    /**
     * Return the shell environment variables. If caseSensitive == true, then envar
     * keys will all be upper-case.
     *
     * @param caseSensitive Whether environment variable keys should be treated case-sensitively.
     * @return Properties object of (possibly modified) envar keys mapped to their values.
     * @throws IOException
     * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result
     * since 2.0.2 System#getenv() will be used if available in the current running jvm.
     */
    public static Properties getSystemEnvVars( boolean caseSensitive )
        throws IOException
    {
        
        // check if it's 1.5+ run 
        
        Method getenvMethod = getEnvMethod();
        if ( getenvMethod != null )
        {
            try
            {
                return getEnvFromSystem( getenvMethod, caseSensitive );
            }
            catch ( IllegalAccessException e )
            {
                throw new IOException( e.getMessage() );
            }
            catch ( IllegalArgumentException e )
            {
                throw new IOException( e.getMessage() );
            }
            catch ( InvocationTargetException e )
            {
                throw new IOException( e.getMessage() );
            }
        }
        
        Process p = null;

        try
        {
            Properties envVars = new Properties();

            Runtime r = Runtime.getRuntime();

            //If this is windows set the shell to command.com or cmd.exe with correct arguments.
            boolean overriddenEncoding = false;
            if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
            {
                if ( Os.isFamily( Os.FAMILY_WIN9X ) )
                {
                    p = r.exec( "command.com /c set" );
                }
                else
                {
                    overriddenEncoding = true;
                    // /U = change stdout encoding to UTF-16LE to avoid encoding inconsistency
                    // between command-line/DOS and GUI/Windows, see PLXUTILS-124
                    p = r.exec( "cmd.exe /U /c set" );
                }
            }
            else
            {
                p = r.exec( "env" );
            }

            Reader reader = overriddenEncoding
                ? new InputStreamReader( p.getInputStream(), ReaderFactory.UTF_16LE )
                : new InputStreamReader( p.getInputStream() );
            BufferedReader br = new BufferedReader( reader );

            String line;

            String lastKey = null;
            String lastVal = null;

            while ( ( line = br.readLine() ) != null )
            {
                int idx = line.indexOf( '=' );

                if ( idx > 0 )
                {
                    lastKey = line.substring( 0, idx );

                    if ( !caseSensitive )
                    {
                        lastKey = lastKey.toUpperCase( Locale.ENGLISH );
                    }

                    lastVal = line.substring( idx + 1 );

                    envVars.setProperty( lastKey, lastVal );
                }
                else if ( lastKey != null )
                {
                    lastVal += "\n" + line;

                    envVars.setProperty( lastKey, lastVal );
                }
            }

            return envVars;
        }
        finally
        {
            if ( p != null )
            {
                p.destroy();
            }
        }
    }

    /**
     * Kill a process launched by executeCommandLine methods.
     * Doesn't work correctly on windows, only the cmd process will be destroy but not the sub process (Bug ID 4770092)
     *
     * @param pid The pid of command return by Commandline.getPid()
     */
    public static void killProcess( long pid )
    {
        Process p = (Process) processes.get( new Long( pid ) );

        if ( p != null )
        {
            p.destroy();
            System.out.println( "Process " + pid + " is killed." );
            processes.remove( new Long( pid ) );
        }
        else
        {
            System.out.println( "don't exist." );
        }
    }

    public static boolean isAlive( long pid )
    {
        return ( processes.get( new Long( pid ) ) != null );
    }

    public static boolean isAlive( Process p )
    {
        if ( p == null )
        {
            return false;
        }

        try
        {
            p.exitValue();
            return false;
        }
        catch ( IllegalThreadStateException e )
        {
            return true;
        }
    }

    public static String[] translateCommandline( String toProcess )
        throws Exception
    {
        if ( ( toProcess == null ) || ( toProcess.length() == 0 ) )
        {
            return new String[0];
        }

        // parse with a simple finite state machine

        final int normal = 0;
        final int inQuote = 1;
        final int inDoubleQuote = 2;
        int state = normal;
        StringTokenizer tok = new StringTokenizer( toProcess, "\"\' ", true );
        Vector v = new Vector();
        StringBuffer current = new StringBuffer();

        while ( tok.hasMoreTokens() )
        {
            String nextTok = tok.nextToken();
            switch ( state )
            {
                case inQuote:
                    if ( "\'".equals( nextTok ) )
                    {
                        state = normal;
                    }
                    else
                    {
                        current.append( nextTok );
                    }
                    break;
                case inDoubleQuote:
                    if ( "\"".equals( nextTok ) )
                    {
                        state = normal;
                    }
                    else
                    {
                        current.append( nextTok );
                    }
                    break;
                default:
                    if ( "\'".equals( nextTok ) )
                    {
                        state = inQuote;
                    }
                    else if ( "\"".equals( nextTok ) )
                    {
                        state = inDoubleQuote;
                    }
                    else if ( " ".equals( nextTok ) )
                    {
                        if ( current.length() != 0 )
                        {
                            v.addElement( current.toString() );
                            current.setLength( 0 );
                        }
                    }
                    else
                    {
                        current.append( nextTok );
                    }
                    break;
            }
        }

        if ( current.length() != 0 )
        {
            v.addElement( current.toString() );
        }

        if ( ( state == inQuote ) || ( state == inDoubleQuote ) )
        {
            throw new CommandLineException( "unbalanced quotes in " + toProcess );
        }

        String[] args = new String[v.size()];
        v.copyInto( args );
        return args;
    }

    /**
     * 

Put quotes around the given String if necessary.

*

If the argument doesn't include spaces or quotes, return it * as is. If it contains double quotes, use single quotes - else * surround the argument by double quotes.

* * @throws CommandLineException if the argument contains both, single * and double quotes. * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, * {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or * {@link StringUtils#quoteAndEscape(String, char)} instead. */ public static String quote( String argument ) throws CommandLineException { return quote( argument, false, false, true ); } /** *

Put quotes around the given String if necessary.

*

If the argument doesn't include spaces or quotes, return it * as is. If it contains double quotes, use single quotes - else * surround the argument by double quotes.

* * @throws CommandLineException if the argument contains both, single * and double quotes. * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, * {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or * {@link StringUtils#quoteAndEscape(String, char)} instead. */ public static String quote( String argument, boolean wrapExistingQuotes ) throws CommandLineException { return quote( argument, false, false, wrapExistingQuotes ); } /** * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, * {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or * {@link StringUtils#quoteAndEscape(String, char)} instead. */ public static String quote( String argument, boolean escapeSingleQuotes, boolean escapeDoubleQuotes, boolean wrapExistingQuotes ) throws CommandLineException { if ( argument.indexOf( "\"" ) > -1 ) { if ( argument.indexOf( "\'" ) > -1 ) { throw new CommandLineException( "Can't handle single and double quotes in same argument" ); } else { if ( escapeSingleQuotes ) { return "\\\'" + argument + "\\\'"; } else if ( wrapExistingQuotes ) { return '\'' + argument + '\''; } } } else if ( argument.indexOf( "\'" ) > -1 ) { if ( escapeDoubleQuotes ) { return "\\\"" + argument + "\\\""; } else if ( wrapExistingQuotes ) { return '\"' + argument + '\"'; } } else if ( argument.indexOf( " " ) > -1 ) { if ( escapeDoubleQuotes ) { return "\\\"" + argument + "\\\""; } else { return '\"' + argument + '\"'; } } return argument; } public static String toString( String[] line ) { // empty path return empty string if ( ( line == null ) || ( line.length == 0 ) ) { return ""; } // path containing one or more elements final StringBuffer result = new StringBuffer(); for ( int i = 0; i < line.length; i++ ) { if ( i > 0 ) { result.append( ' ' ); } try { result.append( StringUtils.quoteAndEscape( line[i], '\"' ) ); } catch ( Exception e ) { System.err.println( "Error quoting argument: " + e.getMessage() ); } } return result.toString(); } private static Method getEnvMethod() { try { return System.class.getMethod( "getenv", null ); } catch ( NoSuchMethodException e ) { return null; } catch ( SecurityException e ) { return null; } } private static Properties getEnvFromSystem( Method method, boolean caseSensitive ) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Properties envVars = new Properties(); Map envs = (Map) method.invoke( null, null ); Iterator iterator = envs.keySet().iterator(); while ( iterator.hasNext() ) { String key = (String) iterator.next(); String value = (String) envs.get( key ); if ( !caseSensitive ) { key = key.toUpperCase( Locale.ENGLISH ); } envVars.put( key, value ); } return envVars; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy