add ExecProcess from Electric
authorAdam Megacz <adam@megacz.com>
Mon, 11 Jan 2010 03:02:38 +0000 (19:02 -0800)
committerAdam Megacz <adam@megacz.com>
Mon, 11 Jan 2010 03:02:38 +0000 (19:02 -0800)
src/com/sun/electric/tool/io/ExecProcess.java [new file with mode: 0644]

diff --git a/src/com/sun/electric/tool/io/ExecProcess.java b/src/com/sun/electric/tool/io/ExecProcess.java
new file mode 100644 (file)
index 0000000..18cd885
--- /dev/null
@@ -0,0 +1,160 @@
+package com.sun.electric.tool.io;
+
+import java.net.*;
+import java.util.*;
+import java.io.*;
+
+/**
+ * This class provides the same functionality as Runtime.exec(), but
+ * with extra safeguards and utilities.  Includes the ability to
+ * execute a command on a remote machine via ssh, optionally rsync'ing
+ * the working directory to the remote machine before execution and
+ * back afterwards.
+ *
+ * This class should not depend on other Electric classes.
+ *
+ * @author megacz (heavily influenced by gainsley's ExecProcess)
+ */
+public class ExecProcess {
+
+    /** an OutputStream that discards anything written to it */
+    public static final OutputStream devNull = new OutputStream() {
+            public void write(int b) { }
+            public void write(byte[] b, int ofs, int len) { }
+        };
+
+    /** an InputStream that always returns EOF */
+    public static final InputStream eofInputStream = new InputStream() {
+            public int  read() { return -1; }
+            public int  read(byte[] buf, int ofs, int len) { return -1; }
+            public long skip(long ofs) { return 0; }
+            public int  available() { return 0; }
+        };
+
+    /**
+     *  @param command the command to run (separated into argv[])
+     *  @param workingDirectory the working directory on the LOCAL machine
+     */
+    public ExecProcess(String[] command, File workingDirectory) {
+        this.command = command;
+
+        // Using java.io.tmpdir as the default working directory leads
+        // to far more predictable behavior than simply using the
+        // JVM's working directory.  Electric already has a lot of
+        // bugs and quirks that result from doing that -- let's not
+        // add more!
+        if (workingDirectory==null)
+            workingDirectory = new File(System.getProperty("java.io.tmpdir"));
+
+        this.workingDirectory = workingDirectory;
+    }
+
+    /**
+     *  @param host the hostname to run on
+     *  @param user the username on the remote machine (or null to use
+     *         whatever default ssh chooses)
+     *  @param remoteWorkingDirectory the directory to work in on the remote machine
+     *  @param syncBefore if true then "rsync --delete
+     *         workingDirectory host:remoteWorkingDirectory" before
+     *         invoking command.
+     *  @param syncAfter if true and the command terminates with exit
+     *         code zero, then "rsync --delete
+     *         host:remoteWorkingDirectory workingDirectory" after
+     *         invoking command.
+     */
+    public synchronized void setRemote(String host, String user,
+                                       File remoteWorkingDirectory,
+                                       boolean syncBefore, boolean syncAfter) {
+        if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.setRemote() after ExecProcess.start()");
+        throw new RuntimeException("not implemented");
+    }
+
+    /** undoes setRemote() */
+    public synchronized void setLocal() { }
+
+    public synchronized void redirectStdin(InputStream in) {
+        if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdin() after ExecProcess.start()");
+        this.redirectStdin = in;
+    }
+
+    public synchronized void redirectStdout(OutputStream os) {
+        if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStdout() after ExecProcess.start()");
+        this.redirectStdout = os;
+    }
+
+    public synchronized void redirectStderr(OutputStream os) {
+        if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.redirectStderr() after ExecProcess.start()");
+        this.redirectStderr = os;
+    }
+
+    public synchronized void start() throws IOException {
+        if (proc!=null) throw new RuntimeException("you cannot invoke ExecProcess.start() twice");
+        proc = Runtime.getRuntime().exec(command, null, workingDirectory);
+        if (redirectStdin != null) new StreamCopier(redirectStdin, proc.getOutputStream()).start();
+        if (redirectStdout != null) new StreamCopier(proc.getInputStream(), redirectStdout).start();
+        if (redirectStderr != null) new StreamCopier(proc.getErrorStream(), redirectStderr).start();
+    }
+
+    public synchronized void destroy() throws IOException {
+        if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+        proc.destroy();
+    }
+
+    public int waitFor() throws IOException {
+        if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+        try {
+            return proc.waitFor();
+        } catch (InterruptedException ie) {
+            throw new RuntimeException(ie);
+        }
+    }
+
+    public synchronized OutputStream getStdin() {
+        if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+        if (redirectStdin!=null) throw new RuntimeException("you cannot invoke getStdin() after redirectStdin()");
+        return proc.getOutputStream();
+    }
+
+    public synchronized InputStream getStdout() {
+        if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+        if (redirectStdout!=null) throw new RuntimeException("you cannot invoke getStdout() after redirectStdout()");
+        return proc.getInputStream();
+    }
+
+    public synchronized InputStream getStderr() {
+        if (proc==null) throw new RuntimeException("you must invoke ExecProcess.start() first");
+        if (redirectStderr!=null) throw new RuntimeException("you cannot invoke getStderr() after redirectStderr()");
+        return proc.getErrorStream();
+    }
+
+    private Process      proc;
+    private InputStream  redirectStdin;
+    private OutputStream redirectStdout;
+    private OutputStream redirectStderr;
+    private String[]     command;
+    private File         workingDirectory;
+
+    /**
+     *  Copies from an InputStream to an OutputStream; used to implement redirectXXX().
+     */
+    private static class StreamCopier extends Thread {
+        private final byte[] buf = new byte[16 * 1024];
+        private final InputStream is;
+        private final OutputStream os;
+        public StreamCopier(InputStream is, OutputStream os) {
+            setDaemon(true);
+            this.is = is;
+            this.os = os;
+        }
+        public void run() {
+            try {
+                while(true) {
+                    int numread = is.read(buf, 0, buf.length);
+                    if (numread==-1) break;
+                    os.write(buf, 0, numread);
+                }
+            } catch (Exception e) { throw new RuntimeException(e); }
+        }
+    }
+
+}