/*
 * $Id: VKCProcess.java,v 1.8 2007/10/30 13:35:09 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 */
package vkc.misc;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.Reader;

import vkc.util.FileUtils;


/**
 * Klasse zum Ausführen von Kommandos als eingenstaendige Prozesse
 * 
 * <p></p>
 *
 * @author bigmac
 */
public class VKCProcess extends Object {
    private static boolean debug = false;
    private Reader reader = null;
    private Reader errReader = null;
    private Process process = null;
    private String[] commandArray = null;
    private String[] environment = null;
    private File workingDir = null;
    public String error = "";
    public StringBuffer stdout = null;
    public StringBuffer stderr = null;

    /**
     * Konstruktor VKCProcess
     * 
     * <p></p>
     */
    private VKCProcess() {
    }

    /**
     * Konstruktor VKCProcess
     * 
     * <p></p>
     *
     * @param carr - String-Array, welches das Kommando (Pfad) und evtl.
     *        weitere Aufrugargumente enthält.
     * @param env - optionales String-Array mit Umgebungsvariablen
     * @param dir - optinales Fileobjekt, welches den Pfad angibt, in dem das
     *        Kommando ausgeführt werden soll.
     */
    public VKCProcess(String[] carr, String[] env, File dir)
        throws IllegalArgumentException {
        this();
        if (carr == null) {
            throw new IllegalArgumentException("empty command array");
        }

        if (dir != null) {
            if (!dir.exists()) {
                throw new IllegalArgumentException(
                    "working directory does not exist: " + dir);
            }

            if (!dir.isDirectory()) {
                throw new IllegalArgumentException("\"" + dir +
                    "\" is not a directory.");
            }
        }

        commandArray = carr;
        environment = env;
        workingDir = dir;

        if (debug) {
            for (int i = 0; i < commandArray.length; i++) {
                System.out.println("CMDARR[" + i + "] = " + commandArray[i]);
            }

            if (environment != null) {
                for (int i = 0; i < environment.length; i++) {
                    System.out.println("ENV[" + i + "] = " + environment[i]);
                }
            }

            if (workingDir != null) {
                System.out.println("WORKINGDIR: " + workingDir);
            }
        }
    }

    /**
     * Killt den aktuellen Prozess, aber laesst die Outputstreams offen um noch
     * Ausgaben lesen zu können.
     * 
     * <p></p>
     */
    public void destroy() {
        if (debug) {
            System.out.println("process.destroy()");
        }

        try {
            if (process != null) {
                process.destroy();
            }

            process = null;
        } catch (Throwable e) {
            error += e.getMessage();
            VKCLogManager.errorLog(this, "", e.getMessage(),e);
        }
    }

    /**
     * Führt das im Konstruktor angegebene Kommando als separaten Prozess aus
     * und holt die Outputstreams des Prozesses. Die Methode kehrt sofort
     * zurück. Ausgaben des Prozesses koennen mit getStandardOutput() und
     * getStandardError() nebenläufig ausgelesen werden.
     * 
     * <p></p>
     *
     * @return Exit-Wert des Prozesses
     */
    public int execute() {
        error = "";

        stdout = new StringBuffer();
        stderr = new StringBuffer();

        try {
            process = Runtime.getRuntime().exec(commandArray, environment,
                    workingDir);
            reader = new InputStreamReader(process.getInputStream());
            errReader = new InputStreamReader(process.getErrorStream());

            return (0);
        } catch (Throwable e) {
            VKCLogManager.errorLog(this, "", e.getMessage(),e);
            error += e.getMessage();

            return (-1);
        }
    }

    /**
     * Führt das angegebene Kommando als separaten Prozess aus und holt die
     * Outputstreams des Prozesses. Die Methode kehrt erst zurück wenn der
     * Prozess beendet ist. Ausgaben des Prozesses werden dabei in den
     * StringBuffern "stderr" und "stdout" gesammelt.
     * 
     * <p></p>
     *
     * @return Exit-Wert des Prozesses
     */
    public int executeAndWait() {
        error = "";
        stdout = new StringBuffer();
        stderr = new StringBuffer();

        try {
            process = Runtime.getRuntime().exec(commandArray, environment,
                    workingDir);
            reader = new InputStreamReader(process.getInputStream());
            errReader = new InputStreamReader(process.getErrorStream());
        } catch (Throwable e) {
            VKCLogManager.errorLog(this, "", e.getMessage(),e);
            error += e.getMessage();

            return (-1);
        }

        while (true) {
            try {
                String message = getStandardOutput();

                if ((message != null) && (message.length() > 0)) {
                    stdout.append(message);
                }

                message = getErrorOutput();

                if ((message != null) && (message.length() > 0)) {
                    stderr.append(message);
                }

                int exitValue = process.exitValue();

                return (exitValue);
            } catch (IllegalThreadStateException e) {
                // System.out.println( "VKCProcess not finished yet");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException ex) {
                    if (debug) {
                        VKCLogManager.errorLog(this, "", ex.getMessage(),ex);
                    }
                }

                continue;
            }
        }
    }

    /**
     * Führt das im Konstruktor angegebene Kommando als separaten Prozess aus
     * und holt die Outputstreams des Prozesses. Die Methode kehrt zurück wenn
     * der Prozess beendet ist oder der Timeout abgelaufen ist. Ausgaben des
     * Prozesses werden dabei in den StringBuffern "stderr" und "stdout"
     * gesammelt. Bei erreichen des Timeouts wird der Prozess abgeschossen.
     * 
     * <p></p>
     *
     * @param seconds Timeout in Sekunden
     *
     * @return Exit-Wert des Prozesses
     */
    public int executeAndWaitWithTimeout(int seconds) {
        error = "";
        stdout = new StringBuffer();
        stderr = new StringBuffer();

        if (seconds <= 0) {
            error += "illegal timeout value";

            return (-1);
        }

        try {
            process = Runtime.getRuntime().exec(commandArray, environment,
                    workingDir);
            reader = new InputStreamReader(process.getInputStream());
            errReader = new InputStreamReader(process.getErrorStream());
        } catch (Throwable e) {
            VKCLogManager.errorLog(this, "", e.getMessage(),e);
            error += e.getMessage();

            return (-1);
        }

        long startTime = System.currentTimeMillis();
        long stopTime = startTime + (seconds * 1000);

        while (true) {
            long currentTime = System.currentTimeMillis();

            if (currentTime <= stopTime) // Noch kein Timeout
             {
                try {
                    String message = getStandardOutput();

                    if ((message != null) && (message.length() > 0)) {
                        stdout.append(message);

                        if (debug) {
                            System.out.println(message);
                        }
                    }

                    message = getErrorOutput();

                    if ((message != null) && (message.length() > 0)) {
                        stderr.append(message);

                        if (debug) {
                            System.out.println(message);
                        }
                    }

                    int exitCode = process.exitValue();

                    if (debug) {
                        System.out.println(
                            "\nVKCProcess finished with exit code: " +
                            exitCode + "\n");
                    }

                    return (exitCode);
                } catch (IllegalThreadStateException ite) // noch nicht fertig
                 {
                    if (debug) {
                        System.out.println("VKCProcess still waiting ...");
                    }

                    try {
                        Thread.sleep(5000);
                    } // 5 sec warten
                    catch (InterruptedException e) {
                    }

                    continue;
                }
            } 

            if (debug) {
                System.out.println("VKCProcess has reached its timeout ...");
            }

            destroy();
            error += "process timed out";

            return (-1);
        }
    }

    /**
     * Schliesst evtl. noch offene Outputstreams des Prozesses
     * 
     * <p></p>
     */
    public void close() {
        if (debug) {
            System.out.println("process.close()");
        }

        FileUtils.closeReader(reader);
        FileUtils.closeReader(errReader);
    }

    /**
     * Liesst die Standard-Ausgaben des Prozesses ein
     * 
     * <p></p>
     */
    public String getStandardOutput() {
        return getOutput(reader);
    }

    /**
     * Liesst die Standard-Fehlerausgaben des Prozesses ein
     * 
     * <p></p>
     */
    public String getErrorOutput() {
        return getOutput(errReader);
    }

    /**
     * Liesst solange Daten aus einem Reader bis nix mehr kommt und gibt sie
     * als String zurück.
     * 
     * <p></p>
     */
    protected String getOutput(Reader reader) {
        if (reader == null) {
            System.err.println("NULL");

            return (null);
        }

        try {
            StringBuffer outputBuffer = new StringBuffer();

            BufferedReader buf = new BufferedReader(reader);
            String line = "";

            //System.err.println("Marke");
            while ((line = buf.readLine()) != null) {
                outputBuffer = outputBuffer.append(line).append("\n");
            }

            /*char cBuf[] = new char[1024];
            int size = 0;

            while (reader.ready())
            {
            size = reader.read(cBuf);
            if (size == -1)
            break;
            outputBuffer.append(cBuf, 0, size);
            }*/
            return outputBuffer.toString();
        } catch (Throwable e) {
            if (debug) {
                VKCLogManager.errorLog(this, "", e.getMessage(),e);
            }

            return (null);
        }
    }
}
