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.android.apps.common.testing.broker.shell.SubprocessBuilder.StreamAction;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.lang.ProcessBuilder.Redirect;
  
  /**
   * A subprocess factory that uses {@link java.lang.ProcessBuilder}.
   */
  public class JavaSubprocessFactory implements Subprocess.Factory {
  
    /**
     * A subprocess backed by a {@link java.lang.Process}.
     */
    private static class JavaSubprocess implements Subprocess {
      private final Process process;
  
      private JavaSubprocess(Process process) {
        this.process = process;
      }
  
      @Override
      public boolean destroy() {
        process.destroy();
        return true;
      }
  
      @Override
      public int exitValue() {
        return process.exitValue();
      }
  
      @Override
      public boolean finished() {
        try {
          // this seems to be the only non-blocking call for checking liveness
          process.exitValue();
          return true;
        } catch (IllegalThreadStateException e) {
          return false;
        }
      }
  
      @Override
      public boolean timedout() {
        // Not supported.
        return false;
      }
  
      @Override
      public void waitFor() throws InterruptedException {
        process.waitFor();
      }
  
      @Override
      public OutputStream getOutputStream() {
        return process.getOutputStream();
      }
  
      @Override
      public InputStream getErrorStream() {
        return process.getErrorStream();
      }
  
      @Override
      public InputStream getInputStream() {
        return process.getInputStream();
      }
  
      @Override
      public void close() {
        // java.lang.Process doesn't give us a way to clean things up other than #destroy()
      }
    }
  
    public static final JavaSubprocessFactory INSTANCE = new JavaSubprocessFactory();
  
    private JavaSubprocessFactory() {
      // We are a singleton
    }
  
    @Override
    public Subprocess create(SubprocessBuilder params) throws IOException {
      if (params.getTimeoutMillis() >= 0) {
        throw new UnsupportedOperationException("Timeouts are not supported");
      }
      ProcessBuilder builder = new ProcessBuilder();
      builder.command(params.getArgv());
      if (params.getEnv() != null) {
        builder.environment().clear();
        builder.environment().putAll(params.getEnv());
      }
  
      builder.redirectOutput(getRedirect(params.getStdout(), params.getStdoutFile()));
      builder.redirectError(getRedirect(params.getStderr(), params.getStderrFile()));
      builder.directory(params.getWorkingDirectory());
  
      return new JavaSubprocess(builder.start());
    }
  
    /**
     * Returns a {@link ProcessBuilder.Redirect} appropriate for the parameters. If a file redirected
     * to exists, deletes the file before redirecting to it.
     */
    private Redirect getRedirect(StreamAction action, File file) {
      switch (action) {
        case DISCARD:
          return Redirect.to(new File("/dev/null"));
  
        case REDIRECT:
          // We need to use Redirect.appendTo() here, because on older Linux kernels writes are
          // otherwise not atomic and might result in lost log messages:
          // https://lkml.org/lkml/2014/3/3/308
          if (file.exists()) {
            file.delete();
          }
          return Redirect.appendTo(file);
  
        case STREAM:
          return Redirect.PIPE;
  
        default:
          throw new IllegalStateException();
      }
    }
  }
  