Skip to main content

Executing a child process and redirecting output to a JTextArea

Posted in

Seems like Java developers shun the idea of having to do anything out of their cozy virtual machine, so I couldn't find any good code examples for doing this problem: Fork a new JVM from your current JVM and capture it's stdout/stderr stream to a JTextArea. Allow the user to kill the subprocess at any time.

Well, here's my solution:

import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.*;
import java.io.*;
 
public class ForkDemo implements ActionListener {
 
  protected JTextArea output = new JTextArea(10,20);
  protected JButton start = new JButton("START");
  protected JButton stop = new JButton("STOP");
  private ForkWorker forkWorker;
 
  public ForkDemo() {}
 
  public void actionPerformed(ActionEvent e) {
    if (e.getSource()==start) {
      doStart();
    }
    if (e.getSource()==stop) {
      doStop();
    }
  }
 
  private void doStart() {
    StringBuilder sb = new StringBuilder(System.getProperty("java.home"));
    sb.append(File.separator);
    sb.append("bin");
    sb.append(File.separator);
    sb.append("java");
    File jvm = new File(sb.toString());
    if (! jvm.exists()) {
      // Guess which OS we are on...
      sb.append(".exe");
    }
    ProcessBuilder builder = new ProcessBuilder(sb.toString(), "my.classname");
    builder.redirectErrorStream(true);
    forkWorker = new ForkWorker(output,builder);
    forkWorker.execute();
  }
  
  private void doStop() {
    if (forkWorker!=null) {
      forkWorker.cancel(true);
    }
  }
 
  public static void main(String[] args) {
    ForkDemo demo = new ForkDemo();
    demo.start.addActionListener(demo);
    demo.stop.addActionListener(demo);
    JFrame frame = new JFrame();
    JPanel content = new JPanel();
    content.add(new JScrollPane(demo.output));
    content.add(demo.start);
    content.add(demo.stop);
    frame.setContentPane(content);
    frame.pack();
    frame.setVisible(true);
  }
}

And now the actual workhorse:

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.util.*;
 
class ForkWorker extends SwingWorker<String,String> {
  
  private JTextArea output; // Where to redirect STDERR & STDOUT to
  private ProcessBuilder builder;
  
  public ForkWorker(JTextArea output, ProcessBuilder builder) {
    this.output=output;
    this.builder= builder;
  }
  
  protected void process(java.util.List<String> chunks) {
    // Done on the event thread
    Iterator<String> it = chunks.iterator();
    while (it.hasNext()) {
      output.append(it.next());
    }
  }
  
  public String doInBackground() {
    Process process;
    try {
      process = builder.start();
      InputStream res = process.getInputStream();
      byte[] buffer = new byte[100];
      int len;
      while ( (len=res.read(buffer,0,buffer.length))!=-1) {
        publish(new String(buffer,0,len));
        if (isCancelled()) {
          process.destroy();
          return "";
        }
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    return "";  // Don't care
  }
  
  protected void done() {
    // Done on the swing event thread
    output.append("\nALL DONE");  
  }
}