/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/util/org/apache/slide/util/os/Executable.java,v 1.1 2004/03/25 16:18:12 juergen Exp $
 * $Revision: 1.1 $
 * $Date: 2004/03/25 16:18:12 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * 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 org.apache.slide.util.os;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.slide.util.Properties;
import org.apache.slide.util.Strings;

/**
 ** <p>Executes a command in a sub-process. Provides a fork switch to either execute the command
 ** in a separate switch or not. Logging allows to track command output in a file. Does *not*
 ** provide functionality to set environment variables because passing an array with environment
 ** settings overrides the default environment variables inherited from the parent process. This
 ** would cause problems because settings like JAVA_HOME vanish.
 ** Child processes will always be destroyed. </p>
 **
 ** @version   $Revision: 1.1 $
 **/
public class Executable {
    private static final int BUFFER_SIZE = 256;
    
    private final String executable;
    
    private final String[] args;
    
    /** null to inherit environment **/
    private final Properties env;
    
    public Executable(String executable, String[] args, Properties env) {
        this.executable = executable;
        this.args = args;
        this.env = env;
    }
    
    public String run(File dir) throws IOException {
        ByteArrayOutputStream buffer;
        int result;
        
        buffer = new ByteArrayOutputStream();
        result = run(dir, buffer, true);
        if (result != 0) {
            throw new IOException("command '" + toString() + "' failed: " + result);
        }
        return new String(buffer.toByteArray());
    }
    
    public int run(File dir, StringBuffer output, boolean wait) throws IOException {
        ByteArrayOutputStream tmp;
        int result;
        
        tmp = new ByteArrayOutputStream();
        result = run(dir, tmp, wait);
        output.append(new String(tmp.toByteArray()));
        return result;
    }

    public int run(File dir, OutputStream output, boolean wait) throws IOException {
        CopyThread out;
        CopyThread err;
        Process process;
        String[] line;
        int result;
        
        log("starting");
        if (!dir.isDirectory()) {
            // check this, because failure results in a strange dead-lock with jdk 1.4.1 on linux
            throw new IllegalArgumentException("not a directory: " + dir);
        }
        line = getLine();
        log("start in dir " + dir + ": " + this);
        process = Runtime.getRuntime().exec(line, inherit(env));
        if (wait) {
            Killer.get().add(process);
            out = new CopyThread(process.getInputStream(), output, BUFFER_SIZE);
            err = new CopyThread(process.getErrorStream(), output, BUFFER_SIZE);
            out.start();
            err.start();
            try {
                result = process.waitFor();
            } catch (InterruptedException e) {
                throw new IllegalStateException("TODO: kill process?");
            }
            try {
                log("joining");
                out.join();
                err.join();
            } catch (InterruptedException e) {
                throw new IllegalStateException("nobody must interrupt my copy thread");
            }
            out.exception();
            err.exception();
            Killer.get().remove(process);
        } else {
            result = 0;
        }
        
        log("exit " + toString());
        return result;
    }
    
    private static String[] inherit(Properties additional) throws IOException {
        if (additional == null) {
            return null;
        } else {
            Properties env;
            
            env = new Properties();
            env.addAll(additional);
            env.addAll(Platform.CURRENT.getEnvironment());
            
            return env.toProcessArgument();
        }
    }
    
    public String[] getLine() {
        return Strings.cons(executable, args);
    }
    
    public String toString() {
        return Strings.join(getLine(), " ");
    }
    
    private void log(String str) {
        //System.err.println(str);
        //System.err.flush();
    }
}

