package org.dstadler.commons.exec;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.dstadler.commons.logging.jdk.LoggerFactory;

/**
 * Helper class which provides convenience support for execution of commandline processes via commons-exec.
 *
 * Use getCommandResult() for processes where the output is small as they will store the output in
 * memory. Use getCommandResultIntoStream() in order to properly stream out potentially large
 * amounts of output from the executable.
 */
public class ExecutionHelper {
	private final static Logger log = LoggerFactory.make();

	/**
	 * Run the given commandline in the given directory and verify that the tool
	 * has the expected exit code and does finish in the timeout.
	 *
	 * Note: The resulting output is stored in memory, running a command
	 * which prints out a huge amount of data to stdout or stderr will
	 * cause memory problems.
	 *
     * @param cmdLine The commandline object filled with the executable and command line arguments
     * @param dir The working directory for the command
     * @param expectedExit The expected exit value or -1 to not fail on any exit value
	 * @param timeout The timeout in milliseconds or ExecuteWatchdog.INFINITE_TIMEOUT
	 * @return An InputStream which provides the output of the command.
     *
     * @throws IOException Execution of sub-process failed or the
     *          sub-process returned a exit value indicating a failure
	 */
	public static InputStream getCommandResult(
			CommandLine cmdLine, File dir, int expectedExit,
			long timeout) throws IOException {
		return getCommandResult(cmdLine, dir, expectedExit, timeout, null);
	}

	/**
	 * Run the given commandline in the given directory and provide the given input to the command.
	 * Also verify that the tool has the expected exit code and does finish in the timeout.
	 *
	 * Note: The resulting output is stored in memory, running a command
	 * which prints out a huge amount of data to stdout or stderr will
	 * cause memory problems.
	 *
	 * @param cmdLine The commandline object filled with the executable and command line arguments
	 * @param dir The working directory for the command
	 * @param expectedExit The expected exit value or -1 to not fail on any exit value
	 * @param timeout The timeout in milliseconds or ExecuteWatchdog.INFINITE_TIMEOUT
	 * @param input Input for the command-execution
	 * @return An InputStream which provides the output of the command.
     *
     * @throws IOException Execution of sub-process failed or the
     *          sub-process returned a exit value indicating a failure
	 */
	public static InputStream getCommandResult(
			CommandLine cmdLine, File dir, int expectedExit,
			long timeout, InputStream input) throws IOException {
		DefaultExecutor executor = getDefaultExecutor(dir, expectedExit, timeout);

		try (ByteArrayOutputStream outStr = new ByteArrayOutputStream()) {
			executor.setStreamHandler(new PumpStreamHandler(outStr, outStr, input));
			try {
				execute(cmdLine, dir, executor, null);

				return new ByteArrayInputStream(outStr.toByteArray());
			} catch (IOException e) {
				log.warning("Had output before error: " + new String(outStr.toByteArray()));
				throw new IOException(e);
			}
		}
	}

	/**
	 * Run the given commandline in the given directory and verify that the tool
	 * has the expected exit code and does finish in the timeout.
	 *
     * @param cmdLine The commandline object filled with the executable and command line arguments
     * @param dir The working directory for the command
     * @param expectedExit The expected exit value or -1 to not fail on any exit value
	 * @param timeout The timeout in milliseconds or ExecuteWatchdog.INFINITE_TIMEOUT
	 * @param stream An OutputStream which receives the output of the executed command
     *
     * @throws IOException Execution of sub-process failed or the
     *          sub-process returned a exit value indicating a failure
	 */
	public static void getCommandResultIntoStream(
			CommandLine cmdLine, File dir, int expectedExit,
			long timeout, OutputStream stream) throws IOException {
		getCommandResultIntoStream(cmdLine, dir, expectedExit, timeout, stream, null);
	}

	/**
	 * Run the given commandline in the given directory and verify that the tool
	 * has the expected exit code and does finish in the timeout.
	 *
     * @param cmdLine The commandline object filled with the executable and command line arguments
     * @param dir The working directory for the command
     * @param expectedExit The expected exit value or -1 to not fail on any exit value
	 * @param timeout The timeout in milliseconds or ExecuteWatchdog.INFINITE_TIMEOUT
     * @param stream An OutputStream which receives the output of the executed command
	 * @param environment Environment variables that should be set for the execution of the command
	 *
	 * @throws IOException Execution of subprocess failed or the
     *          subprocess returned a exit value indicating a failure
	 */
	public static void getCommandResultIntoStream(
			CommandLine cmdLine, File dir, int expectedExit,
			long timeout, OutputStream stream, Map<String,String> environment) throws IOException {
		DefaultExecutor executor = getDefaultExecutor(dir, expectedExit, timeout);
		executor.setStreamHandler(new PumpStreamHandler(stream));
		execute(cmdLine, dir, executor, environment);
	}

	private static DefaultExecutor getDefaultExecutor(File dir, int expectedExit, long timeout) {
		DefaultExecutor executor = new DefaultExecutor();
		if(expectedExit != -1) {
			executor.setExitValue(expectedExit);
		} else {
			executor.setExitValues(null);
		}

		ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout);
		executor.setWatchdog(watchdog);
		executor.setWorkingDirectory(dir);
		return executor;
	}

	private static void execute(CommandLine cmdLine, File dir, DefaultExecutor executor,
								Map<String,String> environment) throws IOException {
		log.info("-Executing(" + dir + "): " + cmdLine);
		try {
			int exitValue = executor.execute(cmdLine, environment);
			if (exitValue != 0) {
				log.info("Had exit code " + exitValue + " when calling " + cmdLine);
			}
		} catch (ExecuteException e) {
			throw new ExecuteException("While executing (" + dir + "); " + cmdLine, e.getExitValue(), e);
		}
	}
}

@@ -455,16 +455,57 @@
 tchdog;%0A
+import org.apache.commons.exec.Executor;%0A
 import o
@@ -546,16 +546,16 @@
 andler;%0A
-
 import o
@@ -3211,39 +3211,32 @@
 IOException %7B%0A%09%09
-Default
 Executor executo
@@ -5639,31 +5639,24 @@
 ception %7B%0A%09%09
-Default
 Executor exe
@@ -5839,23 +5839,16 @@
  static 
-Default
 Executor
@@ -5913,23 +5913,16 @@
 ut) %7B%0A%09%09
-Default
 Executor
@@ -6221,24 +6221,24 @@
 ecutor;%0A%09%7D%0A%0A
+
 %09private sta
@@ -6285,23 +6285,16 @@
 le dir, 
-Default
 Executor

Use interface instead of implementation where possible