Execution.java [src/java/m/rse/cfactor/utils] Revision:   Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package m.rse.cfactor.utils;

/*
 * $Id$
 * 
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * 2010-2013, Olaf David and others, Colorado State University.
 *
 * CSIP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 2.1.
 *
 * CSIP 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with OMS.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
 */
import csip.SessionLogger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Executable. Helper class to execute external programs.
 *
 * @author od
 */
class Execution {

    private final ProcessBuilder pb = new ProcessBuilder();
    private final File executable;
    private Object[] args = new Object[]{};
    private SessionLogger logger;
    //
    Writer stderr = new OutputStreamWriter(System.err) {
        @Override
        public void close() throws IOException {
            System.err.flush();
        }
    };
    //
    Writer stdout = new OutputStreamWriter(System.out) {
        @Override
        public void close() throws IOException {
            System.out.flush();
        }
    };


    /**
     * Create a new ProcessExecution.
     *
     * @param executable the executable file.
     */
    Execution(File executable) {
        this.executable = executable;
    }


    File getFile() {
        return executable;
    }


    /**
     * Set the execution arguments.
     *
     * @param args the command line arguments
     */
    void setArguments(Object... args) {
        this.args = args;
    }


    /**
     * Set the execution arguments.
     *
     * @param args the command line arguments
     */
    void setArguments(String... args) {
        this.args = args;
    }


    /**
     * Set the execution arguments.
     *
     * @param args the program arguments.
     */
    void setArguments(List<Object> args) {
        this.args = args.toArray(new Object[args.size()]);
    }


    /**
     * Get the arguments
     *
     * @return
     */
    Object[] getArguments() {
        Object[] ar = new Object[args.length];
        System.arraycopy(args, 0, ar, 0, ar.length);
        return ar;
    }


    /**
     * Set the working directory where the process get executed.
     *
     * @param dir the directory in which the executable will be started
     */
    void setWorkingDirectory(File dir) {
        if (!dir.exists() || dir.isFile()) {
            throw new IllegalArgumentException("directory " + dir + " doesn't exist.");
        }
        pb.directory(dir);
    }


    /**
     * get the execution environment. Use the returned map to customize the
     * environment variables.
     *
     * @return the process environment.
     */
    Map<String, String> environment() {
        return pb.environment();
    }


    /**
     * Set the logger. This is optional.
     *
     * @param log the logger
     */
    void setLogger(SessionLogger log) {
        this.logger = log;
    }

    /**
     * Output handler for stdout/stderr
     */
    private static class OutputHandler extends Thread {

        Writer w;
        Reader r;
        CountDownLatch l;


        OutputHandler(CountDownLatch l, InputStream is, Writer w) {
            r = new InputStreamReader(is);
            this.w = w;
            this.l = l;
            setPriority(Thread.MIN_PRIORITY);
        }


        @Override
        public void run() {
            try {
                char[] b = new char[2048];
                int n = r.read(b);
                while (n != -1) {
                    w.write(b, 0, n);
                    w.flush();
                    n = r.read(b);
                }
            } catch (IOException ex) {
                if (ex.getMessage().equals("Stream closed.")) {
                    return;
                }
                ex.printStackTrace(System.err);
            } finally {
                try {
                    w.flush();
                    w.close();
                    r.close();
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
                l.countDown();
            }
        }
    }


    /**
     * Process execution. This call blocks until the process is done.
     *
     * @return the exit status of the process. 0 = all good
     * @throws java.io.IOException
     */
    int exec() throws IOException {
        List<String> cmd = new ArrayList<>();
        cmd.add(executable.toString());
        for (Object arg : args) {
            if (arg != null) {
                if (arg.getClass() == String[].class) {
                    for (String s : (String[]) arg) {
                        if (s != null && !s.isEmpty()) {
                            cmd.add(s);
                        }
                    }
                } else {
                    cmd.add(arg.toString());
                }
            }
        }
        pb.command(cmd);

        if (logger != null && logger.isLoggable(Level.INFO)) {
            logger.info(pb.command().toString());
        }

        CountDownLatch l = new CountDownLatch(2);

        Process p = pb.start();
        new OutputHandler(l, p.getInputStream(), stdout).start();
        new OutputHandler(l, p.getErrorStream(), stderr).start();

        int exitValue = 0;
        try {
            exitValue = p.waitFor();
            l.await();
        } catch (InterruptedException E) {
            // do nothing
        } finally {
            p.getOutputStream().close();
            p.destroy();
        }
        return exitValue;
    }


    /**
     * Redirect the output stream
     *
     * @param w the stream handler
     */
    void redirectOutput(Writer w) {
        if (w == null) {
            throw new NullPointerException("w");
        }
        stdout = w;
    }


    /**
     * Redirect the error stream
     *
     * @param w the new handler.
     */
    void redirectError(Writer w) {
        if (w == null) {
            throw new NullPointerException("w");
        }
        stderr = w;
    }


    public static void main(String[] args) throws IOException {
//        SessionLogger l = new SessionLogger(new File("/tmp"), "", "");
        System.out.println("exec");
//        Execution e = new Execution(new File("/od/projects/csip-all/csip-example/src/C/mod.exe"));
        Execution e = new Execution(new File("mod.exe"));
        e.setArguments("1", "2", "3");

//      Execution e = new Execution(new File("/bin/bash"));
//      e.setArguments("-c","ls -l");
        e.setWorkingDirectory(new File("/home/od"));
//        e.setLogger(l);
        System.out.println(e.environment().get("PATH"));
        String p = e.environment().get("PATH");
        p = p + File.pathSeparator + "/od/projects/csip-all/csip-example/src/C";
        e.environment().put("PATH", p);
        System.out.println(e.environment().get("PATH"));

        int r = e.exec();
        System.out.println("return " + r);
    }

}