package org.biojava.utils; /////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002 Scott McCrory // // This program 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; either version 2 // of the License, or (at your option) any later version. // // This program 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 this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /////////////////////////////////////////////////////////////////////////////// import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.StringTokenizer; /** *

Makes running external executables easier, optionally under a watched thread. * * In addition, probably the most useful feature of ExecRunner is using it to * run a command-line program and obtain its stdout and stderr results in two * strings. This is done with exec(String) - see that method for an example. * * With acknowledgements to Michael C. Daconta, author of "Java Pitfalls, * Time Saving Solutions, and Workarounds to Improve Programs." and his * article in JavaWorld "When Runtime.exec() Won't".

* * @author Scott McCrory. * @author Francois Pepin * @author Andreas Dräger * @version CVS $Id: ExecRunner.java 4023 2006-12-11 19:00:50Z holland $ */ public class ExecRunner { /** 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"; /** String to send to STDERR if program exceeds max run time **/ private static final String MAX_RUN_TIME_EXCEEDED_STRING = "MAX_RUN_TIME_EXCEEDED"; /** String to capture STDOUT **/ private String out = new String(); /** String to capture STDERR **/ private String err = new String(); /** Default max run time (in seconds) **/ private int maxRunTimeSecs = 0; /** Flag to indicate if we've exceeded max run time **/ private boolean maxRunTimeExceeded = false; /** * Basic ExecRunner constructor. * */ public ExecRunner() { super(); } /** * ExecRunner constructor which also conveniently runs exec(String). * * @param command The program or command to run * @throws ExceptionInInitializerError thrown if a problem occurs */ public ExecRunner(String command) throws ExceptionInInitializerError { this(); try { exec(command); } catch (IOException ioe) { throw new ExceptionInInitializerError(ioe.getMessage()); } catch (InterruptedException inte) { throw new ExceptionInInitializerError(inte.getMessage()); } } /** * ExecRunner constructor which also conveniently runs exec(String). * * @param command The program or command to run * @param arguments An array of Strings in which every single String * is exactly one of the program's arguments. So these arguments are * allowed to contain white spaces and are marked as Strings by the * system. * @throws ExceptionInInitializerError thrown if a problem occurs * @since 1.5 */ public ExecRunner(String command, String[] arguments) throws ExceptionInInitializerError { this(); try { exec(command, arguments); } catch (IOException ioe) { throw new ExceptionInInitializerError(ioe.getMessage()); } catch (InterruptedException inte) { throw new ExceptionInInitializerError(inte.getMessage()); } } /** * We override the clone method here to prevent cloning of our class. * * @throws CloneNotSupportedException To indicate cloning is not allowed * @return Nothing ever really returned since we throw a CloneNotSupportedException **/ public final Object clone() throws CloneNotSupportedException { throw new java.lang.CloneNotSupportedException(); } /** * The exec(String) method runs a process inside of a watched thread. * It returns the client's exit code and feeds its STDOUT and STDERR to * ExecRunner's out and err strings, where you then use getOutString() * and getErrString() to obtain these values. * If the command String contains arguments which are Strings containing white * spaces, the method
exec(program, arguments)
should be used * instead, where arguments is a String[] array * containing the arguments of the program. * * Example: * *
      * // Execute the program and grab the results
      * String out = "";
      * String err = "";
      * try {
      *
      *     ExecRunner er = new ExecRunner();
      *     er.setMaxRunTimeSecs(maxRunTime);
      *     er.exec(program);
      *     if (!er.getMaxRunTimeExceeded()) {
      *         out = er.getOutString();
      *         err = er.getErrString();
      *     }
      *     else {
      *         log.error("Maximum run time exceeded!");
      *         continue;
      *     }
      *
      * }
      * catch (Exception e) {
      *     log.error("Error executing " + program + ": " + e.getMessage());
      *     continue;
      * }
      * 
* * @return The command's return code * @param command The program or command to run * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs */ public int exec(String command) throws IOException, InterruptedException { StringWriter swOut = new StringWriter(); PrintWriter pwOut = new PrintWriter(swOut, true); StringWriter swErr = new StringWriter(); PrintWriter pwErr = new PrintWriter(swErr, true); int rc = exec(command, pwOut, pwErr); out = swOut.toString(); err = swErr.toString(); return rc; } /** Sometimes special cases may occur that the arguments of an external program * are Strings containing white spaces. Another example are external processes * needing the String arguments to be characterized in a special way. * In this cases it is sensefull to write every single argument in an String * array. The system itselfe will mark these arguments as Strings so that these * special encoding can be omitted. * @param command The external program to be executed. * @param arguments An array of Strings. Every entry of this array is an argument * of the external program to be executed. * @return The command's return code * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs * @since 1.5 */ public int exec(String command, String[] arguments) throws IOException, InterruptedException { StringWriter swOut = new StringWriter(); PrintWriter pwOut = new PrintWriter(swOut, true); StringWriter swErr = new StringWriter(); PrintWriter pwErr = new PrintWriter(swErr, true); int rc = exec(command, arguments, pwOut, pwErr); out = swOut.toString(); err = swErr.toString(); return rc; } /** * Convenience method for calling exec with OutputStreams. * * @return The command's return code * @param command The program or command to run * @param stdoutStream java.io.OutputStream * @param stderrStream java.io.OutputStream * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs **/ public int exec( String command, OutputStream stdoutStream, OutputStream stderrStream) throws IOException, InterruptedException { PrintWriter pwOut = new PrintWriter(stdoutStream, true); PrintWriter pwErr = new PrintWriter(stderrStream, true); return exec(command, pwOut, pwErr); } /** * Convenience method for calling exec with OutputStreams. * * @return The command's return code * @param command The program or command to run * @param arguments An array of Strings containing the program's arguments. * @param stdoutStream java.io.OutputStream * @param stderrStream java.io.OutputStream * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs * @since 1.5 **/ public int exec( String command, String[] arguments, OutputStream stdoutStream, OutputStream stderrStream) throws IOException, InterruptedException { PrintWriter pwOut = new PrintWriter(stdoutStream, true); PrintWriter pwErr = new PrintWriter(stderrStream, true); return exec(command, arguments, pwOut, pwErr); } /** * The exec(String, PrintWriter, PrintWriter) method runs * a process inside of a watched thread. It returns the client's exit code * and feeds its STDOUT and STDERR to the passed-in streams. * * @return The command's return code * @param command The program or command to run * @param stdoutWriter java.io.PrintWriter * @param stderrWriter java.io.PrintWriter * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs **/ public int exec( String command, PrintWriter stdoutWriter, PrintWriter stderrWriter) throws IOException, InterruptedException { // Default exit value is non-zero to indicate a problem. int exitVal = 1; //////////////////////////////////////////////////////////////// Runtime rt = Runtime.getRuntime(); Process proc; String[] cmd = null; // First get the start time & calculate comparison numbers Date startTime = new Date(); long startTimeMs = startTime.getTime(); long maxTimeMs = startTimeMs + (maxRunTimeSecs * 1000); //////////////////////////////////////////////////////////////// // 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")) { 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.equals("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; } } // Execute the command and start the two output gobblers if (cmd != null && cmd.length > 0) { //System.out.println("**Checkpoint** :" + cmd.length); proc = rt.exec(cmd); } else throw new IOException("Insufficient commands!"); StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), stdoutWriter); StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), stderrWriter); outputGobbler.start(); errorGobbler.start(); // Wait for the program to finish running and return the // exit value obtained from the executable while (true) { try { exitVal = proc.exitValue(); break; } catch (IllegalThreadStateException e) { // If we get this exception, then the process isn't // done executing and we determine if our time is up. if (maxRunTimeSecs > 0) { Date endTime = new Date(); long endTimeMs = endTime.getTime(); if (endTimeMs > maxTimeMs) { // Time's up - kill the process and the gobblers and return proc.destroy(); maxRunTimeExceeded = true; stderrWriter.println(MAX_RUN_TIME_EXCEEDED_STRING); outputGobbler.quit(); errorGobbler.quit(); stdoutWriter.close(); stderrWriter.close(); proc.getOutputStream().close(); return exitVal; } else { // Time is not up yet so wait 100 ms before testing again Thread.sleep(100); } } } } //////////////////////////////////////////////////////////////// // Wait for output gobblers to finish forwarding the output while (outputGobbler.isAlive() || errorGobbler.isAlive()) { } //////////////////////////////////////////////////////////////// // All done, flush the streams and return the exit value stdoutWriter.flush(); stderrWriter.flush(); stdoutWriter.close(); stderrWriter.close(); proc.getOutputStream().close(); return exitVal; } /** * The exec(String, PrintWriter, PrintWriter) method runs * a process inside of a watched thread. It returns the client's exit code * and feeds its STDOUT and STDERR to the passed-in streams. * The arguments for the external program or command are passed as an array * of Strings. This has the advantage that the arguments are allowed to * contain white spaces and are marked to be Strings by the System itselfe, * which is sometimes important. Every single argument has to be exactly one * dimension of the arguments array. * * @return The command's return code * @param command The program or command to run * @param arguments An array of Strings containing the program's arguments/options. * @param stdoutWriter java.io.PrintWriter * @param stderrWriter java.io.PrintWriter * @throws IOException thrown if a problem occurs * @throws InterruptedException thrown if a problem occurs * @since 1.5 **/ public int exec( String command, String[] arguments, PrintWriter stdoutWriter, PrintWriter stderrWriter) throws IOException, InterruptedException { // Default exit value is non-zero to indicate a problem. int exitVal = 1; //////////////////////////////////////////////////////////////// Runtime rt = Runtime.getRuntime(); Process proc; String[] cmd = null; // First get the start time & calculate comparison numbers Date startTime = new Date(); long startTimeMs = startTime.getTime(); long maxTimeMs = startTimeMs + (maxRunTimeSecs * 1000); int i = 0; //////////////////////////////////////////////////////////////// // First determine the OS to build the right command string String osName = System.getProperty("os.name"); if (osName.startsWith("Windows")) { cmd = new String[3+arguments.length]; if (osName.endsWith("NT") || osName.endsWith("2000")) { cmd[0] = WINDOWS_NT_2000_COMMAND_1; cmd[1] = WINDOWS_NT_2000_COMMAND_2; } else if (osName.endsWith("95") || osName.endsWith("98") || osName.endsWith("ME")) { cmd[0] = WINDOWS_9X_ME_COMMAND_1; cmd[1] = WINDOWS_9X_ME_COMMAND_2; } cmd[2] = command; for (i = 3; i 0) { //System.out.println("**Checkpoint** :" + cmd.length); proc = rt.exec(cmd); } else { throw new IOException("Insufficient commands!"); } StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), stdoutWriter); StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), stderrWriter); outputGobbler.start(); errorGobbler.start(); // Wait for the program to finish running and return the // exit value obtained from the executable while (true) { try { exitVal = proc.exitValue(); break; } catch (IllegalThreadStateException e) { // If we get this exception, then the process isn't // done executing and we determine if our time is up. if (maxRunTimeSecs > 0) { Date endTime = new Date(); long endTimeMs = endTime.getTime(); if (endTimeMs > maxTimeMs) { // Time's up - kill the process and the gobblers and return proc.destroy(); maxRunTimeExceeded = true; stderrWriter.println(MAX_RUN_TIME_EXCEEDED_STRING); outputGobbler.quit(); errorGobbler.quit(); stdoutWriter.close(); proc.getOutputStream().close(); stderrWriter.close(); return exitVal; } else { // Time is not up yet so wait 100 ms before testing again Thread.sleep(100); } } } } //////////////////////////////////////////////////////////////// // Wait for output gobblers to finish forwarding the output while (outputGobbler.isAlive() || errorGobbler.isAlive()) { } //////////////////////////////////////////////////////////////// // All done, flush the streams and return the exit value stdoutWriter.flush(); stderrWriter.flush(); stdoutWriter.close(); stderrWriter.close(); proc.getOutputStream().close(); return exitVal; } /** * Returns the error string if exec(String) was invoked. * * @return The error string if exec(String) was invoked. */ public String getErrString() { return err; } /** * Returns whether the maximum runtime was exceeded or not. * * @return boolean indicating whether the maximum runtime was exceeded or not. */ public boolean getMaxRunTimeExceeded() { return maxRunTimeExceeded; } /** * Returns the maximum run time in seconds for this object. * * @return the maximum run time in seconds for this object. */ public int getMaxRunTimeSecs() { return maxRunTimeSecs; } /** * Returns the output string if exec(String) was invoked. * * @return The output string if exec(String) was invoked. */ public String getOutString() { return out; } /** * This is for unit testing of the class. * * @param args an array of command-line arguments * @throws IOException thrown if a problem occurs **/ public static void main(String[] args) throws IOException { try { ExecRunner er = new ExecRunner(); ///////////////////////////////////////////////////////////////////// // Linux: Test the exec operation with just STDOUT and STDERR //System.out.println("Testing ExecRunner with STDOUT and STDERR..."); //er.exec("ls -l", System.out, System.err); //System.out.println("Complete"); ///////////////////////////////////////////////////////////////////// // Windows: Test the exec operation with just STDOUT and STDERR System.out.println("Testing ExecRunner with StringWriter..."); er = new ExecRunner(); er.setMaxRunTimeSecs(1); er.exec("dir /s c:\\"); //er.exec("ls -l"); System.out.println("\n" + er.getOutString() + ""); System.out.println("\n" + er.getErrString() + ""); System.out.println("Testing Done"); ///////////////////////////////////////////////////////////////////// // Exit nicely System.exit(0); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } /** * We override the readObject method here to prevent * deserialization of our class for security reasons. * * @param in java.io.ObjectInputStream * @throws IOException thrown if a problem occurs **/ private final void readObject(ObjectInputStream in) throws IOException { throw new IOException("Object cannot be deserialized"); } /** * Sets the maximum run time in seconds. * If you do not want to limit the executable's run time, simply pass in * a 0 (which is also the default). * * @param max Maximim number of seconds to let program run */ public void setMaxRunTimeSecs(int max) { maxRunTimeSecs = max; } /** * We override the writeObject method here to prevent * serialization of our class for security reasons. * * @param out java.io.ObjectOutputStream * @throws IOException thrown if a problem occurs **/ private final void writeObject(ObjectOutputStream out) throws IOException { throw new IOException("Object cannot be serialized"); } } /** *

Captures the output of an InputStream.

* * With acknowledgements to Michael C. Daconta, author of "Java Pitfalls, * Time Saving Solutions, and Workarounds to Improve Programs." and his * article in JavaWorld "When Runtime.exec() Won't". * * See the ExecRunner class for a reference implementation. * * @author Scott McCrory. * @version CVS $Id: ExecRunner.java 4023 2006-12-11 19:00:50Z holland $ */ class StreamGobbler extends Thread { /** The input stream we're gobbling **/ private InputStream in = null; /** The printwriter we'll send the gobbled characters to if asked**/ private PrintWriter pwOut = null; /** Our flag to allow us to safely terminate the monitoring thread **/ private boolean quit = false; /** * A simpler constructor for StreamGobbler - defaults to stdout. * * @param in InputStream */ public StreamGobbler(InputStream in) { this.in = in; this.pwOut = new PrintWriter(System.out, true); } /** * A more explicit constructor for StreamGobbler where you can tell * it exactly where to relay the output to. * Creation date: (9/23/2001 8:48:01 PM) * * @param in InputStream * @param out OutputStream */ public StreamGobbler(InputStream in, OutputStream out) { this.in = in; this.pwOut = new PrintWriter(out, true); } /** * A more explicit constructor for StreamGobbler where you can tell * it exactly where to relay the output to. * Creation date: (9/23/2001 8:48:01 PM) * * @param in InputStream * @param pwOut PrintWriter */ public StreamGobbler(InputStream in, PrintWriter pwOut) { this.in = in; this.pwOut = pwOut; } /** * We override the clone method here to prevent cloning of our class. * * @throws CloneNotSupportedException To indicate cloning is not allowed * @return Nothing ever really returned since we throw a CloneNotSupportedException **/ public final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } /** * Tells the StreamGobbler to quit it's operation. * This is safer than using stop() since it uses a semophore checked in the * main wait loop instead of possibly forcing semaphores to untimely unlock. */ public void quit() { quit = true; } /** * We override the readObject method here to prevent * deserialization of our class for security reasons. * * @param in java.io.ObjectInputStream * @throws IOException thrown if a problem occurs **/ private final void readObject(ObjectInputStream in) throws IOException { throw new IOException("Object cannot be deserialized"); } /** * Gobbles up all the stuff coming from the InputStream and * sends it to the OutputStream specified during object construction. **/ public void run() { try { // Set up the input stream BufferedReader br = new BufferedReader(new InputStreamReader(in)); // Initialize the temporary results containers String line = null; // Main processing loop which captures the output while ((line = br.readLine()) != null) { if (quit) break; else pwOut.println(line); } this.in.close(); } catch (Exception e) { e.printStackTrace(); } } /** * We override the writeObject method here to prevent * serialization of our class for security reasons. * * @param out java.io.ObjectOutputStream * @throws IOException thrown if a problem occurs **/ private final void writeObject(ObjectOutputStream out) throws IOException { throw new IOException("Object cannot be serialized"); } }