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

org.buildobjects.process.ProcBuilder Maven / Gradle / Ivy

There is a newer version: 2.8.2
Show newest version
package org.buildobjects.process;

import java.io.*;
import java.util.*;

import static java.util.Arrays.asList;
import static org.buildobjects.process.Helper.asSet;


/** A builder to construct a new process. The process gets configured by the withXXX-methods and
 * spawned by the run() method*/
public class ProcBuilder {

    private ByteArrayOutputStream defaultStdout = new ByteArrayOutputStream();

    private String command;
    private List args = new ArrayList();
    private Map env = new HashMap<>();

    private OutputStream stdout = defaultStdout;
    private InputStream stdin;
    private OutputStream stderr;

    private Long timoutMillis = 5000L;

    private Set expectedExitStatuses = new HashSet(){{add(0);}};

    private File directory;

    private StreamConsumer outputConsumer;
    private StreamConsumer errorConsumer;
    private boolean clearEnvironment;


    /** Creates a new ProcBuilder
     * @param command The command to run
     * @param args The command line arguments
     */

    public ProcBuilder(String command, String... args) {
        this.command = command;
        withArgs(args);
    }

    /**
     * Adds another argument
     * @param arg to add
     * @return this, for chaining
     * */
    public ProcBuilder withArg(String arg) {
        args.add(arg);
        return this;

    }

    /** Redirecting the standard output. If it is not redirected the output gets captured in memory and
     * is available on the @see ProcResult
     *
     * @param stdout stream to redirect the output to. \
     * @return this, for chaining
     * */
    public ProcBuilder withOutputStream(OutputStream stdout) {
        if (outputConsumer != null) {
            throw new IllegalArgumentException("`withOutputStream(OutputStream)` and `withOutputConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }

        this.stdout = stdout;
        return this;
    }

    /** Redirecting the error output. If it is not redirected the output gets captured in memory and
     * is available on the @see ProcResult
     *
     * @param stderr stream to redirect the output to. \
     * @return this, for chaining
     * */
    public ProcBuilder withErrorStream(OutputStream stderr) {
        if (errorConsumer != null) {
            throw new IllegalArgumentException("`withErrorStream(OutputStream)` and `withErrorConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }
        this.stderr = stderr;
        return this;
    }


    /** Specify a timeout for the operation. If not specified the default is 5 seconds.
     * @param timeoutMillis time that the process gets to run
     * @return this, for chaining
     * */
    public ProcBuilder withTimeoutMillis(long timeoutMillis) {
        this.timoutMillis = timeoutMillis;
        return this;
    }

    /** Disable timeout for the operation.
     *
     * @return this, for chaining
     * */
    public ProcBuilder withNoTimeout() {
        this.timoutMillis = null;
        return this;
    }


    /** Take the input for the program from a given InputStream
     * @param stdin stream to read the input from
     * @return this, for chaining
     */
    public ProcBuilder withInputStream(InputStream stdin) {
        this.stdin = stdin;
        return this;
    }

    /** Supply the input as string
     * @param input the actual input
     * @return this, for chaining
     */
    public ProcBuilder withInput(String input) {
        stdin = new ByteArrayInputStream(input.getBytes());
        return this;
    }


    /** Supply the input as byte[]
     * @param input the actual input
     * @return this, for chaining
     */
    public ProcBuilder withInput(byte[] input) {
        stdin = new ByteArrayInputStream(input);
        return this;
    }

    /** Override the wokring directory
     * @param directory the working directory for the process
     * @return this, for chaining
     */
    public ProcBuilder withWorkingDirectory(File directory) {
        if (!directory.isDirectory()) {
            throw new IllegalArgumentException("File '" + directory.getPath() + "' is not a directory.");
        }
        this.directory = directory;
        return this;
    }


    /** Add multiple args
     *   @param args the arguments add
     *   @return this, for chaining
     */
    public ProcBuilder withArgs(String... args) {
        this.args.addAll(asList(args));
        return this;
    }

    /** Define the valid exit status codes for the command
     *
     * @param exitstatuses array containing the exit codes that are valid
     * @return the ProcBuilder object; permits chaining.
     * @author Mark Galbraith ([email protected])
     *
     * @deprecated Please use the variants with a set or vargs parameters*/

    public ProcBuilder withExitStatuses(int[] exitstatuses) {
        this.expectedExitStatuses = asSet(exitstatuses);
        return this;
    }

    /** Define the valid exit status codes for the command
     *
     * @param expectedExitStatuses array containing the exit codes that are valid
     * @return the ProcBuilder object; permits chaining.
     * @author Mark Galbraith ([email protected])
     */
    public ProcBuilder withExpectedExitStatuses(Set expectedExitStatuses) {
        this.expectedExitStatuses = expectedExitStatuses;
        return this;
    }


    /** Define the valid exit status codes for the command
     *  Convenience method taking varargs.
     *
     * @param expectedExitStatuses varargs parameter containing the exit codes that are valid
     * @return the ProcBuilder object; permits chaining.
     * @author Mark Galbraith ([email protected])
     */
    public ProcBuilder withExpectedExitStatuses(int... expectedExitStatuses) {
        this.expectedExitStatuses = asSet(expectedExitStatuses);
        return this;
    }

    /** Ignore the error status returned from this command
     *
     * @return the ProcBuilder object; permits chaining.
     */
    public ProcBuilder ignoreExitStatus() {
        this.expectedExitStatuses = Collections.emptySet();
        return this;
    }

    /** Spawn the actual execution.
     *  This will block until the process terminates.
     * @return the result of the successful execution
     *
     * @throws StartupException if the process can't be started
     * @throws TimeoutException if the timeout kicked in
     * @throws ExternalProcessFailureException if the external process returned a non-null exit value*/
    public ProcResult run() throws StartupException, TimeoutException, ExternalProcessFailureException {

        if (stdout != defaultStdout && outputConsumer != null) {
            throw new IllegalArgumentException("`withOutputStream(OutputStream)` and `withOutputConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }

        if (stderr != null && errorConsumer != null) {
            throw new IllegalArgumentException("`withErrorStream(OutputStream)` and `withErrorConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }

        try {
            Proc proc = new Proc(command, args, env, clearEnvironment, stdin, outputConsumer != null ? outputConsumer : stdout , directory, timoutMillis, errorConsumer != null ? errorConsumer : stderr);

            final ByteArrayOutputStream output = defaultStdout == stdout && outputConsumer == null ? defaultStdout : null;

            if (expectedExitStatuses.size() > 0 && !expectedExitStatuses.contains(proc.getExitValue())) {
                throw new ExternalProcessFailureException(command, proc.toString(), proc.getExitValue(), proc.getErrorString(), output, proc.getExecutionTime());
            }

            return new ProcResult(proc.toString(), output, proc.getExitValue(), proc.getExecutionTime(), proc.getErrorBytes());
        } finally {
            stdout = defaultStdout = new ByteArrayOutputStream();
            stdin = null;
        }
    }

    /** Static helper to run a process
     * @param cmd the command
     * @param args the arguments
     * @return the standard output
     * @throws StartupException if the process can't be started
     * @throws TimeoutException if the timeout kicked in
     * @throws ExternalProcessFailureException if the external process returned a non-null exit value
     *  */
    public static String run(String cmd, String... args) {
        ProcBuilder builder= new ProcBuilder(cmd)
                .withArgs(args);

        return builder.run().getOutputString();
    }

    /** Static helper to filter a string through a process
     * @param input the input to be fed into the process
     * @param cmd the command
     * @param args the arguments
     * @return the standard output
     * @throws StartupException if the process can't be started
     * @throws TimeoutException if the timeout kicked in
     * @throws ExternalProcessFailureException if the external process returned a non-null exit value
     *  */
    public static String filter(String input, String cmd, String... args) {
        ProcBuilder builder= new ProcBuilder(cmd)
                .withArgs(args)
                .withInput(input);


        return builder.run().getOutputString();
    }

    /** Clears the environment before setting new variables. */
     public ProcBuilder clearEnvironment() {
        this.clearEnvironment = true;
        return this;
    }

    /**
     * Add a variable to the process's environment
     *
     * @param var variable name
     * @param value the value to be passed in
     * @return this, for chaining
     * */
    public ProcBuilder withVar(String var, String value) {
        env.put(var, value);
        return this;
    }

    /**
     * Add multiple variables to the process's environment
     *
     * @param vars Map of variables to their respective values
     * @return this, for chaining
     */
    public ProcBuilder withVars(Map vars) {
        env.putAll(vars);
        return this;
    }

    /**
     * Process the standard output with the given consumer object
     *
     * @param outputConsumer an object that defines how to process the standard output stream
     * @return this, for chaining
     */
    public ProcBuilder withOutputConsumer(StreamConsumer outputConsumer) {
        if (stdout != defaultStdout) {
            throw new IllegalArgumentException("`withOutputStream(OutputStream)` and `withOutputConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }

        this.outputConsumer = outputConsumer;

        return this;
    }

    /**
     * Process the error output with given consumer object
     * @param errorConsumer an object that defines how to process the error output stream
     * @return this, for chaining
     */
    public ProcBuilder withErrorConsumer(StreamConsumer errorConsumer) {
        if (stderr != null) {
            throw new IllegalArgumentException("`withErrorStream(OutputStream)` and `withErrorConsumer(OutputConsumer)` " +
                "are mutually exclusive.");
        }
        this.errorConsumer = errorConsumer;
        return this;
    }

    /** @return  a string representation of the process invocation.
     *
     *           This approximates the representation of this invocation
     *           in a shell. Note that the escaping of arguments is incomplete,
     *           it works only for whitespace. Fancy control characters are
     *           not replaced.
     *
     *           Also, this returns a representation of the current state of
     *           the builder. If more arguments are added the process this
     *           representation will not represent the process that gets launched.
     *
     * @deprecated Use getCommandLine instead.
     */
    @Deprecated
    public String getProcString() {
        return Proc.formatCommandLine(command, args);
    }

    /** @return  a string representation of the process invocation.
     *
     *           This approximates the representation of this invocation
     *           in a shell. Note that the escaping of arguments is incomplete,
     *           it works only for whitespace. Fancy control characters are
     *           not replaced.
     *
     *           Also, this returns a representation of the current state of
     *           the builder. If more arguments are added the process this
     *           representation will not represent the process that gets launched.
     */
    public String getCommandLine() {
        return Proc.formatCommandLine(command, args);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy