How to use ProcessBuilder to continually interact with a CLI program - java

I use a CLI program regularly which is accessed through a docker container. Once I enter the container, I can start using my CLI program in question. The issue I'm having is I want to continue to interact with the same command line instance. Basically I'm trying to create a GUI program that will run "on top" of a CLI program. I just don't know how to keep sending commands to the same CLI instance:
List<String> command = new ArrayList<String>();
command.add("cmd.exe" );
command.add("/c");
command.add("docker-compose up -d");
System.out.println(command);
ProcessBuilder builder = new ProcessBuilder(command);
builder.inheritIO();
Map<String, String> environ = builder.environment();
Process process = builder.start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
command.clear();
command.add("cmd.exe" );
command.add("/c");
command.add("docker ps");
System.out.println(command);
process = builder.start();
is = process.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
while ((line = br.readLine()) != null) {
System.out.println(line);
}
But this isn't working the way I would like it to. Above, you'll see I'm running two commands: docker-compose up -d and then docker ps. But I don't think they are running in the same instance. So if I were to change the directory in the first command, it's not going to remember the directory for the second command.
Also, it seems to be running my commands in reverse order from the order in the code.

Instances of class ProcessBuilder are intended to be short-lived, in my opinion. I don't think creating a new instance each time you want to create a new process wastes memory or other resources - but I'm only guessing.
In any case, to re-use a ProcessBuilder instance in order to execute several processes, you simply use its methods, like command(String...)
I wrote a small Swing app that lets the user enter a [operating system] command and displays that command's output. It's not production ready, but I hope it's enough to get you going.
Note that creating and handling a Process in java code is not simple nor intuitive. The article When Runtime.exec() won't helped me a lot. It is an ancient article, but nonetheless still relevant (again, in my opinion). Simply replace references to class Runtime in the article with class ProcessBuilder since the article was written before ProcessBuilder was added to the JDK.
Here is the code of my app. Please refer to the above-mentioned article in order to understand the ProcessBuilder related code. In order to understand the Swing code, I recommend the tutorial Creating a GUI With JFC/Swing
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class ProcExec implements ActionListener, Runnable {
private static final String CLEAR = "Clear";
private static final String EXIT = "Exit";
private static final String RUN = "Run";
private JTextArea commandOutput;
private JTextArea textArea;
private ProcessBuilder procBuilder;
public ProcExec() {
procBuilder = new ProcessBuilder();
}
public void actionPerformed(ActionEvent actionEvent) {
String actionCommand = actionEvent.getActionCommand();
if (CLEAR.equals(actionCommand)) {
textArea.setText("");
}
else if (EXIT.equals(actionCommand)) {
System.exit(0);
}
else if (RUN.equals(actionCommand)) {
try {
execute();
}
catch (Exception x) {
x.printStackTrace();
}
}
}
public void run() {
createAndDisplayGui();
}
private void createAndDisplayGui() {
JFrame frame = new JFrame("Process Executor");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createTopPanel(), BorderLayout.PAGE_START);
frame.add(createCommandPanel(), BorderLayout.CENTER);
frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JButton createButton(String text, int mnemonic, String tooltip) {
JButton button = new JButton(text);
button.setMnemonic(mnemonic);
button.setToolTipText(tooltip);
button.addActionListener(this);
return button;
}
private JPanel createButtonsPanel() {
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(createButton(RUN, KeyEvent.VK_R, "Run entered command."));
buttonsPanel.add(createButton(CLEAR, KeyEvent.VK_C, "Removes entered command."));
buttonsPanel.add(createButton(EXIT, KeyEvent.VK_X, "Exit application."));
return buttonsPanel;
}
private JSplitPane createCommandPanel() {
textArea = new JTextArea(30, 40);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
JScrollPane cmdScrollPane = new JScrollPane(textArea);
commandOutput = new JTextArea(30, 80);
JScrollPane outputScrollPane = new JScrollPane(commandOutput);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
cmdScrollPane,
outputScrollPane);
return splitPane;
}
private JPanel createTopPanel() {
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
JLabel label = new JLabel("Enter a command...");
topPanel.add(label);
return topPanel;
}
private int execute() throws IOException, InterruptedException {
commandOutput.setText("");
String raw = textArea.getText();
String[] words = raw.split(" ");
String[] command = new String[words.length + 2];
command[0] = "cmd.exe";
command[1] = "/C";
System.arraycopy(words, 0, command, 2, words.length);
procBuilder.command(command);
Process proc = procBuilder.start();
ProcHandler stdout = new ProcHandler(proc.getInputStream());
ProcHandler stderr = new ProcHandler(proc.getErrorStream());
Thread stdoutThread = new Thread(stdout);
stdoutThread.start();
Thread stderrThread = new Thread(stderr);
stderrThread.start();
int status = proc.waitFor();
stderrThread.join();
stdoutThread.join();
return status;
}
private class ProcHandler implements Runnable {
private BufferedReader streamReader;
public ProcHandler(InputStream is) {
InputStreamReader isr = new InputStreamReader(is);
streamReader = new BufferedReader(isr);
}
public void run() {
try {
String line = streamReader.readLine();
while (line != null) {
SwingUtilities.invokeLater(new StreamLine(line));
line = streamReader.readLine();
}
}
catch (Exception x) {
throw new RuntimeException("Stream reading failed.", x);
}
}
}
private class StreamLine implements Runnable {
private final String text;
public StreamLine(String txt) {
text = txt + "\n";
}
public void run() {
ProcExec.this.commandOutput.append(text);
}
}
public static void main(String[] args) {
ProcExec instance = new ProcExec();
EventQueue.invokeLater(instance);
}
}

Related

How to make a String available in other classes

I've managed to make the input into a string which is available within the same class but I want to make it so the input string can be available in different classes. Current class is OpenDetails and I want the string selectedFile to be available in a different class called OpenFileInfo. How would I set it so the result from selectedFile can be stored in either selectedRequirement or make it available in other classes?
I'm new to Java so if someone could help thank you.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class OpenFile
{
String selectedRequirement = "";
public static void main(String a[])
{
JFrame parent = new JFrame();
String selectedFile;
selectedFile = JOptionPane.showInputDialog(parent, "Add a new module");
if(selectedFile.equalsIgnoreCase(selectedFile)){
//Makes the user input case insensitive
}
final JTextArea edit = new JTextArea(60,100);
JButton read = new JButton("Open "+ selectedFile +".txt");
read.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
FileReader reader = new FileReader(selectedFile + ".txt");
BufferedReader br = new BufferedReader(reader);
edit.read( br, null );
br.close();
edit.requestFocus();
}
catch(Exception e2) { System.out.println(e2); }
}
});
JButton write = new JButton("Save "+ selectedFile + ".txt");
write.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
FileWriter writer = new FileWriter(selectedFile + ".txt");
BufferedWriter bw = new BufferedWriter( writer );
edit.write( bw );
bw.close();
edit.setText("");
edit.requestFocus();
}
catch(Exception e2) {}
}
});
System.out.println("Module: " + selectedFile);
JFrame frame = new JFrame("Requirements");
frame.getContentPane().add( new JScrollPane(edit), BorderLayout.NORTH );
frame.getContentPane().add(read, BorderLayout.WEST);
frame.getContentPane().add(write, BorderLayout.EAST);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
As you are running from a static context, you need to define selectedRequirement as static:
private static String selectedRequirement = "";
To make selectedRequirement equal to selectedFile, simply say selectedRequirement = selectedFile; towards the end of the main function (maybe where you print it already).
To make selectedRequirement available to other classes, you need to create a "getter function" in the OpenFIle class (outside of the main function) like:
public String getSelectedRequirement(){
return selectedRequirement;
}
As pointed out in the comments, it would be a good idea for you (or anyone who finds this in the future) to look at some tutorials on getters, setters, and general encapsulation.

Reading URL from a console and printing it in textarea in GUI

I am working on project "Distributed Web Crawler using Java RMI".
The web crawls the pages and displays the URLs on two consoles-- a client one and a server one.
My problem is that I have to read these URLs from the console and display it under a Text Area in a window. I have tried a lot but the URLs are not getting displayed in Text Area.
However they were getting displayed on the console.
My code is:
import java.net.*;
import java.io.*;
import java.util.Date;
import java.io.File;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.Channels;
import java.rmi.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
class UCDemo implements ActionListener {
static String dispServerURL;
JFrame f1;
JPanel p1;
JLabel l1;
JLabel l2;
JTextField t1;
JTextArea t2;
JButton b1;
public void showFrame() {
f1 = new JFrame("Web Crawler");
p1 = new JPanel();
f1.setSize(7000, 7000);
f1.setVisible(true);
f1.setBackground(Color.pink);
f1.getContentPane().add(p1);
l1 = new JLabel("Enter seed URL");
t1 = new JTextField(100);
b1 = new JButton("Start");
l2 = new JLabel("Result");
t2 = new JTextArea(200, 200);
p1.add(l1);
p1.add(t1);
p1.add(b1);
p1.add(l2);
p1.add(t2);
b1.addActionListener(this);
}
public void actionPerformed(ActionEvent ae) {
try {
DispServerIntf dispServerIntf = (DispServerIntf) Naming.lookup(dispServerURL);
if (ae.getSource() == b1) {
String n1, k;
InputStreamReader ir = new InputStreamReader(System.in);
n1 = t1.getText();
int c = 0;
Document document = null;
URL hp = new URL(n1);
URLConnection hpcon = hp.openConnection();
try {
document = Jsoup.parse(hp, 3000);
} catch (IOException e) {
e.printStackTrace();
}
for (Element element : document.getElementsByTag("a")) {
c++;
ReadableByteChannel rbc = Channels.newChannel(hp.openStream());
FileOutputStream fos = new FileOutputStream("te.html");
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
if (c <= 7) {
Document doc_tmp = null;
URL hp_tmp = new URL(element.attr("href"));
t2.setText("" + hp_tmp);
URLConnection hpcontmp = hp_tmp.openConnection();
/**
* try { doc_tmp=Jsoup.parse(hp_tmp,3000); }
* catch(IOException e) {e.printStackTrace(); } for
* (Element ele: doc_tmp.getElementsByTag("a")) {
* System.out.println(ele.attr("href")); }
**/
ReadableByteChannel rbc_tmp = Channels.newChannel(hp_tmp.openStream());
FileOutputStream fos_tmp = new FileOutputStream("te" + c + ".html");
fos_tmp.getChannel().transferFrom(rbc_tmp, 0, Long.MAX_VALUE);
} else {
dispServerIntf.send(element.attr("href"));
}
}
/**
* BufferedReader in=new BufferedReader(new
* InputStreamReader(hpcon.getInputStream())); String inputline;
* while((inputline=in.readLine())!=null)
* System.out.println(inputline); in.close();
**/
}
} catch (Exception e) {
}
}
public static void main(String args[]) {
dispServerURL = "rmi://" + args[0] + "/DispServer";
new UCDemo().showFrame();
}
}
One problem is that you are doing all the work in a loop on the AWT thread (the "event dispatch thread") within the actionPerformed method, and updating the GUI in that loop.
This means that the GUI will not be updated properly (because the thread that updates the GUI is busy doing the HTML parsing work, and does not become free to process GUI events). You probably need to do the main loop in a separate thread, then update the GUI in the AWT thread using invokeLater().
See:
this tutorial, for example,
and the linked introduction to threading in Swing.
The official Swing Java Tutorial on this topic, which assumes you have read...
...the Concurrency lesson
Also, you always set the text to a single value, so you would only see the latest URL, not a list of them:
t2.setText("" + hp_tmp);

JavaFX FileRader read lines gui show counter

I have a little problem.
Here is my code
package email;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.FileChooser;
public class Controller {
#FXML
public static Label daten;
#FXML
public static Button Datei;
#FXML
public TextField Trennzeichen;
static int i = 0;
#FXML
public void Datei(ActionEvent event) throws IOException, InterruptedException {
FileChooser fileChooser = new FileChooser();
File file = fileChooser.showOpenDialog(null);
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt");
fileChooser.getExtensionFilters().add(extFilter);
String[] buffer = new String[9];
String[][] data = new String[2000][9];
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String zeile = reader.readLine();
while (zeile != null) {
if ((zeile.substring(0, 1).equals("I")) || (zeile.substring(0, 1).equals("-"))) {
System.out.println("ha");
} else {
buffer = zeile.split(Trennzeichen.getText());
for (int t = 0; t < buffer.length; t++) {
data[i][t] = buffer[t];
}
++i;
daten.setText("" + i);
}
zeile = reader.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
I have a Label on my GUI, thats have to show me how many lines are in my txt file.
This works fine, but it is not live, the gui update after the work, before is the gui freezy..
How can I fix this in a task?
short again:
I have 1 label
filechooser thats load a txt file
the label have to show how many lines are reading after every line
See the question and my answer here: Display progress bar while compressing video file
JavaFX is a single thread GUI toolkit and if you do a long running task on the GUI Thread, it will freeze. So you should move the file reading to a background task as described in the linked question.
Edit: Sample skeleton solution
public void readFile() {
Label counter = new Label();
ReadFileTask task = new ReadFileTask();
counter.textProperty().bind(task.progressProperty().asString());
new Thread(task).start();
}
public class ReadFileTask extends Task<Void> {
#Override
protected Void call() throws Exception {
File file = new File(pathname); //TODO
long lines = Files.lines(file.toPath()).count();
long line = 0;
BufferedReader reader = new BufferedReader(new FileReader(file));
String zeile = null;
while ((zeile = reader.readLine()) != null) {
//TODO do the work
updateProgress(line++, lines);
}
return null;
}
}

JLabel's text is not changing

EDIT: Huge code re-organisation, old question here: http://pastebin.com/Mbg4dYiY
I have created a basic program that is designed to show the weather in a window using Swing. I am developing using IntelliJ and I have used the UI builder in that. I am attempting to fetch some information from the Weather Underground servers and then make a JLabel called weatherlabel display this information. However, the JLabel doesn't actually change in the window; it just stays as 'Weather Will Go Here'. How do I fix this?
Here is my main.java:
public class main {
public static void main(String[] args) {
System.out.println("Hello World!");
Display d = new Display();
d.getandsetWeather();
}
}
Here is my Display.java:
public class Display {
Display disp = this;
public JPanel myPanel;
public JLabel weatherfield;
private JButton button1;
public void init() {
JFrame frame = new JFrame("Display");
frame.setContentPane(new Display().myPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(480, 234));
frame.pack();
frame.setVisible(true);
}
public void getandsetWeather() {
String editedline = null;
init();
try {
// Construct data
String data = URLEncoder.encode("key1", "UTF-8") + "=" + URLEncoder.encode("value1", "UTF-8");
data += "&" + URLEncoder.encode("key2", "UTF-8") + "=" + URLEncoder.encode("value2", "UTF-8");
// Send data
URL url = new URL("http://api.wunderground.com/api/772a9f2cf6a12db3/geolookup/conditions/q/UK/Chester.json");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
if( line.contains("\"weather\":")) {
System.out.println(line);
editedline = line.replace("\"weather\":\"", "");
editedline = editedline.replace("\",", "");
System.out.println(editedline);
weatherfield.setText(editedline);
}
}
wr.close();
rd.close();
weatherfield.setText(editedline);
System.out.println(weatherfield.getText());
weatherfield.repaint();
weatherfield.revalidate();
} catch (Exception e) {
System.out.println("Error!" + e);
}
}
}
When I run the program, this is printed to the log:
Hello World!
"weather":"Scattered Clouds",
Scattered Clouds
Scattered Clouds
You seem to have a strange understanding of OOP and code-flow
You have to main methods, one of which you are trying to call the other main method. Don't do that. A program should only have one main method. You should never have to this
Display.main(new String[]{});
Why even have this ChangeWeatherLabelText class? There is only one method, that doesn't seem to be needed in it's own class. Your instantiation if Display in that method does nothing to the rest of the program. So you call has no effect on the label.
Instead of 3, put that method in the class that actually has the label and just reference the label field in the method.
Also, GetWeather just seems like a helper class with a helper method. "Helper" class methods are useless if they don't return something.
IMHO, you should restructure your entire program. Some things may work now, but there's a lot of bad practice going on in your code
If I were to write this program, all the code would be in one file. If you insist on them being in separate files, you need to learn how to use constructors and how to pass objects to them. That is how you are going to manipulate objects from other classes. That is unless you understand the MVC model, which may be a little advance at this point for you.
UPDATE with OP update of code
Test this out and be sure to read the comments so you can see what I did. Let me know if you have any question.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class main {
public static void main(String[] args) {
System.out.println("Hello World!");
new Display(); // <-- just instantiate
}
}
class Display {
Display disp = this;
public JPanel myPanel; // <--------- Haven't been initialized
public JLabel weatherfield;
private JButton button1;
public Display() { // you need constructor to call init
init();
}
public void init() {
myPanel = new JPanel(new BorderLayout()); // initialize
weatherfield = new JLabel(" "); // initialize
button1 = new JButton("Button"); // initialize
myPanel.add(weatherfield, BorderLayout.CENTER);
myPanel.add(button1, BorderLayout.SOUTH);
button1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
getandsetWeather(); // <-------- add listener to call getandsetweather
}
});
JFrame frame = new JFrame("Display");
frame.setContentPane(myPanel); // <--------------------- fix 1
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(480, 234));
frame.pack();
frame.setVisible(true);
}
public void getandsetWeather() {
String editedline = null;
init();
try {
// Construct data
String data = URLEncoder.encode("key1", "UTF-8") + "=" + URLEncoder.encode("value1", "UTF-8");
data += "&" + URLEncoder.encode("key2", "UTF-8") + "=" + URLEncoder.encode("value2", "UTF-8");
// Send data
URL url = new URL("http://api.wunderground.com/api/772a9f2cf6a12db3/geolookup/conditions/q/UK/Chester.json");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
if( line.contains("\"weather\":")) {
System.out.println(line);
editedline = line.replace("\"weather\":\"", "");
editedline = editedline.replace("\",", "");
System.out.println(editedline);
weatherfield.setText(editedline);
}
}
wr.close();
rd.close();
weatherfield.setText(editedline);
System.out.println(weatherfield.getText());
weatherfield.repaint();
weatherfield.revalidate();
} catch (Exception e) {
System.out.println("Error!" + e);
}
}
}

Process output only becomes available after the process has finished

I have a Runnable that reads Console output from an externally called exe (see below) and writes it to both a log file and a JTextArea.
But my Runnable doesn't show the Console output in the JTextArea until the exe completely finishes. How do I get it to print Console output as it happens?
Short Concise Code Example below:
//Main
import java.awt.*;
import java.io.IOException;
import javax.swing.*;
public class Example extends JFrame {
private static final long serialVersionUID = 1L;
public static int maxX, maxY;
public static JTextArea ta = new JTextArea(20, 60);//For LOG display window
public static void main(String args[] ) throws IOException
{
new Example();
}
public Example() {
this.setTitle("Example");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//MAIN Panel
final JPanel main = new JPanel();
JButton RunButton = button.run(main);
main.add(RunButton);
Container container = getContentPane();
container.add(main);
this.pack();
this.setVisible(true);
}
}
//Button Action Listener
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
public class button {
public static JButton run( final JPanel parent ) {
JButton RunButton = new JButton();
RunButton.setText("Start!");
RunButton.addActionListener(
new ActionListener()
{
public void actionPerformed( ActionEvent event)
{
try
{
//Set up LOG Display
JDialog dialog = new JDialog((JFrame)null, "Working...");
JPanel temp_panel = new JPanel();
temp_panel.add(new JScrollPane(Example.ta));
dialog.getContentPane().add(temp_panel);
dialog.pack();
dialog.setVisible(true);
//Build the Command
ArrayList<String> command = new ArrayList<String>();
command.add("ping");
command.add("127.0.0.1");
//Start the process
Process p = new ProcessBuilder(command).start();
//Starts LOG display capture in separate thread
SwingUtilities.invokeLater(new execute(p));
//Wait for call to complete
p.waitFor();
}
catch(Exception err)
{
JOptionPane.showMessageDialog( parent, "Error Executing Run!", "Warning", JOptionPane.ERROR_MESSAGE );
}
}//end ActionPerformed
});
return RunButton;
}
}
//Runnable
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class execute implements Runnable {
String line;
Process p;
public execute ( Process process ) {
p = process;
}
public void run() {
try {
//Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));
while ( (line = is.readLine()) != null ) {
Example.ta.append(line + "\n");
}
System.out.flush();
} catch(Exception ex) { ex.printStackTrace(); }
}
}
Maybe it's because you don't respect Swing's threading policy. All accesses to swing components must be done in the event dispatch thread. Your runnable should thus use SwingUtilities.invokeLater to update the text area in the EDT, rather than in your separate thread.
EDIT : as alf mentions in his comment: JTextArea.append is thread-safe, so it's not absolutely needed here. I would still do it, though, because if the append to a text area was replaced or complemented by any other Swing interaction, it wouldn't be thread-safe anymore.
It could also be that the external process doesn't send any newline character, which makes readLine block until one is found or the end of communication is reached.
Just to help the miner - below's a complete minimalistic (left out everything not absolutely necessary) example that indeed works in my context: each line appears in the textArea as read. It's basically using the SwingWorker as suggested by Justin and re-arranged thingies a bit for clarity.
public class ProcessExample {
public static class ProcessWorker extends SwingWorker<Void, String> {
private JTextArea ta;
private List<String> process;
public ProcessWorker(List<String> command, JTextArea ta) {
this.process = command;
this.ta = ta;
}
#Override
protected Void doInBackground() throws Exception {
Process p = new ProcessBuilder(process).start();
// Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
publish(line);
}
is.close();
return null;
}
#Override
protected void process(List<String> chunks) {
for (String string : chunks) {
ta.append(string + "\n");
}
}
}
private void startProcess(JTextArea ta) {
ArrayList<String> command = new ArrayList<String>();
command.add("ping");
command.add("127.0.0.1");
new ProcessWorker(command, ta).execute();
}
private JComponent getContent() {
JPanel main = new JPanel(new BorderLayout());
final JTextArea ta = new JTextArea(20, 60);
main.add(new JScrollPane(ta));
Action action = new AbstractAction("Start!") {
#Override
public void actionPerformed(ActionEvent e) {
startProcess(ta);
}
};
main.add(new JButton(action), BorderLayout.SOUTH);
return main;
}
public static void main(String args[]) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ProcessExample().getContent());
frame.pack();
frame.setVisible(true);
}
});
}
}
You could try the same logic with a SwingWorker instead. You can extend this class, instead of implementing runnable. It could take your Text area as a paramater, and you can publish the data, without having to deal with the SwingUtils.invokeLater, which is guiltily easier...
Try:
public class execute extends javax.swing.SwingWorker {
String line;
Process p;
JTextArea jta;
File f = new File( properties.prop.getProperty( "LOG_FILE_DIR" ) + "\\PartGen.log");
public execute ( Process process , JTextArea jta ) {
p = process;
this.jta = jta;
}
//implements a method in the swingworker
public void doInBackground() throws Exception {
//Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));
while ( (line = is.readLine()) != null ) {
osfile.writeline(line, f);
publish(new String(line + "\n"));
}
System.out.flush();
return null;
}
//This will happen on the UI Thread.
public void process(List lines){
for(Object o : lines){
jta.append((String)o);
}
}
public void done(){
try{
get();
//You will get here if everything was OK. So show a popup or something to signal done.
}catch(Exception ex){
//this is where your IO Exception will surface, should you have one.
}
}
}
Also, in your calling code, which I assume is in your ui somewhere:
Process p = new ProcessBuilder(command).start();
execute ex = new execute( p , yourTextArea);
ex.execute();
I didnt attempt to compile this, so you may have to check against the API, but hopefully it will give you a gist of what to do.
The problem was not that the thread wasn't capturing the data, it was the JTextArea just not refreshing. repaint(), revalidate(), and updateUI() did not refresh the JTextArea, but the following did:
Example.ta.update(Example.ta.getGraphics());
The problem in this case is with the waitFor:
p.waitFor();
This causes the Button Action Listener to wait on that point until the process is completed.

Categories