Introduction
Sometimes, when you depend on the execution of External Processes from your Java code, it can be a real pain to actually get that code running properly.
There are several pitfalls while trying to run external processes:
- Input and Output streams from and to the process aren't handled properly.
- Although the Process is running, your application isn't waiting for it to finish.
- Your application IS waiting for the process to finish by simply halting.
- The process terminated, but you did not receive the result code.
- You simply have no idea how to actually run an External Process!
If any of these problems ever troubled you, read on.
I will use the DOS prompt (under WinXP) as an External Process for this article's demonstration.
The sources are composed as follows:
- Reusable code
dev.exec.util.ExecHelper
dev.exec.util.ExecProcessor
- Demo code
dev.exec.tester.TestApplication
dev.exec.tester.TestFrame
The idea is simple:
TestApplication
creates an instance of TestFrame
.
TestFrame
is displayed to the user.
- The user requests the execution of the Command Processor.
TestFrame
registers itself to the ExecHelper
as an ExecProcessor
.
TestFrame
uses the ExecHelper
to run the Command Processor.
ExecHelper
notifies the TestFrame
about new output text.
- The user uses the
TestFrame
to input commands for the Command Processor.
TestFrames
notifies the ExecHelper
about new user input.
ExecHelper
notifies the Command Processor about new user input.
- While the Command Processor is running, loop through 6 - 10.
- While the program is running, loop through 3 - 11.
- Quit.
The source files are documented (at least the ExecHelper
and ExecProcessor
), and the demo ZIP contains a JBuilder
project and the compiled classes plus a JAR file.
I will now explain how the concept is put to work in the Reusable code.
First off, I declared ExecProcessor
as an interface to handle all the events coming from the External Process.
public interface ExecProcessor {
public void processNewInput(String input);
public void processNewError(String error);
public void processEnded(int exitValue);
}
TestFrame
which implements the ExecProcessor
interface has the following methods:
public void processNewInput(String input) {
updateTextArea(jTextArea1, input);
}
public void processNewError(String error) {
updateTextArea(jTextArea1, error);
}
public processEnded(int exitValue) {
exh = null;
statusBar.setText("Command.exe ended..");
JOptionPane.showMessageDialog(this, "Exit value for Command.exe was ["
+ exitValue + "]", "Command.exe is done!", JOptionPane.INFORMATION_MESSAGE);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
jTextArea1.setText(null);
statusBar.setText("Ready..");
}
As you see, things are kept simple. The methods processNewInput()
and processNewError()
update a TextArea
with the new line of text sent from the Command Processor, while the processEnded()
method notifies the program that the Command Processor has terminated, and displays the exit value sent from the Command Processor.
The TestFrame
itself uses the runCommandActionPerformed()
method to invoke the ExecHelper
's exec()
method thus executing the Command Processor as the following code demonstrates:
void runCommandActionPerformed(ActionEvent e) {
if (exh == null) {
try {
exh = ExecHelper.exec(this, "cmd.exe");
statusBar.setText("Command.exe running..");
} catch (IOException ex) {
processNewError(ex.getMessage());
}
}
All that is really left is to describe how the ExecHelper
performs its magic. First the ExecHelper
is a Runnable
object, which means it has a run()
method which lets it perform its tasks while the application itself keeps running (i.e. multi-threading).
ExecHelper
has three streams to handle, and three Threads to work with. Yes, this version of the code requires that three separate Threads be activated for each Process. There probably is a way to do this with less, but currently I haven't had the time to research it yet.
As a Runnable
object, the ExecHelper
's run()
method is the most interesting:
public void run() {
if (processThread == Thread.currentThread()) {
try {
processEnded(process.waitFor());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} else if (inReadThread == Thread.currentThread()) {
try {
for (int i = 0; i > -1; i = pInputStream.read(inBuffer)) {
processNewInput(new String(inBuffer, 0, i));
}
} catch (IOException ex) {
ex.printStackTrace();
}
} else if (errReadThread == Thread.currentThread()) {
try {
for (int i = 0; i > -1; i = pErrorStream.read(errBuffer)) {
processNewError(new String(errBuffer, 0, i));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
I leave you to look at the source code and find out for yourself how the user input flows from TestFrame
to the Command Processor.
I hope this will be useful to anyone who needs this functionality in Java.
History
- 7th April, 2004: Initial post