// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.util; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import jodd.io.StreamGobbler; import static jodd.util.RuntimeUtil.ERROR_PREFIX; import static jodd.util.RuntimeUtil.OUTPUT_PREFIX; /** * Simple user-friendly wrapper over {@code ProcessBuilder}. Has the following: * <ul> * <li>fluent interface</li> * <li>no exception is throw</li> * <li>output is collected to string</li> * <li>easy environment manipulation</li> * </ul> */ public class CommandLine { public static final int OK = 0; protected final List<String> cmdLine = new ArrayList<>(); protected Map<String, String> env = null; protected boolean cleanEnvironment = false; protected File workingDirectory; protected String outPrefix = OUTPUT_PREFIX; protected String errPrefix = ERROR_PREFIX; protected OutputStream out = System.out; protected OutputStream err = System.err; // ---------------------------------------------------------------- ctor protected CommandLine(String command) { cmdLine.add(command); } /** * Creates command line with given command. */ public static CommandLine cmd(String command) { return new CommandLine(command); } // ---------------------------------------------------------------- arguments /** * Defines working directory. */ public CommandLine workingDirectory(File workDirectory) { this.workingDirectory = workDirectory; return this; } /** * Defines working directory. */ public CommandLine workingDirectory(String workDirectory) { this.workingDirectory = new File(workDirectory); return this; } /** * Adds single argument. */ public CommandLine arg(String argument) { cmdLine.add(argument); return this; } /** * Adds several arguments. */ public CommandLine args(String... arguments) { if (arguments != null && arguments.length > 0) { Collections.addAll(cmdLine, arguments); } return this; } /** * Defines output prefix. */ public CommandLine outPrefix(String prefix) { this.outPrefix = prefix; return this; } /** * Defines error prefix. */ public CommandLine errPrefix(String prefix) { this.errPrefix = prefix; return this; } public CommandLine out(OutputStream out) { this.out = out; return this; } public CommandLine err(OutputStream err) { this.err = err; return this; } /** * Sets environment variable. */ public CommandLine env(String key, String value) { if (env == null) { env = new HashMap<>(); } env.put(key, value); return this; } /** * When set to {@code true}, environment will not be copied from the * parent process and will be completly empty. */ public CommandLine newEnv(boolean clean) { cleanEnvironment = clean; return this; } // ---------------------------------------------------------------- execute /** * Runs command and returns process result. */ public RuntimeUtil.ProcessResult run() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); out = err = baos; try { baos.write(StringUtil.join(cmdLine, ' ').getBytes()); baos.write(StringPool.BYTES_NEW_LINE); } catch (IOException ignore) { } ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command(cmdLine); if (cleanEnvironment) { processBuilder.environment().clear(); } if (env != null) { processBuilder.environment().putAll(env); } processBuilder.directory(workingDirectory); Process process = null; try { process = processBuilder.start(); } catch (IOException ioex) { return writeException(baos, ioex); } StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), out, outPrefix); StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), err, errPrefix); outputGobbler.start(); errorGobbler.start(); int result; try { result = process.waitFor(); } catch (InterruptedException iex) { return writeException(baos, iex); } outputGobbler.waitFor(); errorGobbler.waitFor(); return new RuntimeUtil.ProcessResult(result, baos.toString()); } private RuntimeUtil.ProcessResult writeException(ByteArrayOutputStream baos, Exception ex) { try { baos.write(errPrefix.getBytes()); } catch (IOException ignore) { } ex.printStackTrace(new PrintStream(baos)); return new RuntimeUtil.ProcessResult(-1, baos.toString()); } }