/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.exec;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Utility class for executing processes and handling the output from them.
 *
 * @author Robert "Unlogic" Olofsson (unlogic@unlogic.se)
 *
 */
public class ExecUtils {

   /**
    * Executes the given command and waits for the resulting processes to terminate and all data written to standard out and error out to be handled.<p>
    *
    * All data written to standard out is piped to System.out with the given prefix<p>
    *
    * All data written to error out is piped to System.err with the given prefix<p>
    *
    * @param command the command to executed
    * @param timeout an optional timeout that controls how long the process i allowed to run before it is killed
    * @return the exit value of the process. By convention, 0 indicates normal termination.
    * @throws IOException
    * @throws InterruptedException
    */
   public static int execAndWait(String command, Long timeout) throws IOException, InterruptedException {

      Runtime rt = Runtime.getRuntime();
      Process proc = rt.exec(command);

      try{

         //CHECKSTYLE.OFF: RegexpSinglelineJava
         StreamHandler errorOutHandler = new StreamPrinter(System.err);
         //CHECKSTYLE.ON: RegexpSinglelineJava
         StreamHandler stdOutHandler = new StreamPrinter();

         errorOutHandler.handleStream(proc.getErrorStream());
         stdOutHandler.handleStream(proc.getInputStream());

         return waitForProcessAndStreams(proc,stdOutHandler,errorOutHandler, timeout);

      }finally{

         if(proc != null){
            proc.destroy();
         }
      }
   }

   /**
    * Executes the given command and waits for the resulting processes to terminate and all data written to standard out and error out to be handled.<p>
    *
    * @param command the command to executed
    * @param stdOutHandler the {@link StreamHandler} to handle all output the process writes on standard out
    * @param errorOutHandler the {@link StreamHandler} to handle all output the process writes on error out
    * @param timeout an optional timeout that controls how long the process i allowed to run before it is killed
    * @return the exit value of the process. By convention, 0 indicates normal termination.
    * @throws IOException
    * @throws InterruptedException
    */
   public static int execAndWait(String command, StreamHandler stdOutHandler, StreamHandler errorOutHandler, Long timeout) throws IOException, InterruptedException {

      Runtime rt = Runtime.getRuntime();
      Process proc = rt.exec(command);

      try{
         errorOutHandler.handleStream(proc.getErrorStream());
         stdOutHandler.handleStream(proc.getInputStream());

         return waitForProcessAndStreams(proc,stdOutHandler,errorOutHandler,timeout);

      }finally{

         if(proc != null){
            proc.destroy();
         }
      }
   }

   /**
    * Executes the given command and waits for the resulting processes to terminate and all data written to standard out and error out to be handled.<p>
    *
    * All data written to standard out and error out is piped to the given {@link OutputStream}<p>
    *
    * @param command the command to executed
    * @param outputStream the {@link OutputStream} to handle all output of the process
    * @param timeout an optional timeout that controls how long the process i allowed to run before it is killed
    * @return the exit value of the process. By convention, 0 indicates normal termination.
    * @throws IOException
    * @throws InterruptedException
    */
   public static int execAndWait(String command, OutputStream outputStream, Long timeout) throws IOException, InterruptedException {

      Runtime rt = Runtime.getRuntime();
      Process proc = rt.exec(command);

      try{

         StreamHandler errorOutHandler = new StreamPrinter(outputStream);
         StreamHandler stdOutHandler = new StreamPrinter(outputStream);

         errorOutHandler.handleStream(proc.getErrorStream());
         stdOutHandler.handleStream(proc.getInputStream());

         return waitForProcessAndStreams(proc,stdOutHandler,errorOutHandler, timeout);

      }finally{

         if(proc != null){
            proc.destroy();
         }
      }
   }

   /**
    * Executes the given command and waits for the resulting processes to terminate and all data written to standard out and error out to be handled.<p>
    *
    * All data written to standard out and error out is piped to the given {@link OutputStream}<p>
    *
    * @param builder the ProcessBuilder to get to start the Process through
    * @param outputStream the {@link OutputStream} to handle all output of the process
    * @param timeout an optional timeout that controls how long the process i allowed to run before it is killed
    * @return the exit value of the process. By convention, 0 indicates normal termination.
    * @throws IOException
    * @throws InterruptedException
    */
   public static int execAndWait(ProcessBuilder builder, OutputStream outputStream, Long timeout) throws IOException, InterruptedException {

      Process proc = builder.start();

      try{

         StreamHandler errorOutHandler = new StreamPrinter(outputStream);
         StreamHandler stdOutHandler = new StreamPrinter(outputStream);

         errorOutHandler.handleStream(proc.getErrorStream());
         stdOutHandler.handleStream(proc.getInputStream());

         return waitForProcessAndStreams(proc,stdOutHandler,errorOutHandler, timeout);

      }finally{

         if(proc != null){
            proc.destroy();
         }
      }
   }

   /**
    * Waits for the given processes to terminate and all data written to standard out and error out to be handled.<p>
    *
    * All data written to standard out and error out is piped to the given {@link OutputStream}<p>
    *
    * @param command the command to executed
    * @param outputStream the {@link OutputStream} to handle all output of the process
    * @return the exit value of the process. By convention, 0 indicates normal termination.
    * @throws IOException
    * @throws InterruptedException
    */

   public static int waitForProcessAndStreams(Process proc, StreamHandler stdOutHandler, StreamHandler errorOutHandler, Long timeout) throws InterruptedException {

      int exitVal;

      if(timeout != null){

         long elapsedTimeout = 0;

         while(true){

            if(elapsedTimeout > timeout){
               proc.destroy();
            }

            Thread.sleep(100);

            try{
               exitVal = proc.exitValue();
               break;

            }catch(IllegalThreadStateException e){

               elapsedTimeout += 100;
            }
         }

         exitVal = proc.exitValue();

         stdOutHandler.terminate();
         errorOutHandler.terminate();

      }else{

         exitVal = proc.waitFor();
      }

      if(!stdOutHandler.isFinished()){
         stdOutHandler.awaitFinish();
      }

      if(errorOutHandler != null){

         if(!errorOutHandler.isFinished()){
            errorOutHandler.awaitFinish();
         }
      }

      return exitVal;
   }
}
