Shell.java

package org.codehaus.plexus.util.cli.shell;

/*
 * 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.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.codehaus.plexus.util.StringUtils;

/**
 * Class that abstracts the Shell functionality, with subclasses for shells that behave particularly, like
 * <ul>
 * <li><code>command.com</code></li>
 * <li><code>cmd.exe</code></li>
 * </ul>
 *
 * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
 * @since 1.2
 *
 */
public class Shell implements Cloneable {
    private static final char[] DEFAULT_QUOTING_TRIGGER_CHARS = {' '};

    private String shellCommand;

    private List<String> shellArgs = new ArrayList<String>();

    private boolean quotedArgumentsEnabled = true;

    private boolean unconditionallyQuote = false;

    private String executable;

    private String workingDir;

    private boolean quotedExecutableEnabled = true;

    private boolean doubleQuotedArgumentEscaped = false;

    private boolean singleQuotedArgumentEscaped = false;

    private boolean doubleQuotedExecutableEscaped = false;

    private boolean singleQuotedExecutableEscaped = false;

    private char argQuoteDelimiter = '\"';

    private char exeQuoteDelimiter = '\"';

    private String argumentEscapePattern = "\\%s";

    /**
     * Toggle unconditional quoting
     *
     * @param unconditionallyQuote see name
     */
    public void setUnconditionalQuoting(boolean unconditionallyQuote) {
        this.unconditionallyQuote = unconditionallyQuote;
    }

    /**
     * Set the command to execute the shell (eg. COMMAND.COM, /bin/bash,...)
     *
     * @param shellCommand see name
     */
    public void setShellCommand(String shellCommand) {
        this.shellCommand = shellCommand;
    }

    /**
     * Get the command to execute the shell
     *
     * @return the command
     */
    public String getShellCommand() {
        return shellCommand;
    }

    /**
     * Set the shell arguments when calling a command line (not the executable arguments) (eg. /X /C for CMD.EXE)
     *
     * @param shellArgs see name
     */
    public void setShellArgs(String[] shellArgs) {
        this.shellArgs.clear();
        this.shellArgs.addAll(Arrays.asList(shellArgs));
    }

    /**
     * @return the shell arguments
     */
    public String[] getShellArgs() {
        if ((shellArgs == null) || shellArgs.isEmpty()) {
            return null;
        } else {
            return shellArgs.toArray(new String[0]);
        }
    }

    /**
     * Get the command line for the provided executable and arguments in this shell
     *
     * @param executable executable that the shell has to call
     * @param arguments arguments for the executable, not the shell
     * @return List with one String object with executable and arguments quoted as needed
     */
    public List<String> getCommandLine(String executable, String[] arguments) {
        return getRawCommandLine(executable, arguments);
    }

    protected String quoteOneItem(String inputString, boolean isExecutable) {
        char[] escapeChars = getEscapeChars(isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped());
        return StringUtils.quoteAndEscape(
                inputString,
                isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
                escapeChars,
                getQuotingTriggerChars(),
                '\\',
                unconditionallyQuote);
    }

    protected List<String> getRawCommandLine(String executable, String[] arguments) {
        List<String> commandLine = new ArrayList<String>();
        StringBuilder sb = new StringBuilder();

        if (executable != null) {
            String preamble = getExecutionPreamble();
            if (preamble != null) {
                sb.append(preamble);
            }

            if (isQuotedExecutableEnabled()) {
                sb.append(quoteOneItem(getOriginalExecutable(), true));
            } else {
                sb.append(getExecutable());
            }
        }
        for (String argument : arguments) {
            if (sb.length() > 0) {
                sb.append(" ");
            }

            if (isQuotedArgumentsEnabled()) {
                sb.append(quoteOneItem(argument, false));
            } else {
                sb.append(argument);
            }
        }

        commandLine.add(sb.toString());

        return commandLine;
    }

    protected char[] getQuotingTriggerChars() {
        return DEFAULT_QUOTING_TRIGGER_CHARS;
    }

    protected String getExecutionPreamble() {
        return null;
    }

    protected char[] getEscapeChars(boolean includeSingleQuote, boolean includeDoubleQuote) {
        StringBuilder buf = new StringBuilder(2);
        if (includeSingleQuote) {
            buf.append('\'');
        }

        if (includeDoubleQuote) {
            buf.append('\"');
        }

        char[] result = new char[buf.length()];
        buf.getChars(0, buf.length(), result, 0);

        return result;
    }

    protected boolean isDoubleQuotedArgumentEscaped() {
        return doubleQuotedArgumentEscaped;
    }

    protected boolean isSingleQuotedArgumentEscaped() {
        return singleQuotedArgumentEscaped;
    }

    protected boolean isDoubleQuotedExecutableEscaped() {
        return doubleQuotedExecutableEscaped;
    }

    protected boolean isSingleQuotedExecutableEscaped() {
        return singleQuotedExecutableEscaped;
    }

    protected void setArgumentQuoteDelimiter(char argQuoteDelimiter) {
        this.argQuoteDelimiter = argQuoteDelimiter;
    }

    protected char getArgumentQuoteDelimiter() {
        return argQuoteDelimiter;
    }

    protected void setExecutableQuoteDelimiter(char exeQuoteDelimiter) {
        this.exeQuoteDelimiter = exeQuoteDelimiter;
    }

    protected char getExecutableQuoteDelimiter() {
        return exeQuoteDelimiter;
    }

    protected void setArgumentEscapePattern(String argumentEscapePattern) {
        this.argumentEscapePattern = argumentEscapePattern;
    }

    protected String getArgumentEscapePattern() {
        return argumentEscapePattern;
    }

    /**
     * Get the full command line to execute, including shell command, shell arguments, executable and executable
     * arguments
     *
     * @param arguments arguments for the executable, not the shell
     * @return List of String objects, whose array version is suitable to be used as argument of
     *         Runtime.getRuntime().exec()
     */
    public List<String> getShellCommandLine(String[] arguments) {

        List<String> commandLine = new ArrayList<String>();

        if (getShellCommand() != null) {
            commandLine.add(getShellCommand());
        }

        if (getShellArgs() != null) {
            commandLine.addAll(getShellArgsList());
        }

        commandLine.addAll(getCommandLine(getOriginalExecutable(), arguments));

        return commandLine;
    }

    public List<String> getShellArgsList() {
        return shellArgs;
    }

    public void addShellArg(String arg) {
        shellArgs.add(arg);
    }

    public void setQuotedArgumentsEnabled(boolean quotedArgumentsEnabled) {
        this.quotedArgumentsEnabled = quotedArgumentsEnabled;
    }

    public boolean isQuotedArgumentsEnabled() {
        return quotedArgumentsEnabled;
    }

    public void setQuotedExecutableEnabled(boolean quotedExecutableEnabled) {
        this.quotedExecutableEnabled = quotedExecutableEnabled;
    }

    public boolean isQuotedExecutableEnabled() {
        return quotedExecutableEnabled;
    }

    /**
     *
     * @param executable Sets the executable to run.
     */
    public void setExecutable(String executable) {
        if ((executable == null) || (executable.length() == 0)) {
            return;
        }
        this.executable = executable.replace('/', File.separatorChar).replace('\\', File.separatorChar);
    }

    public String getExecutable() {
        return executable;
    }

    /**
     * @param path Sets execution directory.
     */
    public void setWorkingDirectory(String path) {
        if (path != null) {
            workingDir = path;
        }
    }

    /**
     * @param workingDir Sets execution directory.
     */
    public void setWorkingDirectory(File workingDir) {
        if (workingDir != null) {
            this.workingDir = workingDir.getAbsolutePath();
        }
    }

    public File getWorkingDirectory() {
        return workingDir == null ? null : new File(workingDir);
    }

    public String getWorkingDirectoryAsString() {
        return workingDir;
    }

    public void clearArguments() {
        shellArgs.clear();
    }

    @Override
    public Object clone() {
        Shell shell = new Shell();
        shell.setExecutable(getExecutable());
        shell.setWorkingDirectory(getWorkingDirectory());
        shell.setShellArgs(getShellArgs());
        return shell;
    }

    public String getOriginalExecutable() {
        return executable;
    }

    public List<String> getOriginalCommandLine(String executable, String[] arguments) {
        return getRawCommandLine(executable, arguments);
    }

    protected void setDoubleQuotedArgumentEscaped(boolean doubleQuotedArgumentEscaped) {
        this.doubleQuotedArgumentEscaped = doubleQuotedArgumentEscaped;
    }

    protected void setDoubleQuotedExecutableEscaped(boolean doubleQuotedExecutableEscaped) {
        this.doubleQuotedExecutableEscaped = doubleQuotedExecutableEscaped;
    }

    protected void setSingleQuotedArgumentEscaped(boolean singleQuotedArgumentEscaped) {
        this.singleQuotedArgumentEscaped = singleQuotedArgumentEscaped;
    }

    protected void setSingleQuotedExecutableEscaped(boolean singleQuotedExecutableEscaped) {
        this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
    }
}