/*
 * Copyright (C) 2006 Kustaa Nyholm. All rights reserved. Use is
 * subject to license terms.
 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */
package jApp.jdic;

import java.io.*;
import java.util.*;

/**
 * Implements a simple wrapper around the RunTime.exec() method that allows easy
 * capture of command results (that is  'stdout' and 'stderr' and feeding the command via
 * 'stdin'.
 
 @author Kustaa Nyholm
 
 */
public class Shell {
  private boolean m_TraceCommands = true;

  private String m_Command;

  private int m_EditCode;
  
  private Exception m_Exception;
  /**
   * Executes a given command line with root rights and default time out of five seconds.
   
   @see #execute(String, String,int)
   @param cmd the command line to execute
   @param input the input to feed to the command via 'stdin', can be null
   @return the combined output of 'stdout' and 'stderr' from the command
   @throws Exception
   */
  public String sudoExecute(String cmd, final String input, String passwordthrows Exception {
    return sudoExecute(cmd,input,password,5000);
  }
  /**
   * Executes a given command line with root rights.
   
   @see #execute(String, String,int)
   @param cmd the command line to execute
   @param input the input to feed to the command via 'stdin', can be null
   @return the combined output of 'stdout' and 'stderr' from the command
   @param timeout in milliseconds after which the process is aborted, use zero for no timeout
   @throws Exception
   */
  public String sudoExecute(String cmd, final String input, String password, int timeoutthrows Exception {
    return execute("sudo -S " + cmd, password+input!=null?input:"",5000);
  }
  /**
   * Executes a given command line with default time out of five seconds.
   
   @see #execute(String, String,int)
   @param cmd the command line to execute
   @param input the input to feed to the command via 'stdin', can be null
   @return the combined output of 'stdout' and 'stderr' from the command
   */
  public String execute(String cmd, final String input) {
    return execute(cmd, input, 5000);
  }

  /**
   * Executes a given command line, aborting after given timeout.
   
   * After calling this method you should check for error by calling the getExitCode(). A non zero
   * positive value indicates an error, typically Unix/Linux processes can only use values in the
   * range 0..255 inclusive.
   * If an exception was thrown during the call execute method
   * the {@link #getExitCode()} returns -1 and you can get the exception with the {@link #getException()} call
   
   @see #getExitCode
   @see #getCommand
   @see #getException
   @param cmd the command line to execute
   @param input the input to feed to the command via 'stdin', can be null
   @param timeout in milliseconds after which the process is aborted, use zero for no timeout
   @return the combined output of 'stdout' and 'stderr' from the command
   */
  public String execute(String cmd, final String input, int timeout) {
    m_Exception=null;
    OutputStream output = new ByteArrayOutputStream();
    m_Command = cmd;
    m_EditCode = -1;
    try {
      if (m_TraceCommands)
        System.out.println("execute: " + cmd);
      final Process cmdproc = Runtime.getRuntime().exec(cmd);
      final PrintStream stdin = new PrintStream(cmdproc.getOutputStream());
      InputStream stdout = cmdproc.getInputStream();
      createForkedPipe(stdout, m_TraceCommands ? System.out : null, output);
      InputStream errout = cmdproc.getErrorStream();
      createForkedPipe(errout, m_TraceCommands ? System.err : null, output);
      if (input != null) {
        stdin.println(input);
        stdin.close();
      }

      java.util.Timer timer = new java.util.Timer();
      if (timeout > 0)
        timer.schedule(new TimerTask() {
          public void run() {
            cmdproc.destroy();
          }
        }, timeout);
      int res = cmdproc.waitFor();
      timer.cancel();
      m_EditCode = res;
    catch (IOException e) {
      m_Exception=e;
      System.err.println(e.getMessage());
    catch (Exception e) {
      m_Exception=e;
      e.printStackTrace();
    }
    if (m_TraceCommands)
      System.out.println("exit code: " + m_EditCode);
    return output.toString();
  }
  /**
   * Gets the exit code from the last command executed, should be checked after each 
   * invokation of {@code execute} to verify success. Typically 0 value indicates success,
   * no Unix/Linux script/program should not be able to return a code outside 0..255,
   * a return code of -1 indicates an exception in attempting to execute the command line
   * in other words an exception was thrown before or during execution of the command
   * and it was not possible to capture the actual exit code from the command.
   
   @return the exit code
   */

  public int getExitCode() {
    return m_EditCode;
  }
  /**
   * Gets the last command that has been executed (succesfully or not in other word the command line that 
   * returned the exit code you can query with getExitCode()
   @return the command line
   */
  public String getCommand() {
    return m_Command;
  }
  /**
   * Gets the exception that was thrown during, before or after the execution of the command line within
   * the execute method, if any. If no exception was thrown, returns null.
   @return the exception or null
   */
  public Exception getException() {
    return m_Exception;
  }
  private void createForkedPipe(final InputStream in, final OutputStream out1, final OutputStream out2) {
    Thread thread = new Thread() {
      @Override
      public void run() {
        byte b[] new byte[1];
        try {
          int n;
          while ((n = in.read(b)) 0) {
            if (out1 != null)
              out1.write(b);
            if (out2 != null)
              out2.write(b);
          }
        catch (IOException e) {
          e.printStackTrace();
          m_Exception=e;
        }
      }
    };
    thread.start();
  }


}