content: // Copyright 2016 The Bazel Authors. All rights reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //    http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  package com.google.android.apps.common.testing.broker.shell;
  
  import com.google.common.base.Preconditions;
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  import java.io.File;
  import java.io.IOException;
  import java.util.Map;
  
  /**
   * A builder class that starts a subprocess.
   */
  public class SubprocessBuilder {
    /**
     * What to do with an output stream of the process.
     */
    public enum StreamAction {
      /** Redirect to a file */
      REDIRECT,
  
      /** Discard. */
      DISCARD,
  
      /** Stream back to the parent process using an output stream. */
      STREAM };
  
    private ImmutableList<String> argv;
    private ImmutableMap<String, String> env;
    private StreamAction stdoutAction;
    private File stdoutFile;
    private StreamAction stderrAction;
    private File stderrFile;
    private File workingDirectory;
    private long timeoutMillis = -1;
  
    private static Subprocess.Factory factory = JavaSubprocessFactory.INSTANCE;
  
    public static void setSubprocessFactory(Subprocess.Factory factory) {
      SubprocessBuilder.factory = factory;
    }
  
    public SubprocessBuilder() {
      stdoutAction = StreamAction.STREAM;
      stderrAction = StreamAction.STREAM;
    }
  
    /**
     * Returns the complete argv, including argv0.
     *
     * <p>argv[0] is either absolute (e.g. "/foo/bar" or "c:/foo/bar.exe"), or is a single file name
     * (no directory component, e.g. "true" or "cmd.exe"). It might be non-normalized though (e.g.
     * "/foo/../bar/./baz").
     */
    public ImmutableList<String> getArgv() {
      return argv;
    }
  
    /**
     * Sets the argv, including argv[0], that is, the binary to execute.
     *
     * <p>argv[0] must be either absolute (e.g. "/foo/bar" or "c:/foo/bar.exe"), or a single file name
     * (no directory component, e.g. "true" or "cmd.exe") which should be on the OS-specific search
     * path (PATH on Unixes, Windows-specific lookup paths on Windows).
     *
     * @throws IllegalArgumentException if argv is empty, or its first element (which becomes
     *     this.argv[0]) is neither an absolute path nor just a single file name
     */
    public SubprocessBuilder setArgv(Iterable<String> argv) {
      this.argv = ImmutableList.copyOf(argv);
      Preconditions.checkArgument(!this.argv.isEmpty());
      File argv0 = new File(this.argv.get(0));
      Preconditions.checkArgument(
          argv0.isAbsolute() || argv0.getParent() == null,
          "argv[0] = '%s'; it should be either absolute or just a single file name"
              + " (no directory component)",
          this.argv.get(0));
      return this;
    }
  
    public ImmutableMap<String, String> getEnv() {
      return env;
    }
  
    /**
     * Sets the environment passed to the child process. If null, inherit the environment of the
     * server.
     */
    public SubprocessBuilder setEnv(Map<String, String> env) {
      this.env = env == null
          ? null : ImmutableMap.copyOf(env);
      return this;
    }
  
    public StreamAction getStdout() {
      return stdoutAction;
    }
  
    public File getStdoutFile() {
      return stdoutFile;
    }
  
    /**
     * Tells the object what to do with stdout: either stream as a {@code InputStream} or discard.
     *
     * <p>It can also be redirected to a file using {@link #setStdout(File)}.
     */
    public SubprocessBuilder setStdout(StreamAction action) {
      if (action == StreamAction.REDIRECT) {
        throw new IllegalStateException();
      }
      this.stdoutAction = action;
      this.stdoutFile = null;
      return this;
    }
  
    /**
     * Sets the file stdout is appended to. If null, the stdout will be available as an input stream
     * on the resulting object representing the process.
     */
    public SubprocessBuilder setStdout(File file) {
      this.stdoutAction = StreamAction.REDIRECT;
      this.stdoutFile = file;
      return this;
    }
  
    public SubprocessBuilder setTimeoutMillis(long timeoutMillis) {
      this.timeoutMillis = timeoutMillis;
      return this;
    }
  
    public long getTimeoutMillis() {
      return timeoutMillis;
    }
  
    public StreamAction getStderr() {
      return stderrAction;
    }
  
    public File getStderrFile() {
      return stderrFile;
    }
  
    /**
     * Tells the object what to do with stderr: either stream as a {@code InputStream} or discard.
     *
     * <p>It can also be redirected to a file using {@link #setStderr(File)}.
     */
    public SubprocessBuilder setStderr(StreamAction action) {
      if (action == StreamAction.REDIRECT) {
        throw new IllegalStateException();
      }
      this.stderrAction = action;
      this.stderrFile = null;
      return this;
    }
  
    /**
     * Sets the file stderr is appended to. If null, the stderr will be available as an input stream
     * on the resulting object representing the process.
     */
    public SubprocessBuilder setStderr(File file) {
      this.stderrAction = StreamAction.REDIRECT;
      this.stderrFile = file;
      return this;
    }
  
    public File getWorkingDirectory() {
      return workingDirectory;
    }
  
    /**
     * Sets the current working directory. If null, it will be that of this process.
     */
    public SubprocessBuilder setWorkingDirectory(File workingDirectory) {
      this.workingDirectory = workingDirectory;
      return this;
    }
  
    public Subprocess start() throws IOException {
      return factory.create(this);
    }
  }
  