//============================================================================== // RuntimeExec.java //============================================================================== package tribble.util; // System imports import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.Exception; import java.lang.InterruptedException; import java.lang.Process; import java.lang.Runtime; import java.lang.RuntimeException; import java.lang.String; import java.lang.StringBuffer; import java.lang.System; import java.lang.Thread; /******************************************************************************* * Contains methods to execute a command shell in a separate process. * This is designed to be a replacement for the java.lang.Runtime.exec() * methods. * *

* The java.lang.Runtime.exec() method is horribly designed. To use it, * the calling program (i.e., the programmer writing the calling program) is * burdened with handling all of the data written to the standard output and * error streams. This takes an rather large amount of code, involving threads * and being very difficult to do correctly. * *

* Hence the need for this class. It provides a much more straightforward API * method, {@link #exec(String[], String[], File, OutputStream, OutputStream) exec()}, * which handles all of the nasty details of redirecting the standard I/O * streams. * * *

* Implementation Notes *

* The exec() method(s) invoke the default command shell program for the * native operating system. The name of this program is determined by examining * the native operating system name, which is stored as the "os.name" * system property, and using this to look up the command shell name in a * hard-coded table (SHELL_PGMS[]). As coded, only a minimal set of * operating systems are contained in the table, so it is likely that the table * will need to be amended when porting this class to other systems. * * * @version $Revision: 1.4 $ $Date: 2007/04/28 15:02:54 $ * @since 2006-08-08 * @author David R. Tribble (david@tribble.com). *

* Copyright ©2007 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. *

* If you find this software useful, please drop me a note describing how * you used it. * * @see java.lang.Runtime#exec java.lang.Runtime.exec() */ public class RuntimeExec { // Identification /** Revision information. */ static final String REV = "@(#)tribble/util/RuntimeExec.java $Revision: 1.4 $ $Date: 2007/04/28 15:02:54 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants private static final String OP_SYS = System.getProperty("os.name"); private static final String SINGLE = "single"; private static final String MULTI = "multi"; private static final String NONE = "none"; private static final String[][] SHELL_PGMS = { { "Windows XP", MULTI, "cmd", "/c" }, { "Windows 2000", MULTI, "cmd", "/c" }, { "Windows NT", MULTI, "cmd", "/c" }, { "Windows 95", MULTI, "command", "/c" }, { "Windows", MULTI, "command", "/c" }, { "Unix", SINGLE, "/bin/ksh", "-c" }, // Add more O/S shell programs here... }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static variables /** Local JVM runtime object. */ private static Runtime s_runtime = Runtime.getRuntime(); /** Native shell program. */ private static String[] s_shellCmd; /** Native shell program type ({@link SINGLE} or {@link MULTI}). */ private static String s_shellType; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods /*************************************************************************** * Determine the native command shell program. * * @throws IOException * Thrown if the native shell command cannot be determined. * * @throws UnsupportedOperationException (unchecked) * Thrown if the native O/S is not recognized. * * @since 1.1, 2007-04-19 */ private static void getShellCmd() throws IOException { String prog; int i; // Determine the command shell program for the native O/S prog = null; for (i = 0; i < SHELL_PGMS.length; i++) if (OP_SYS.equalsIgnoreCase(SHELL_PGMS[i][0])) break; if (i >= SHELL_PGMS.length) throw new UnsupportedOperationException( "No command shell program for O/S: '" + OP_SYS + "'"); // Save the native command shell program s_shellType = SHELL_PGMS[i][1]; s_shellCmd = new String[SHELL_PGMS[i].length-2]; for (int j = 2; j < SHELL_PGMS[i].length; j++) s_shellCmd[j-2] = SHELL_PGMS[i][j]; } /*************************************************************************** * Execute a native shell command. * The command is executed in a separate process using the native command * shell. This method waits for the process to terminate and then returns * the command's exit status. * * @param cmd * Command line. The first word in the string is the name of the program to * execute. Command line arguments containing spaces or tabs should be * enclosed within quotes so that the command shell can handle them properly. * * @param env * Environment variables. This is an array of strings of the form * "name=value", where each name is the name of * an environment variable and value is its value. * If the array is null, the shell command inherits the environment from the * calling program. * * @param dir * Current working directory in which to execute the shell command. * If this is null, the command executes within the current working directory * of the calling program. * * @param out * The standard output stream that the shell command writes to. * This can be null, in which case any output the shell command writes is * discarded. * * @param err * The standard error output stream that the shell command writes to. * This can be null, in which case any error output the shell command writes * is discarded. * * @return * The exit status of the shell command, which is typically a small integer * value. Zero generally indicates success. * * @since 1.2, 2007-04-21 */ public static int exec(String cmd, String[] env, File dir, OutputStream out, OutputStream err) throws IOException { String[] argv; int i; // Initialize the native shell command if (s_shellCmd == null) getShellCmd(); // Set up the external native shell command argv = new String[s_shellCmd.length + 1]; for (i = 0; i < s_shellCmd.length; i++) argv[i] = s_shellCmd[i]; argv[i] = cmd; // Execute the shell command return (execShell(argv, env, dir, out, err)); } /*************************************************************************** * Execute a native shell command. * The command is executed in a separate process using the native command * shell. This method waits for the process to terminate and then returns * the command's exit status. * * @param cmd * Array of command line parameters. The first element (cmd[0]) is * the name of the program to execute. Command line arguments containing * spaces or tabs are enclosed within quotes (") so that the command * shell can handle them properly. * * @param env * Environment variables. This is an array of strings of the form * "name=value", where each name is the name of * an environment variable and value is its value. * If the array is null, the shell command inherits the environment from the * calling program. * * @param dir * Current working directory in which to execute the shell command. * If this is null, the command executes within the current working directory * of the calling program. * * @param out * The standard output stream that the shell command writes to. * This can be null, in which case any output the shell command writes is * discarded. * * @param err * The standard error output stream that the shell command writes to. * This can be null, in which case any error output the shell command writes * is discarded. * * @return * The exit status of the shell command, which is typically a small integer * value. Zero generally indicates success. * * @since 1.2, 2007-04-21 */ public static int exec(String[] cmd, String[] env, File dir, OutputStream out, OutputStream err) throws IOException { // Initialize the native shell command if (s_shellCmd == null) getShellCmd(); // Set up the external native shell command if (s_shellType == SINGLE) { StringBuffer cmdBuf; // Combine all the command args into a single shell arg cmdBuf = new StringBuffer(80); for (int i = 0; i < cmd.length; i++) { String arg; if (i > 0) cmdBuf.append(' '); arg = cmd[i]; if ((arg.indexOf(' ') >= 0 || arg.indexOf('\t') >= 0) && arg.charAt(0) == '"' && arg.endsWith("\"")) { cmdBuf.append('"'); cmdBuf.append(arg); cmdBuf.append('"'); } else cmdBuf.append(arg); } // Execute the shell command return (exec(cmdBuf.toString(), env, dir, out, err)); } else if (s_shellType == MULTI) { String[] argv; int i, j; // Pass multiple command args to the shell command argv = new String[s_shellCmd.length + cmd.length]; for (i = j = 0; i < s_shellCmd.length; i++) argv[j++] = s_shellCmd[i]; for (i = 0; i < cmd.length; i++) argv[j++] = cmd[i]; // Execute the shell command return (execShell(argv, env, dir, out, err)); } else // (s_shellType == NONE) { // Pass multiple command args as is to the native O/S return (execShell(cmd, env, dir, out, err)); } } /*************************************************************************** * Execute a native shell command. * The command is executed in a separate process using the native command * shell. This method waits for the process to terminate and then returns * the command's exit status. * *

* This is a helper method called by the exec() methods. * * @return * The exit status of the shell command, which is typically a small integer * value. Zero generally indicates success. * * @since 1.2, 2007-04-21 */ private static int execShell(String[] argv, String[] env, File dir, OutputStream out, OutputStream err) throws IOException { Process proc; IOStream cmdOut; IOStream cmdErr; // Start the external shell command proc = s_runtime.exec(argv, env, dir); // Set up the I/O streams for the external command shell cmdOut = new IOStream(proc.getInputStream(), out); cmdErr = new IOStream(proc.getErrorStream(), err); proc.getOutputStream().close(); cmdOut.start(); cmdErr.start(); // Wait for the external command to complete for (;;) { try { int rc; rc = proc.waitFor(); return (rc); } catch (InterruptedException ex) { } } } /*************************************************************************** * Execute a native shell command. * The command is executed in a separate process using the native command * shell. This method waits for the process to terminate and then returns * the command's exit status. * * @param cmd * Command line. The first word in the string is the name of the program to * execute. Command line arguments containing spaces or tabs should be * enclosed within quotes so that the command shell can handle them properly. * * @param env * Environment variables. This is an array of strings of the form * "name=value", where each name is the name of * an environment variable and value is its value. * If the array is null, the shell command inherits the environment from the * calling program. * * @param dir * Current working directory in which to execute the shell command. * If this is null, the command executes within the current working directory * of the calling program. * * @param in * The standard input stream that the shell command reads from. * This can be null, in which case the shell command receives no input, i.e., * its standard input stream is empty. * * @param out * The standard output stream that the shell command writes to. * This can be null, in which case any output the shell command writes is * discarded. * * @param err * The standard error output stream that the shell command writes to. * This can be null, in which case any error output the shell command writes * is discarded. * * @return * The exit status of the shell command, which is typically a small integer * value. Zero generally indicates success. * * @since 1.1, 2007-04-20 */ ///+Make this method 'public' when it is fixed /*public*/ static int exec(String cmd, String[] env, File dir, InputStream in, OutputStream out, OutputStream err) throws IOException { String[] argv; Process proc; IOStream cmdIn; IOStream cmdOut; IOStream cmdErr; int rc; int i; // Initialize the native shell command if (s_shellCmd == null) getShellCmd(); // Set up the external native shell command argv = new String[s_shellCmd.length + 1]; for (i = 0; i < s_shellCmd.length; i++) argv[i] = s_shellCmd[i]; argv[i] = cmd; // Start the external shell command proc = s_runtime.exec(argv, env, dir); // Set up the I/O streams for the external command shell cmdIn = new IOStream(in, proc.getOutputStream()); cmdOut = new IOStream(proc.getInputStream(), out); cmdErr = new IOStream(proc.getErrorStream(), err); cmdIn.start(); cmdOut.start(); cmdErr.start(); // Wait for the external command to complete for (;;) { try { rc = proc.waitFor(); break; } catch (InterruptedException ex) { } } ///+INCOMPLETE, cmdIn thread needs to be stopped System.out.println("$ cmdIn.alive= " + cmdIn.isAlive()); System.out.println("$ cmdOut.alive=" + cmdOut.isAlive()); System.out.println("$ cmdErr.alive=" + cmdErr.isAlive()); System.out.println("$ (2) cmdIn"); cmdIn.closeOut(); //cmdIn.interrupt(); //cmdIn.destroy(); while (cmdOut.isAlive()) continue; System.out.println("$ cmdIn.alive= " + cmdIn.isAlive()); System.out.println("$ cmdOut.alive=" + cmdOut.isAlive()); System.out.println("$ cmdErr.alive=" + cmdErr.isAlive()); return (rc); } /*************************************************************************** * Execute a native shell command. * The command is executed in a separate process using the native command * shell. This method waits for the process to terminate and then returns * the command's exit status. * * @param cmd * Array of command line parameters. The first element (cmd[0]) is * the name of the program to execute. Command line arguments containing * spaces or tabs are enclosed within quotes (") so that the command * shell can handle them properly. * * @param env * Environment variables. This is an array of strings of the form * "name=value", where each name is the name of * an environment variable and value is its value. * If the array is null, the shell command inherits the environment from the * calling program. * * @param dir * Current working directory in which to execute the shell command. * If this is null, the command executes within the current working directory * of the calling program. * * @param in * The standard input stream that the shell command reads from. * This can be null, in which case the shell command receives no input, i.e., * its standard input stream is empty. * * @param out * The standard output stream that the shell command writes to. * This can be null, in which case any output the shell command writes is * discarded. * * @param err * The standard error output stream that the shell command writes to. * This can be null, in which case any error output the shell command writes * is discarded. * * @return * The exit status of the shell command, which is typically a small integer * value. Zero generally indicates success. * * @since 1.1, 2007-04-19 */ ///+Make this method 'public' when the method above is fixed /*public*/ static int exec(String[] cmd, String[] env, File dir, InputStream in, OutputStream out, OutputStream err) throws IOException { StringBuffer cmdBuf; Process proc; // Initialize the native shell command if (s_shellCmd == null) getShellCmd(); // Set up the external shell command line cmdBuf = new StringBuffer(80); for (int i = 0; i < cmd.length; i++) { String arg; if (i > 0) cmdBuf.append(' '); arg = cmd[i]; if ((arg.indexOf(' ') >= 0 || arg.indexOf('\t') >= 0) && arg.charAt(0) == '"' && arg.endsWith("\"")) { cmdBuf.append('"'); cmdBuf.append(arg); cmdBuf.append('"'); } else cmdBuf.append(arg); } // Execute the shell command return (exec(cmdBuf.toString(), env, dir, in, out, err)); } /*************************************************************************** * Test driver. * Execute a command using the native shell command program. * * @param args * Command line args, specifying a shell command to execute. * * @since 1.1, 2007-04-19 */ public static void main(String[] args) throws Exception { int rc; // Check command line usage if (args.length < 1) { System.out.println("Execute a command using the native shell" + " command program."); System.out.println(); System.out.println("usage: java " + Runtime.class.getName() + " cmd [arg...]"); System.out.println(); System.exit(255); } // Execute the shell command; // see how much easier this is to use than java.lang.Runtime.exec()? rc = exec(args, null, null, System.out, System.err); // Can't use this method yet until it is fixed... //rc = exec(args, null, null, System.in, System.out, System.err); System.exit(rc); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Inner classes /*************************************************************************** * Contains methods to redirect I/O from an executing native command shell * program. * * * @version $Revision: 1.4 $ $Date: 2007/04/28 15:02:54 $ * @since {@link Runtime} 1.1, 2007-04-20 * @author David R. Tribble (david@tribble.com). *

* Copyright ©2007 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those * designated by by the United States Department of State as a * terrorist, or terrorist government or agency, to use and * distribute this source code provided that the original copyright * notice remains present and unaltered. *

* If you find this software useful, please drop me a note * describing how you used it. * * @see Runtime */ private static class IOStream extends java.lang.Thread { // Identification /** Revision information. */ static final String REV = "@(#)tribble/util/RuntimeExec$IOStream.java $Revision: 1.4 $ $Date: 2007/04/28 15:02:54 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants /** I/O buffer size. */ private static final int BUFSIZE = 8*1024; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** Input stream. */ private InputStream m_in; /** Output stream. */ private OutputStream m_out; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*********************************************************************** * Constructor. * * @param in * Input stream to read data from. * * @param out * Output stream to redirect the data from in into. * * @since 1.1, 2007-04-20 */ IOStream(InputStream in, OutputStream out) { m_in = in; m_out = out; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*********************************************************************** * Close the output stream of this I/O stream handler. * * @since 1.3, 2007-04-25 */ void closeOut() { try { // Close the output stream m_out.close(); } catch (IOException ex) { } } /*********************************************************************** * Run this stream handler in its own thread. * *

* Data is read from the input stream and then written to the output * stream. Note that the streams are not closed after the data * has been copied. * *

* This thread is terminated if an IOException occurs while * copying the data streams. * * @since 1.1, 2007-04-20 */ public void run() { byte[] buf; // Sanity check if (m_in == null) return; // Redirect data from the input stream to the output stream buf = new byte[BUFSIZE]; for (;;) { try { int len; // Read a block of data from the input stream len = m_in.read(buf, 0, buf.length); if (len < 0) break; // Write the block of data to the output stream if (len > 0 && m_out != null) { m_out.write(buf, 0, len); m_out.flush(); } } catch (IOException ex) { // I/O error, stop processing break; } } } } } // End RuntimeExec.java