7

UPDATE: I found a crucial part to why this probably isn't working! I used System.setOut(out); where out is a special PrintStream to a JTextArea

This is the code, but the issue I'm having is that the information is only printed out once I end the process.

public Constructor() {
    main();
}

private void main() {
    btnStart.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            try {
                ProcessBuilder builder = new ProcessBuilder("java", textFieldMemory.getText(), "-jar", myJar);
                Process process = builder.start();
                InputStream inputStream = process.getInputStream();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream), 1);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.println(line);
                }
                inputStream.close();
                bufferedReader.close();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    });
} 

Current output:

Line 1
Line 2
Line 3
Line 4
Line 5

This is the correct output, but it is only being printed as one big block when I end the process.

Does anyone know what the issue is? If so can you help explain to me why this is happening, thank-you in advance.

Ciphor
  • 732
  • 4
  • 11
  • 25
  • 2
    The output is been buffered in the `BufferedReader`, try just reading the contents from the `InputStream` directly to see if it makes a difference – MadProgrammer Apr 04 '13 at 01:31
  • 1
    I agree with @MadProgrammer that the problem is probably with the buffer. However, I'd suggest just setting the `BufferedReader` buffer size to 1 with [its two parameter constructor](http://docs.oracle.com/javase/6/docs/api/java/io/BufferedReader.html#BufferedReader%28java.io.Reader,%20int%29): `new BufferedReader(new InputStreamReader(inputStream), 1)` – DaoWen Apr 04 '13 at 01:38
  • Would using a ByteArrayOutputStream work any better? I'm completely new to streams, and this is bugging me XD – Ciphor Apr 04 '13 at 01:45
  • In this situation, I just use a small `byte` array and read directly from the `InputStream`. The main reason I do this is because not all processes send newline characters as part of there output which means sometimes, you don't get any output and the process seems to never die – MadProgrammer Apr 04 '13 at 01:49
  • I doubt your buffered reader is the problem. Two quick questions: are you using `process.waitFor`? And are you reading the stream output on a separate thread? – Perception Apr 04 '13 at 01:50
  • @Perception no I'm not using `process.waitFor` and I haven't created any additional threads. – Ciphor Apr 04 '13 at 01:54
  • @Ciphor - you should do both. If you post a more complete sample of your code (from when you create the process to when you are done with it, including reading the output) I can provide recommendations. – Perception Apr 04 '13 at 01:58
  • @Perception added some more content. – Ciphor Apr 04 '13 at 02:08
  • I found a crucial part to why this probably isn't working! I used `System.setOut(out);` where out is a special `PrintStream` to a `JTextArea` – Ciphor Apr 04 '13 at 02:16
  • @Ciphor I would STRONGLY discourage you from redirecting output from `System.out` to a Swing component without ensuring that the output is correctly re-synced with the EDT – MadProgrammer Apr 04 '13 at 02:58
  • @MadProgrammer The program that starts is not mine so I cannot edit it, and the useful information it prints out is within the console. what is the best way to display this information within my application if it is not a good idea to redirect the output to my text area? – Ciphor Apr 04 '13 at 03:14
  • Ugh I'm way too ambitious for my knowledge as a first year, I'll sleep on this. – Ciphor Apr 04 '13 at 03:17
  • @Ciphor If you have control over the `PrintStream` then you need to ensure that any time it outputs to the text area, it's done within the context of the EDT. Take a look at my updated answer – MadProgrammer Apr 04 '13 at 03:21

2 Answers2

11

Processing the output stream of the process in a separate thread might help here. You also want to explicitly wait for the process to end before continuing with your logic:

ProcessBuilder builder = new ProcessBuilder("java",
        textFieldMemory.getText(), "-jar", myJar);
final Process process = builder.start();
final Thread ioThread = new Thread() {
    @Override
    public void run() {
        try {
            final BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
};
ioThread.start();

process.waitFor();
Perception
  • 79,279
  • 19
  • 185
  • 195
  • 1
    Didn't work for me, like I said, I think it has to do with that I redirected the console output to a JTextArea. – Ciphor Apr 04 '13 at 02:33
  • Yea, thats a pretty important detail. More than likely you have to explicitly flush the stream on each write then trigger a repaint of the JTextArea, in order to get the output to show up in real time. – Perception Apr 04 '13 at 02:35
  • I found something interesting if you look at my original code in the question and moved `inputStream.close();` into the while loop, I successfully get the first line printed in my `JTextArea` and then the stream is obviously closed. – Ciphor Apr 04 '13 at 02:40
  • Well, `close` does a `flush`, so try doing that instead and see how that affects the program. – Perception Apr 04 '13 at 02:42
  • Unfortunately InputStream doesn't have a `flush` method :/ – Ciphor Apr 04 '13 at 02:47
  • Sorry, I misunderstood what you wrote. I meant a flush on your custom output stream, not on the input. – Perception Apr 04 '13 at 02:49
4

Basically, from what little information there is, it sounds like your executing the process and reading the InputStream from within the Event Dispatching Thread.

Anything to blocks the EDT will prevent it from processing repaint requests, instead, you should use something like a SwingWorker which has functionality to allow you to update the UI from with the EDT.

Take a look at Concurrency in Swing for more details

enter image description here

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class PBDemo {

    public static void main(String[] args) throws Exception {
        new PBDemo();
    }

    public PBDemo() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            JTextArea ta = new JTextArea();
            add(new JScrollPane(ta));

            new ProcessWorker(ta).execute();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }
    }

    public interface Consumer {
        public void consume(String value);            
    }

    public class ProcessWorker extends SwingWorker<Integer, String> implements Consumer {

        private JTextArea textArea;

        public ProcessWorker(JTextArea textArea) {
            this.textArea = textArea;
        }

        @Override
        protected void process(List<String> chunks) {
            for (String value : chunks) {
                textArea.append(value);
            }
        }

        @Override
        protected Integer doInBackground() throws Exception {
            // Forced delay to allow the screen to update
            Thread.sleep(5000);
            publish("Starting...\n");
            int exitCode = 0;
            ProcessBuilder pb = new ProcessBuilder("java.exe", "-jar", "HelloWorld.jar");
            pb.directory(new File("C:\\DevWork\\personal\\java\\projects\\wip\\StackOverflow\\HelloWorld\\dist"));
            pb.redirectError();
            try {
                Process pro = pb.start();
                InputConsumer ic = new InputConsumer(pro.getInputStream(), this);
                System.out.println("...Waiting");
                exitCode = pro.waitFor();

                ic.join();

                System.out.println("Process exited with " + exitCode + "\n");

            } catch (Exception e) {
                System.out.println("sorry" + e);
            }
            publish("Process exited with " + exitCode);
            return exitCode;
        }

        @Override
        public void consume(String value) {
            publish(value);
        }
    }

    public static class InputConsumer extends Thread {

        private InputStream is;
        private Consumer consumer;

        public InputConsumer(InputStream is, Consumer consumer) {
            this.is = is;
            this.consumer = consumer;
            start();
        }

        @Override
        public void run() {
            try {
                int in = -1;
                while ((in = is.read()) != -1) {
//                    System.out.print((char) in);
                    consumer.consume(Character.toString((char)in));
                }
            } catch (IOException exp) {
                exp.printStackTrace();
            }
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366