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

com.github.dakusui.cmd.Command Maven / Gradle / Ivy

There is a newer version: 0.0.8
Show newest version
package com.github.dakusui.cmd;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dakusui.cmd.exceptions.CommandException;
import com.github.dakusui.cmd.exceptions.CommandRuntimeException;
import com.github.dakusui.cmd.exceptions.CommandTimeoutException;
import com.github.dakusui.cmd.io.BasicLineReader;
import com.github.dakusui.cmd.io.LineConsumer;
import com.github.dakusui.cmd.io.LoggerLineWriter;
import com.github.dakusui.cmd.io.RingBufferedLineWriter;

public class Command {
	static enum State {
		NOT_STARTED,
		STARTED,
		FINISHED;
	}
	
	public static enum SourceType {
		STDOUT,
		STDERR
	}
	
	private static Logger LOGGER = LoggerFactory.getLogger(Command.class);
	
	private State state = State.NOT_STARTED;
	private Process proc;

	private LineConsumer stdout;

	private LineConsumer stderr;
	
	private int pid = -1;

	private String[] execShell;

	private String cmd;
	
	private RingBufferedLineWriter stdoutRingBuffer;
	private RingBufferedLineWriter stderrRingBuffer;
	private RingBufferedLineWriter stdouterrRingBuffer;

	Command(String[] execShell, String cmd) {
		this.proc = null;
		this.execShell = execShell;
		this.cmd = cmd;
	}
	
	public int pid() {
		if (state == State.NOT_STARTED) {
			throw new IllegalStateException(String.format("This command(%s) is not yet started.", this.cmd));
		}
		return this.pid;
	}

	/**
	 * Executes this command in a synchronous manner.
	 * if timeOut is set to a value less than or equal to zero, this method
	 * does never time out.
	 * Otherwise times out in timeOut milliseconds.
	 * 
	 * @param timeOut duration 
	 * @return
	 * @throws CommandException The command failed during execution. 
	 * @throws CommandRuntimeException The command couldn't be executed or this platform isn't suitable for this library.
	 */
	public CommandResult exec(int timeOut) throws CommandException {
		String[] execCmd = new String[this.execShell.length + 1];
		System.arraycopy(this.execShell, 0, execCmd, 0, this.execShell.length);
		execCmd[execCmd.length - 1] = this.cmd;

		try {
			this.proc = Runtime.getRuntime().exec(execCmd);
		} catch (IOException e) {
			throw new CommandException(e);
		}
		LOGGER.debug("EXEC={}", StringUtils.join(execCmd, " "));
		this.pid = getPID(this.proc);

		this.state = State.STARTED;
		LOGGER.debug("pid={}", this.pid);

		final Callable callable = new Callable() {
			public CommandResult call() throws CommandException {
				return Command.this.waitFor();
			}
		};
		CommandResult ret;
		if (timeOut <= 0) {
			this.stdoutRingBuffer = new RingBufferedLineWriter(100);
			this.stderrRingBuffer = new RingBufferedLineWriter(100);
			this.stdouterrRingBuffer = new RingBufferedLineWriter(100);
			this.stdout = new LineConsumer(new BasicLineReader(Charset.defaultCharset(), 0, proc.getInputStream()));
			this.stdout.addLineWriter(LoggerLineWriter.DEBUG);
			this.stdout.addLineWriter(stdoutRingBuffer);
			this.stdout.addLineWriter(stdouterrRingBuffer);
			this.stderr = new LineConsumer(new BasicLineReader(Charset.defaultCharset(), 0, proc.getErrorStream()));
			this.stderr.addLineWriter(LoggerLineWriter.DEBUG);
			this.stderr.addLineWriter(stderrRingBuffer);
			this.stderr.addLineWriter(stdouterrRingBuffer);
			
			this.stdout.start();
			this.stderr.start();
			ret = this.waitFor();
		} else {
			ExecutorService executor = Executors.newSingleThreadExecutor();
			Future future = executor.submit(callable);
			try {
				ret = future.get(timeOut, TimeUnit.MILLISECONDS);
			} catch (InterruptedException e) {
				throw new CommandException(e);
			} catch (ExecutionException e) {
				throw new CommandException(e);
			} catch (TimeoutException e) {
				throw new CommandTimeoutException(e);
			}
		}
		this.state = State.FINISHED;
		return ret;
	}
	
	private int getPID(Process proc) {
		int ret = -1;
		try {
			Field f = proc.getClass().getDeclaredField("pid");
			f.setAccessible(true);
			try {
				ret = Integer.parseInt(f.get( this.proc ).toString());
			} finally {
				f.setAccessible(false);
			}
		} catch (IllegalAccessException e) {
			assert false;
			String msg = "PID isn't available on this platform. (IllegalAccess) '-1' is used insted.";
			LOGGER.debug(msg);
		} catch (NoSuchFieldException e) {
			String msg = "PID isn't available on this platform. (NoSuchField) '-1' is used insted.";
			LOGGER.debug(msg);
		} catch (SecurityException e) {
			String msg = "PID isn't available on this platform. (Security) '-1' is used insted.";
			LOGGER.debug(msg);
		} catch (NumberFormatException e) {
			String msg = "PID isn't available on this platform. (NumberFormat) '-1' is used insted.";
			LOGGER.debug(msg);
		}
		return ret;
	}
	
	private CommandResult waitFor() throws CommandException {
		int exitCode;
		try {
			exitCode = Command.this.proc.waitFor();
		} catch (InterruptedException e) {
			throw new CommandException(e);
		}
		try {
			this.stderr.join();
		} catch (InterruptedException e) {
		}
		try {
			this.stdout.join();
		} catch (InterruptedException e) {
		}
		CommandResult ret = new CommandResult(
				this.cmd, 
				exitCode, 
				this.stdoutRingBuffer.asString(), 
				this.stderrRingBuffer.asString(), 
				this.stdouterrRingBuffer.asString()
		);
		return ret;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy