/*******************************************************************************
 * 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{
		
			StreamHandler errorOutHandler = new StreamPrinter(System.err);
			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;		
	}
}
