Saturday, June 2, 2012

JDK7: ProcessBuilder and how redirecting input and output from operating system's processes.

The java.lang.ProcessBuilder class has several new methods added into JDK7 that are useful for redirecting the input and output of external processes executed from a Java application. The nested ProcessBuilder.Redirect class has been introduced to provide these additional redirect capabilities.

To demonstrate this process, we are going to send command-line arguments from a text file to a DOS prompt and record the output in another text file.

Getting ready
----------------
In order to control input and output from external processes, you must:
  1. Create a new ProcessBuilder object.
  2. Direct the input and output of the process to the appropriate locations.
  3. Execute the process via the start method.

How to do it
---------------
1. First, create a new console application. Create three new file instances to represent the three files involved in our process execution: input, output, and errors as follows:

2. Create the file Commands.txt using the path specified for the file and enter the following text:
C:
dir
mkdir "Test Directory"
dir
3. Make sure that there is a carriage return after the last line.

4. Next, create a new instance of a ProcessBuilder, passing the string "cmd" to the constructor to specify the external process that we want to launch, which is the operating system command window. Call the redirectInput, redirectOutput, and redirectError methods with no arguments and print out the default locations:

5. Then we want to call the overloaded form of the previous methods, passing the respective file to each one. Once again, call the no argument form of each method executed using the toString method to verify that the IO sources have been changed:

6. Finally, call the start method to execute the process as follows:

7. Run the application. You should see output similar to the following:

8. Examine each of the text files. Your output file should have text similar to this:

9. Execute the program again and examine the contents of your error log. Because your test directory had already been created with the first process execution, you should now see the following error message:

How it works
---------------
We created three files to handle the input and output of our process. When we created the instance of the ProcessBuilder object, we specified the application to launch to be the command window. The information required to perform actions within the application was stored in our input file.

When we first called the redirectInput, redirectOutput, and redirectError methods, we did not pass any arguments. These methods all return a ProcessBuilder. Redirect object, which we printed. This object represents the default IO source, which in all three cases was Redirect.PIPE, one of the ProcessBuilder.Redirect.Type enumerations. A pipe takes the output of one source and sends it to another.

The second form of the methods that we used involved passing a java.io.File instance to the redirectInput, redirectOutput, and redirectError methods. These methods return a ProcessBuilder object as well, but they also have the function of setting the IO source. In our example, we then called the no argument form of each method once more to verify that the IO had been redirected.

The first time the program was executed, your error log should have been empty, assuming you used valid file paths for each File object, and you have write permissions on your computer. The second execution was intended to display how the capture of errors can be directed to a separate file.

If the redirectError method is not invoked, the errors will inherit the standard location and will be displayed in your IDE's output window. See the There's More... section for information about inheriting standard IO locations.

It is important to note that the start method must be called after the redirect methods. Starting the process before redirecting input or output will cause the process to disregard your redirects and the application will execute using the standard IO locations.

There's more
---------------
In this section, we will examine the use of the ProcessBuilder.Redirect class and the inheritIO method.

Using the ProcessBuilder.Redirect class

The ProcessBuilder.Redirect class provides another way to specify how the IO data is redirected. Using the previous example, add a new line prior to calling the start method:
pb.redirectError(Redirect.appendTo(errors));
This form of the redirectError method allows you to specify that the errors should be appended to the error log text file rather than overwritten. If you execute the application with this change, you will see two instances of the error when the process tries to create the Test Directory directory again:
A subdirectory or file Test Directory already exists.
A subdirectory or file Test Directory already exists.
This is an example of using the overloaded form of the redirectError method, passing a ProcessBuilder.Redirect object instead of a file. All three methods, redirectError, redirectInput, and redirectOutput, have this overloaded form.

The ProcessBuilder.Redirect class has two special values, namely, Redirect. PIPE and Redirect.INHERIT. Redirect.PIPE is the default way external process IO is handled, and simply means that the Java process will be connected to the external process via a pipe. The Redirect.INHERIT value means that the external process will have the same input or output location as the current Java process. You can also redirect the input or output of data using the Redirect.to and Redirect.from methods.

Using the inheritIO method to inherit the default IO locations

If you execute an external process from a Java application, you can set the location of the source and destination data to be the same as that of the current Java process. The ProcessBuilder class' inheritIO method is a convenient way to accomplish this. If you have a ProcessBuilder object pb, executing the following code:
pb.inheritIO()
Then it has the same effect as executing the following three statements together:
pb.redirectInput(Redirect.INHERIT)
pb.redirectOutput(Redirect.INHERIT)
pb.redirectError(Redirect.INHERIT)
In both cases, the input, output, and error data will be located in the same places as the current Java process' input, output, and error data.

References:
---------------
1.Class ProcessBuilder.
2.Java 7 New Features (by Richard M. Reese, Jennifer L. Reese)