When runtime.exec () won't
Navigate yourself arround pitfalls related to the runtime.exec () METHOD
Summary In this installment of Java Traps, Michael Daconta discusses one new pitfall and revisits another from his previous column. Originating in the java.lang package, the pitfall specifically involves problems with the Runtime.exec () method. Daconta also corrects an error from Pitfall 3 and Offers a Simpler Solution. (2,500 Words)
Advertisement
s part of the Java language, the java.lang package is implicitly imported into every Java program. This package's pitfalls surface often, affecting most programmers. This month, I'll discuss the traps lurking in the Runtime.exec () method.
Pitfall 4: when runtime.exec () Won't the class java.lang.Runtime Features a static method Called getRuntime (), Which Retrieves The Current Java Runtime Environment.
That is the only way to obtain a reference to the Runtime object. With that reference, you can run external programs by invoking the Runtime class's exec () method. Developers often call this method to launch a browser for displaying a help page in HTML.
There Are Four Overloaded Versions of The Exec () Command:
Public process exec (String Command); Public Process Exec (String Commoc); Public Process Exec (String Command, String [] ENVP); Public Process Exec (String [] cmdarray, String [] ENVP);
For each of these methods, a command - and possibly a set of arguments -. Is passed to an operating-system-specific function call This subsequently creates an operating-system-specific process (a running program) with a reference to a Process class returned to the Java VM The Process class is an abstract class, because a specific subclass of Process exists for each operating system.You can pass three possible input parameters into these methods.:
A Single String That Repesents Both The Program To Execute and Any Arguments to That Program
An Array of strings That Separate The Program from ITS Arguments
An Array of Environment Variables
Pass in the environment variables in the form name = value. If you use the version of exec () with a single string for both the program and its arguments, note that the string is parsed using white space as via the delimiter the StringTokenizer class.
Stumpling Into An IllegalthreadStateException
The first pitfall relating to Runtime.exec () is the IllegalThreadStateException. The prevalent first test of an API is to code its most obvious methods. For example, to execute a process that is external to the Java VM, we use the exec () Method. To see the value That The External Process Returns, We Use the exitvalue () Method on the process class. IN OUR First Example, We Well Attempt to Execute The Java Compiler (Javaac.exe):
Listing 4.1 Badexecjavac.java
Import java.util. *; import java.io. *; public class badexecjavac {public static void main (string args []) {Try {runtime = runtime.getRuntime (); process prot = rt.exec ("javac" ); Int exitval = proc.exitvalue (); system.out.println ("Process EXITVALUE:" EXITVAL);} catch (throwable t) {t.PrintStackTrace ();}}} a Run of Badexecjavac produduces:
E: / classes / com / javaworld / jpitfalls / article2> java BadExecJavac java.lang.IllegalThreadStateException: process has not exited at java.lang.Win32Process.exitValue (Native Method) at BadExecJavac.main (BadExecJavac.java:13)
If an external process has not yet completed, the exitValue () method will throw an IllegalThreadStateException;. That's why this program failed While the documentation states this fact, why can not this method wait until it can give a valid answer?
A more thorough look at the methods available in the Process class reveals a waitFor () method that does precisely that. In fact, waitFor () also returns the exit value, which means that you would not use exitValue () and waitFor () in conjunction with each other, but rather would choose one or the other. The only possible time you would use exitValue () instead of waitFor () would be when you do not want your program to block waiting on an external process that may never complete . Instead of using the waitFor () method, I would prefer passing a boolean parameter called waitFor into the exitValue () method to determine whether or not the current thread should wait. A boolean would be more beneficial because exitValue () is a more appropriate name for this method, and it is not necessary for two methods to perform the same function under different conditions. Such simple condition discrimination is the domain of an input parameter.Therefore, to avoid this trap, either catch the IllegalThreadStateE Xception or Wait for the process to completion.
NOW, Let's Fix The Problem in Listing 4.1 and Wait for the Process To Complete. In Listing 4.2, The Program Again Attempts To Execute Javaac.exe and the Waits for the External Process To Complete:
Listing 4.2 Badexecjavac2.java
import java.util *;. import java.io. *; public class BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Process proc = rt.exec ( "javac" ); Int exitval = proc.waitfor (); system.out.println ("Process EXITVALUE: EXITVAL);} catch (throwable t) {T.PrintStackTrace ();}}} unfortunately, A Run of Badexecjavac2 Products NO Output. The Program HANGS AND NEVER COMPLETES. Why does the javac process NEVER COMPLETE?
Why runtime.exec () hangs the jdk's javadoc Documentation Provides the answer to this question:
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.
Is this just a case of programmers not reading the documentation, as implied in the oft-quoted advice: read the fine manual (RTFM) The answer is partially yes In this case, reading the Javadoc would get you halfway there; it explains?. That You NEED To Handle The Streams To your External Process, but it does not tell you how.
Another variable is at play here, as is evident by the large number of programmer questions and misconceptions concerning this API in the newsgroups: though Runtime.exec () and the Process APIs seem extremely simple, that simplicity is deceiving because the simple, or obvious , use of the API is prone to error. The lesson here for the API designer is to reserve simple APIs for simple operations. Operations prone to complexities and platform-specific dependencies should reflect the domain accurately. It is possible for an abstraction to be carried too far. The JConfig library provides an example of a more complete API to handle file and process operations (see Resources below for more information) .Now, let's follow the JDK documentation and handle the output of the javac process. When you run javac without Any Arguments, IT Products That Describe How To Run The Program and The Meaning Of All The Available Program Options. Knowing That Is Going to the stderr stream, you can easily write a program to exhaust that stream before waiting for the process to exit. Listing 4.3 completes that task. While this approach will work, it is not a good general solution. Thus, Listing 4.3's program is named MediocreExecJavac ; it provides only a mediocre solution A better solution would empty both the standard error stream and the standard output stream and the best solution would empty these streams simultaneously (I'll demonstrate that later)...
Listing 4.3 Mediocreexecjavac.java
import java.util *;. import java.io. *; public class MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Process proc = rt.exec ( "javac" ); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = new InputStreamReader (stderr); BufferedReader br = new BufferedReader (isr); String line = null; System.out.println ( "
E: / classes / com / javaworld / jpitfalls / article2> java MediocreExecJavac
Thus, to circumvent the second pitfall - hanging forever in Runtime.exec () - if the program you launch produces output or expects input, ensure that you process the input and output streams.Assuming a command is an executable program Under the Windows operating system, many new programmers stumble upon Runtime.exec () when trying to use it for nonexecutable commands like dir and copy Subsequently, they run into Runtime.exec () 's third pitfall Listing 4.4 demonstrates exactly that..:
Listing 4.4 BadexecWindir.Java
Import java.util. *; import java.io. *; public class bagexecwindir {public static void main (string args []) {try {runtime = runtime.Getruntime (); process prot = rt.exec ("DIR" ); InputStream stdin = proc.getInputStream (); InputStreamReader isr = new InputStreamReader (stdin); BufferedReader br = new BufferedReader (isr); String line = null; System.out.println ( "
A Run of BadexecWindir Produces:
E: / classes / com / javaworld / jpitfalls / article2> java BadExecWinDir java.io.IOException: CreateProcess: dir error = 2 at java.lang.Win32Process.create (Native Method) at java.lang.Win32Process
Listing 4.5 GoodwindowsExec.java
import java.util *;. import java.io. *; class StreamGobbler extends Thread {InputStream is; String type; StreamGobbler (InputStream is, String type) {this.is = is; this.type = type;} public void run () {try {InputStreamReader isr = new InputStreamReader (is); BufferedReader br = new BufferedReader (isr); String line = null; while (! (line = br.readLine ()) = null) System.out.println (type "> line);} catch (ioexception} {ooe.printstacktrace ();}}} public class goodwindowsexec {public static void main (string args []) {if (args.length <1) {system. Out.println ("USAGE: Java GoodwindowsExec
"" Cmd [2]); Process proc = rt.exec (cmd); // any error message StreamGobbler errorGobbler = new StreamGobbler (proc.getErrorStream (?), "ERROR");? // any output StreamGobbler outputGobbler = New streamgobbler (proc.getinputstream (), "output"); // kick it is off errorgobbler.start (); outputgobbler.start (); // any error ??? int exitval = proc.WaitFor (); system.out .println ("EXITVALUE:" EXITVAL);} catch (throwable t) {t.printStackTrace ();}}} Running GoodWindowsExec with the Dir Command GeneRates:
E:. / Classes / com / javaworld / jpitfalls / article2> java GoodWindowsExec "dir * .java" Execing cmd.exe / C dir * .java OUTPUT> Volume in drive E has no label OUTPUT> Volume Serial Number is 5C5F-0CC9 OUTPUT> OUTPUT> Directory of E: / classes / com / javaworld / jpitfalls / article2 OUTPUT> OUTPUT> 10/23/00 09: 01p 805 BadExecBrowser.java OUTPUT> 10/22/00 09: 35a 770 BadExecBrowser1.java OUTPUT> 10/24/00 08: 45P 488 Badexecjavac.java Output> 10/24/00 08: 46P 519 Badexecjavac2.java Output> 10/24/00 09: 13P 930 Badexecwindir.java Output> 10/22/00 09: 21A 2,282 Badurlpost.java Output> 10/22/00 09: 20A 2,273 Badurlpost1.java ... (some output omitted for break) Output> 10/12/00 09: 29P 151 Superframe.java Output> 10/24/00 09 : 23P 1,814 TESTEXEC.JAVA OUTPUT> 10/09/00 05: 47P 23,543 TeststringReplace.java Output> 10/12/00 08: 55P 228 TO pLevel.java OUTPUT> 22 File (s) 46,661 bytes OUTPUT> 19,678,420,992 bytes free ExitValue: 0Running GoodWindowsExec with any associated document type will launch the application associated with that document type For example, to launch Microsoft Word to display a Word document (ie. One with a .doc extension, Type:
> Java GoodwindowSexec "YourDoc.doc"
Notice that GoodWindowsExec uses the os.name system property to determine which Windows operating system you are running -.. And thus determine the appropriate command interpreter After executing the command interpreter, handle the standard error and standard input streams with the StreamGobbler class StreamGobbler empties any stream passed into it in a separate thread. The class uses a simple String type to denote the stream it empties when it prints the line just read to the console.Thus, to avoid the third pitfall related to Runtime.exec (), do not assume that a command is an executable program;. know whether you are executing a standalone executable or an interpreted command At the end of this section, I will demonstrate a simple command-line tool that will help you with that analysis.
It is important to note that the method used to obtain a process's output stream is called getInputStream (). The thing to remember is that the API sees things from the perspective of the Java program and not the external process. Therefore, the external program's output .
Runtime.exec () is not a command line One final pitfall to cover with Runtime.exec () is mistakenly assuming that exec () accepts any String that your command line (or shell) accepts. Runtime.exec () is much more limited and not cross-platform. This pitfall is caused by users attempting to use the exec () method to accept a single String as a command line would. The confusion may be due to the fact that command is the parameter name for the exec () method. Thus, the programmer incorrectly associates the parameter command with anything that he or she can type on a command line, instead of associating it with a single program and its arguments. In listing 4.6 below, a user tries to execute a command and redirect ITS Output in One Call To Exec (): Listing 4.6 Badwinredirect.java
import java.util *;. import java.io. *; // StreamGobbler omitted for brevity public class BadWinRedirect {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Process proc = rt .exec ( "java jecho 'Hello World'> test.txt"); // any error message StreamGobbler errorGobbler = new StreamGobbler (proc.getErrorStream (), "eRROR");?? // any output StreamGobbler outputGobbler = new StreamGobbler (Proc.getInputStream (), "output"); // kick the off errorgobbler.start (); outputgobbler.start (); // any error ??? int exitval = proc.waitfor (); system.out.println ("EXITVALUE:" EXITVAL);} catch (throwable t) {t.printStackTrace ();}}} r Unning Badwinredirect Product Produces:
E: / classes / com / javaworld / jpitfalls / article2> java badwinredirect output> 'Hello World'> Test.txt exitValue: 0
The program BadWinRedirect attempted to redirect the output of The jecho program simply takes its command-line arguments and writes them an echo program's simple Java version into the file test.txt. However, we find that the file test.txt does not exist. To the standard output stream. (you will find the source for jecho in the source code available for download in Resources.) in Listing 4.6, the user assumed that you could redirect standard output into a file just as you could on a DOS command line. nevertheless, you do not redirect the output through this approach The incorrect assumption here is that the exec () method acts like a shell interpreter;... it does not Instead, exec () executes a single executable (a program or script) If you want to process the stream to either redirect it or pipe it into another program, you must do so programmatically, using the java.io package. Listing 4.7 properly redirects the standard output stream of the jecho process into a file.Listing 4.7 GoodWin Redirect.java
import java.util *;. import java.io. *; class StreamGobbler extends Thread {InputStream is; String type; OutputStream os; StreamGobbler (InputStream is, String type) {this (is, type, null);} StreamGobbler (InputStream IS, STRING TYPUTSTREAM Redirect) {this.is = IS; this.type = type; this.os = redirect;} public void run () {Try {printwriter PW = NULL; if (OS! = null) PW = new PrintWriter (os); InputStreamReader isr = new InputStreamReader (is); BufferedReader br = new BufferedReader (isr); String line = null;! while (! (line = br.readLine ()) = null) {if (pw = NULL) PW.Println (line); system.out.println (Type > line);} if (pw! = null) PW.Flush ();} catch (ooex ception ioe) {ioe.printStackTrace ();}}} public class GoodWinRedirect {public static void main (String args []) {if (args.length <1) {System.out.println ( "USAGE java GoodWinRedirect
new StreamGobbler (proc.getErrorStream (), "ERROR");? // any output StreamGobbler outputGobbler = new StreamGobbler (proc.getInputStream (), "OUTPUT", fos); // kick them off errorGobbler.start (); outputGobbler .Start (); // any error = proc.waitfor (); system.out.println ("EXITVALUE:" EXITVAL); fos.flush (); fos.close ();} catch Throwable t) {T.PrintStackTrace ();}}} Running Goodwinredirect Product Produces:
E: / classes / com / javaworld / jpitfalls / article2> java goodwinredirect test.txt output> 'Hello World' ExitValue: 0
After running GoodWinRedirect, test.txt does exist. The solution to the pitfall was to simply control the redirection by handling the external process's standard output stream separately from the Runtime.exec () method. We create a separate OutputStream, read in the filename to which we redirect the output, open the file, and write the output that we receive from the spawned process's standard output to the file Listing 4.7 completes that task by adding a new constructor to our StreamGobbler class The new constructor takes three arguments:.. the input stream to gobble, the type String that labels the stream we are gobbling, and the output stream to which we redirect the input. This new version of StreamGobbler does not break any of the code in which it was previously used, as we have not Changed The EXISTING PUBLIC API - We Only Extended IT.
Since the argument to Runtime.exec () is dependent on the operating system, the proper commands to use will vary from one OS to another. So, before finalizing arguments to Runtime.exec () and writing the code, quickly test the arguments. Listing 4.8 is a simple command-line utility that allows you to do just that.Here's a useful exercise:. try to modify TestExec to redirect the standard input or standard output to a file When executing the javac compiler on Windows 95 or Windows 98, .
Listing 4.8 Testexec.java
Import java.util. *; import java.io. *; // class streamgobbler omitted for break scientiociid main (string args []) {ix (args.length <1) {system.out.println ("USAGE: JAVA TESTEXEC /" CMD / "); System.exit (1);} try {string cmd = args [0]; runtime = runtime.getime (); process prot = rt.exec (cmd) ; // any error message StreamGobbler errorGobbler = new StreamGobbler (proc.getErrorStream (), "ERR");?? // any output StreamGobbler outputGobbler = new StreamGobbler (proc.getInputStream (), "OUT"); // kick them OFF ERRORGOBBLER.START (); OUTPUTGOBBLER.START (); // Any Error ??? int exitval = proc.waitfor (); sys Tem.out.println ("EXITVALUE:" EXITVAL); Catch (throwable t) {T.PrintStackTrace ();}}} Running Testexec to launch the netscape browser and load the java help documentation produces:
E: / classes / com / javaworld / jpitfalls / article2> java testexec "e: /java/docs/index.html" java.io.ioException: createProcess: E: /java/docs/index.html error = 193 AT Java .lang.Win32Process.create (Native Method) at java.lang.Win32Process.
THEFORE, WE TRY TEST AGAIN, THIS TIME GIVING IT A FULL PATH TO NETSCAPE (ALTERNATELY, We Could Add Netscape To Our Path Environment Variable.) A Second Run Of Testexec Produces:
E: / classes / com / javaworld / jpitfalls / article2> java testexec "E: / program files / netscape / program / netscape.exe e: /java/docs/index.html" EXITVALUE: 0
THIS WORKED! The netscape browser launches, and it. Loadings the java help documentation.
One additional improvement to TestExec would include a command-line switch to accept input from standard input. You would then use the Process.getOutputStream () method to pass the input to the spawned external program.
To Sum up, FOLLOW THESE Rules of thumb to avoid the pitfalls in runtime.exec ():
You cannot Obtain An Exit Status from An External Process Until It Has EXITED
You Must IMDIATELY HANDLE The Input, Output, And Error Streams from Your Spawned External ProcessYou Must Use runtime.exec () To Execute Program
You cannot USE runtime.exec () Like a command line
Correction to Pitfall 3 In the discussion of Pitfall 3 ( "Do not mix floats and doubles when generating text or XML messages") in my last column, I incorrectly stated that the different string representation of a decimal number after casting it from a float To a Double Was A bug. While this is a pitfall, but the fact what the the decimal number in question - 100.28 and 91.09 - do not represent precisely in binary. I'd like to Thank Thomas Okken and the Others Who Straightened Me Out. If You Enjoy Discussing The Finer Points of Numeric Methods, You Can Email Thomas.
The combination of forgetting my numerical methods class, the numerous bug reports on the bug parade, and the automatic rounding of floats and doubles when printing (but not after casting a float to a double) threw me. I apologize for confusing anyone who read the Article, especially to new java program. I present TWO Better Solutions To The Problem:
The first possible solution is to always specify the desired rounding explicitly with NumberFormat In my case, I use the float and double to represent dollars and cents;. Therefore, I need only two significant digits Listing C3.1 demonstrates how to use the NumberFormat. Class to specify a maximum of two flaw snrough.
Listing C3.1 FormatNumBers.java
import java.text *;. public class FormatNumbers {public static void main (String [] args) {try {NumberFormat fmt = NumberFormat.getInstance (); fmt.setMaximumFractionDigits (2); float f = 100.28f; System.out. Println ("AS A Float:" F); Double D = F; System.out.println ("Cast to A Double:" D); System.out.Println ("Using Numberformat:" FMT.Format d);} catch (throwable t) {T.PrintStackTrace ();}}} When We Run The FormatNumBers Program, IT Products:
E: / Classes / Com / JavaWorld / Jpitfalls / Article2> Java FormatNumBers As A Float: 100.28 Cast to a Double: 100.27999877929689 Using NumberFormat: 100.28
As you can see - regardless of whether we cast the float to a double - when we specify the number of digits we want, it properly rounds to that precision -. Even if the number is infinitely repeating in binary To circumvent this pitfall Control the formatting of your Doubles and floats when converting to a string.
A Second, SIMPLER SOLUTION WOULD BE to NOT USE A Float To Reperest Cents (Number of Pennies) Can Represent Cents, with a legal range of 0 to 99. you can check the range in the mutator method.