/* * BioJava development code * * This code may be freely distributed and modified under the * terms of either the BSD licence or the GNU Lesser General * Public Licence. These should be distributed with the code. * If you do not have copies see: * * http://www.opensource.org/licenses/bsd-license.php * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.utils; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.StringTokenizer; /** * Convenience methods for running external processes. This class * offers wrappers around the java.lang.Process API, * but hides away the details of managing threads and process I/O. * *

Example

* *
 * StringWriter out = new StringWriter();
 * ProcessTools.exec(
 *          new String[] {"/usr/bin/wc", "-w"},
 *          "The quick brown fox jumps over the lazy dog",
 *          out,
 *          null
 * );
 * int numWords = Integer.parseInt(out.toString().trim());
 * 
* * @author Thomas Down * @author Francois Pepin * @since 1.4 * @deprecated preferable to use org.biojava.utils.ExecRunner * or the org.biojava.utils.process package. */ public class ProcessTools { /** Win NT/2K/MEPro require cmd.exe to run programs **/ private static final String WINDOWS_NT_2000_COMMAND_1 = "cmd.exe"; /** Win NT/2K/MEPro require the /C to specify what to run **/ private static final String WINDOWS_NT_2000_COMMAND_2 = "/C"; /** Win 9X/MEHome require cmd.exe to run programs **/ private static final String WINDOWS_9X_ME_COMMAND_1 = "command.exe"; /** Win 9X/MEHome require the /C to specify what to run **/ private static final String WINDOWS_9X_ME_COMMAND_2 = "/C"; // Dummy constructor since this is just a tools class private ProcessTools() { } /** * Execute the specified command and wait for it to return. * * @param args the command line to execute. * @param input data to present to the process' standard input, or null if the process does not require input. * @param stdout a Writer which will be filled with data from the process' output stream, or null to ignore output. * @param stderr a Writer which will be filled with data from the process' error stream, or null to ignore output. * @return the process' return code. */ public static int exec( String[] args, Reader input, Writer stdout, Writer stderr ) throws IOException { try { return exec(args, null, null, input, stdout, stderr, 0L); } catch (ProcessTimeoutException ex) { throw new Error("Assertion failed: unexpected process timeout"); } } /** * Execute the specified command and wait for it to return, or kill * it if the specified timeout expires first. * * @param args the command line to execute. * @param envp environment variables for the child process, or null to inherit the current set. * @param dir working directory for the child process, or null to inherit the current directory. * @param input data to present to the process' standard input, or null if the process does not require input. * @param stdout a Writer which will be filled with data from the process' output stream, or null to ignore output. * @param stderr a Writer which will be filled with data from the process' error stream, or null to ignore output. * @param timeout maximum run-time (in milliseconds) for the child process. A value of 0 indicates no limit. * @return the process' return code. * @throws IOException if an error occurs while starting or communicating with the process * @throws ProcessTimeoutException if the child process was killed because its timeout had expired. */ public static int exec( String[] args, String[] envp, File dir, Reader input, Writer stdout, Writer stderr, long timeout ) throws IOException, ProcessTimeoutException { Process proc = Runtime.getRuntime().exec(args, envp, dir); CharPump outPump; CharPump inPump, errPump; if (input == null) { input = new StringReader(""); } outPump = new PumpReaderToWriter(input, new OutputStreamWriter(proc.getOutputStream())); if (stdout == null) { inPump = new PumpReaderToNull(new InputStreamReader(proc.getInputStream())); } else { inPump = new PumpReaderToWriter(new InputStreamReader(proc.getInputStream()), stdout); } if (stderr == null) { errPump = new PumpReaderToNull(new InputStreamReader(proc.getErrorStream())); } else { errPump = new PumpReaderToWriter(new InputStreamReader(proc.getErrorStream()), stderr); } TimeBomb tb = null; if (timeout > 0) { tb = new TimeBomb(proc, timeout); tb.start(); } outPump.start(); inPump.start(); errPump.start(); int rc; try { rc = proc.waitFor(); if (tb != null) { tb.defuse(); } outPump.join(); inPump.join(); errPump.join(); } catch (InterruptedException iex) { throw new IOException("Error waiting for process to complete"); } if (tb != null && tb.fired()) { // ProcessTimeoutException trumps any IOExceptions, since odd exceptions // may be generated after the process is destroyed. throw new ProcessTimeoutException(rc); } else { checkException(outPump, "Output to child"); checkException(inPump, "Input from child"); checkException(errPump, "Errors from child"); return rc; } } /** * Execute the specified command and wait for it to return. This is the * simplified version that tries to be nice and make your life easier. If * you know exactly what you want, you might want to use exec(String[],...) * instead. * * @param command the command line to execute. * @param input data to present to the process' standard input, or * null if the process does not require input. * @param stdout a Writer which will be filled with data * from the process' output stream, or null to ignore output. * @param stderr a Writer which will be filled with data * from the process' error stream, or null to ignore output. * @return the process' return code. * @throws IOException if an error occurs while starting or communicating * with the process */ public static int exec( String command, Reader input, Writer stdout, Writer stderr) throws IOException { try { return exec(command, null, null, input, stdout, stderr, 0L); } catch (ProcessTimeoutException ex) { throw new Error("Assertion failed: unexpected process timeout"); } } /** * Execute the specified command and wait for it to return. This is the * simplified version that tries to be nice and make your life easier. If * you know exactly what you want, you might want to use exec(String[],...) * instead. * * @param command the command line to execute. * @param input data to present to the process' standard input, or * null if the process does not require input. * @param stdout a Writer which will be filled with data * from the process' output stream, or null to ignore output. * @param stderr a Writer which will be filled with data * from the process' error stream, or null to ignore output. * @param timeout maximum run-time (in milliseconds) for the child process. A value of 0 indicates no limit. * @return the process' return code. * @throws IOException if an error occurs while starting or communicating * with the process * @throws ProcessTimeoutException if the child process was killed because its timeout had expired. */ public static int exec( String command, String[] envp, File dir, Reader input, Writer stdout, Writer stderr, long timeout) throws IOException, ProcessTimeoutException { String[] cmd = null; // First determine the OS to build the right command string String osName = System.getProperty("os.name"); if (osName.equals("Windows NT") || osName.equals("Windows 2000") || osName.equals("Windows XP")) { cmd = new String[3]; cmd[0] = WINDOWS_NT_2000_COMMAND_1; cmd[1] = WINDOWS_NT_2000_COMMAND_2; cmd[2] = command; } else if ( osName.equals("Windows 95") || osName.equals("Windows 98") || osName.equalsIgnoreCase("Windows ME")) { cmd = new String[3]; cmd[0] = WINDOWS_9X_ME_COMMAND_1; cmd[1] = WINDOWS_9X_ME_COMMAND_2; cmd[2] = command; } else { // Linux (and probably other *nixes) prefers to be called // with each argument supplied separately, so we first // Tokenize it across spaces as the boundary. StringTokenizer st = new StringTokenizer(command, " "); cmd = new String[st.countTokens()]; int token = 0; while (st.hasMoreTokens()) { String tokenString = st.nextToken(); //System.out.println(tokenString); cmd[token++] = tokenString; } } return exec(cmd, envp, dir, input,stdout,stderr, timeout); } /** * Check the status of a Pump and re-throw any exception which may * have occured during its lifecycle * private static void checkException(Pump p, String msg) throws IOException { IOException ioe = p.getException(); if (ioe != null) { throw new IOException("Exception processing " + msg); } }*/ private static void checkException(CharPump p, String msg) throws IOException { IOException ioe = p.getException(); if (ioe != null) { throw new IOException("Exception processing " + msg); } } /** * Thread which will kill the specified process if it is not defused before * the timeout expires. */ private static class TimeBomb extends Thread { private volatile boolean ticking = false; private volatile boolean fired = false; private final long time; private final Process victim; public TimeBomb(Process victim, long time) { this.time = time; this.victim = victim; } public void run() { synchronized(this) { try { ticking = true; wait(time); } catch (InterruptedException ex) { System.err.println("Timebomb thread was interrupted -- this shouldn't happen"); } } if (ticking) { // System.err.println("TimeBomb activated -- killing child process"); fired = true; victim.destroy(); } } public synchronized void defuse() { ticking = false; notifyAll(); } public boolean fired() { return fired; } } /** * Base class for threads which pump bytes from a source to a sink. Subclasses * must implement sourceData and sinkData. They may also wish to override * shutdownHook, a dummy method which is called when the pump finishes (usually * because the end of the data source has been reached). * private static abstract class Pump extends Thread { private IOException err = null; /** * Read bytes of data from some data source. * protected abstract int sourceData(byte[] buf) throws IOException; /** * Write bytes of data to some data sink. * protected abstract void sinkData(byte[] buf, int len) throws IOException; /** * Perform any required tidying operations when the Pump's job has finished. * protected void shutdownHook() throws IOException {}; public void run() { try { byte[] buf = new byte[256]; int cnt; do { cnt = sourceData(buf); if (cnt > 0) { sinkData(buf, cnt); } } while (cnt >= 0); shutdownHook(); } catch (IOException e) { this.err = e; } } public IOException getException() { return err; } }*/ private static abstract class CharPump extends Thread { private IOException err = null; /** * Read bytes of data from some data source. */ protected abstract int sourceData(char[] buf) throws IOException; /** * Write bytes of data to some data sink. */ protected abstract void sinkData(char[] buf, int len) throws IOException; /** * Perform any required tidying operations when the Pump's job has finished. */ protected void shutdownHook() throws IOException {}; public void run() { try { char[] buf = new char[256]; int cnt; do { cnt = sourceData(buf); if (cnt > 0) { sinkData(buf, cnt); } } while (cnt >= 0); shutdownHook(); } catch (IOException e) { this.err = e; } } public IOException getException() { return err; } } /* private static final class PumpStreamToStringBuffer extends Pump { private final InputStream is; private final StringBuffer sb; public PumpStreamToStringBuffer(InputStream is, StringBuffer sb) { super(); this.is = is; this.sb = sb; } protected int sourceData(byte[] buf) throws IOException { return is.read(buf); } protected void sinkData(byte[] buf, int len) { sb.append(new String(buf, 0, len)); } } private static final class PumpStreamToNull extends Pump { private final InputStream is; public PumpStreamToNull(InputStream is) { super(); this.is = is; } protected int sourceData(byte[] buf) throws IOException { return is.read(buf); } protected void sinkData(byte[] buf, int len) { } } */ private static final class PumpReaderToNull extends CharPump { private final Reader is; public PumpReaderToNull(Reader is) { super(); this.is = is; } protected int sourceData(char[] buf) throws IOException { return is.read(buf); } protected void sinkData(char[] buf, int len) { } } private static final class PumpReaderToWriter extends CharPump { private final Reader reader; private final Writer writer; public PumpReaderToWriter(Reader reader, Writer writer){ this.reader=reader; this.writer=writer; } protected int sourceData(char[] buf) throws IOException { return reader.read(buf, 0, buf.length); } protected void sinkData(char[] buf, int len) throws IOException { writer.write(buf, 0, len); writer.flush(); } protected void shutdownHook() throws IOException { writer.close(); } } /* private static final class PumpStreamToStream extends Pump { private final InputStream is; private final OutputStream os; public PumpStreamToStream(InputStream is, OutputStream os) { this.is = is; this.os = os; } protected int sourceData(byte[] buf) throws IOException { return is.read(buf); } protected void sinkData(byte[] buf, int len) throws IOException { os.write(buf, 0, len); os.flush(); } protected void shutdownHook() throws IOException { os.close(); } }*/ }