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

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

package com.github.dakusui.cmd;

import com.github.dakusui.cmd.exceptions.CommandException;
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;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.concurrent.*;

public class Command {
  enum State {
    NOT_STARTED,
    STARTED,
    FINISHED
  }

  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;
  }

  private void startConsumingOutput() {
    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();
  }

  /**
   * 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.
   */
  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);
      this.startConsumingOutput();
    } 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) {
      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);
      } finally {
        executor.shutdownNow();
      }
    }
    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