I have several little applications which uses the standard console for retrieving user input and for showing messages troughtout System.in and System.out.
Now i would like to realize some Swing based class which called from these applications, it shows a frame with 2 text area, one for input (so associated to System.in) and another one (not editable) that shows the messages (hence associated to System.out). Actually i have implemented all, (actually creating a simple swing based gui and launching it from the event dispatcher thread is not so complex, the same for exporting all as a jar and including it as a library in the original project). The only problem I have so far, which took me here is about the swapping of the standard System.in and System.out to some custom ones which are associated with the 2 JTextArea. Actually checking some solutions online, I ended up with this few lines of code:
I use 2 PipedInputStream and a PrintWriter:
private final PipedInputStream inPipe = new PipedInputStream();
private final PipedInputStream outPipe = new PipedInputStream();
private PrintWriter inWriter;
then i swap the streams
System.setIn(inPipe);
System.setOut(new PrintStream(new PipedOutputStream(outPipe), true));
inWriter = new PrintWriter(new PipedOutputStream(inPipe), true);
for retrieving the data from the outPipe, I use a SwingWorker whose doInBackgroud method troughtout a Scanner reads the lines from the outPipe and publish them in order to append these string of lines in a not editable JTextArea.
Meanwhile a keyListener checks for VK_ENTER click in order to get the text from the JTextField used as prompt, once this happens, the text is displayed using the System.out itself, and it effectively appears in the previous JTextArea, hence the SwingWorker described above works, and then I wrote the same line of text in the inWriter (the PrintStream object associated to the pipe related to the System.in) so the line should be available to be read from Reader objects which are present in the original application.
Unfortunately this is the only part of the code which does not work. Indeed, once i launch the new gui console, then change the streams, the original application will show only the text it prints on System.out, but when it wants to read the text the user writes, for instance troughtout either a BufferedReader or a Scanner object, nothing happens, as if the the in stream was empty.
I think this is due to the Scanner in the SwingWorker doInBackground method since when it reads the next line on the outPipe, it cleans the stream itself too. Any idea to get rid of this problem? I know i could write new methods for handling input and output but i would like to keep this not-intrusive approach, so without editing the original code, a part the creation of the gui Console object in the original application main method. Thanks in advance.
Update 1
This is the Console class, all is done here
public class Console extends JFrame implements KeyListener
{
private JTextField prompt;
private JTextArea log;
private final PipedInputStream inPipe = new PipedInputStream();
private final PipedInputStream outPipe = new PipedInputStream();
private PrintWriter inWriter;
public Console(String title)
{
super(title);
System.setIn(inPipe);
try
{
System.setOut(new PrintStream(new PipedOutputStream(outPipe), true));
inWriter = new PrintWriter(new PipedOutputStream(inPipe), true);
}
catch(IOException e)
{
System.out.println("Error: " + e);
return;
}
JPanel p = new JPanel();
p.setLayout(null);
log = new JTextArea();
log.setEditable(false);
log.setBounds(10, 10, 345, 250);
p.add(log);
prompt = new JTextField();
prompt.setBounds(10, 270, 356, 80);
prompt.addKeyListener(this);
p.add(prompt);
getContentPane().add(p);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setSize(392, 400);
setLocationRelativeTo(null);
(new SwingWorker<Void, String>()
{
protected Void doInBackground() throws Exception
{
Scanner s = new Scanner(outPipe);
while (s.hasNextLine())
{
String line = s.nextLine();
publish(line);
}
return null;
}
#Override
protected void process(java.util.List<String> chunks)
{
for (String line : chunks)
{
if (line.length() < 1)
continue;
log.append(line.trim() + "\n");
}
}
}).execute();
}
public void execute()
{
String text = prompt.getText();
prompt.setText("");
System.out.println(text);
inWriter.print(text.trim().replaceAll("\r\n", ""));
}
#Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER)
execute();
}
#Override
public void keyReleased(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER)
execute();
}
#Override
public void keyTyped(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER)
execute();
}
// this is the method called from the original application
public static void setConsole(final String title)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new Console(title);
//System.out.println("somewhat");
}
});
}
}
I found out the solution by myself, actually all the code posted in the 1st post is almost correct, the problem is with the reading from the System.in the original program, since it was done with a Scanner object (but i tested it with a BufferedReader too) and it seems it's something related to the string termination because if i read by using a simple System.in.read() call, i got the right data, of course char by char. Since both Scanner methods and read one are I/O blocking, I think it's something related to the termination of the string. Because when i wrote it on the InputStream, i cleared the string from any LF and CF char, as you can see in the above code, so when println is invoked only a \r\n should be appended. But reading the result char by char I see \r\n\r\n at the end, so i guess it should be something related to the ending of the string, even if i couldnt figured out the right way to handle it. I will keep you informated by updates tought, but however if you have suggestions and/or feedbacks, they will be obviously welcome.
Related
I created my own little logger to use instead of System.out.println
LogManager.getLogManager().reset();
logger = Logger.getLogger(this.toString());
logger.setLevel(loglevel);
Formatter formatter = new Formatter() {
public String format(LogRecord record) {
return new Date().toString().substring(11, 20) + record.getLevel()+" " + formatMessage(record) + System.getProperty("line.separator");
}
};
logger.addHandler(new StreamHandler(System.out, formatter));
LogManager.getLogManager().addLogger(logger);
At the moment, the messages don't get flushed, so they only appear once the application gets terminated. Is there a way to flush a message after printing it out without creating a new class or adding many lines of code? I want to keep this code as short as possible...
The problem is that the StreamHandler.setOutputStream wraps the given stream in a OutputStreamWriter which according to the javadocs:
The resulting bytes are accumulated in a buffer before being written to the underlying output stream. The size of this buffer may be specified, but by default it is large enough for most purposes.
So there is no way around calling StreamHandler.flush to force those bytes to the console.
Since you don't want to create a new class you can use the ConsoleHandler which will flush to console but by default will write to error stream. You can get around that if you haven't started any other threads by doing the following:
//Global resource so ensure single thread.
final PrintStream err = System.err;
System.setErr(System.out);
try {
ConsoleHandler ch = new ConsoleHandler();
ch.setFormatter(formatter);
logger.addHandler(ch);
} finally {
System.setErr(err);
}
A subclass really is your best bet because you can end up accidentally closing the System.out by calling Handler.close() through LogManager.getLogManager().reset() or by calling StreamHandler.setOutputStream which means you won't see any output at all.
public class OutConsoleHandler extends StreamHandler {
public OutConsoleHandler(OutputStream out, Formatter f) {
super(out, f);
}
#Override
public synchronized void publish(LogRecord record) {
super.publish(record);
flush();
}
#Override
public void close() throws SecurityException {
flush();
}
}
I'm using the Eclipse IDE, and I'm trying to call showSaveDialog(null) from another method which is in turn called from main, but nothing happens when I call it.
Main:
public static void main(String[] args) {
Main main = new Main();
main.pt = main.new PlayThread();
main.generateSound(getSamples(2), 500);
main.play(main.sound, 1);
if(new Scanner(System.in).nextLine().equals("save")){
System.out.println(main.save());
}
}
All the code before calling main.save() works fine, and main.save() starts like this:
public boolean save(){
System.out.println("Calling save");
try{
String saveName = getSaveTo("C:/");
System.out.println("I'm here now");
while getSaveTo() looks like:
public String getSaveTo(String def){
JFileChooser chooser = new JFileChooser();
System.out.println("Save called");
//chooser.setCurrentDirectory(new File(def));
int resp = chooser.showSaveDialog(null);
if(resp == JFileChooser.APPROVE_OPTION){
return chooser.getSelectedFile() + ".wav";
}else return null;
}
After it executes the first few functions in main, I type in 'save', and the console prints:
Calling save
Save called
But the dialog never opens, and it never says "I'm here now." Why's that? Also, when I typed
new JFileChooser().showSaveDialog(null)
That shows up fine, but in my save() method, it won't. Any ideas why?
EDIT:
Here's a condensed program which has the same problem:
package com.funguscow;
import java.util.Scanner;
import javax.swing.JFileChooser;
import com.funguscow.Main.PlayThread;
public class Test {
public static void main(String[] args) {
Test main = new Test();
Scanner scan = new Scanner(System.in);
boolean should = scan.nextLine().equals("save");
scan.close();
if(should){
System.out.println(main.save());
}
}
public String getSaveTo(String def){
JFileChooser chooser = new JFileChooser();
System.out.println("Save called");
//chooser.setCurrentDirectory(new File(def));
int resp = chooser.showSaveDialog(null);
System.out.println("Save called");
if(resp == JFileChooser.APPROVE_OPTION){
return chooser.getSelectedFile() + ".wav";
}else return null;
}
public boolean save(){
System.out.println("Calling save");
try{
String saveName = getSaveTo("C:/");
System.out.println("I'm here now");
if(saveName == null)return false;
}catch(Exception e){}
return false;
}
}
You can run this yourself if you'd like to examine it more.
This MCVE may be enough to reproduce the problem, and it seems that the Scanner that is initialized with System.in is interfering with the JFileChooser's ability to display an open file dialog, even when care is taken so that the file chooser is run on the Swing event thread:
import java.util.Scanner;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
public class Test3 {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("Enter something and press Enter: ");
scan.nextLine();
scan.close();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFileChooser fileChooser = new JFileChooser();
int result = fileChooser.showOpenDialog(null);
}
});
// JFileChooser fileChooser = new JFileChooser();
// int result = fileChooser.showOpenDialog(null);
}
}
On Windows, Scanner interferes w/ JFileChooser -- why?
tl;dr strictly speaking, it's the user that interferes with JFileChooser by using a Scanner expecting input from system console (System.in). Either way, it's just about the windowing focus and Java's Dialog modality.
Explanation
The bug happens because the dialog appears in the background because requiring nextLine() on the Scanner on System.in essentially forces the user to switch the focus to the console. The application loses the focus, so the Dialog appears in the background. The code doesn't "hang" by itself, it just waits until the user selects a file or does any other dialog option - until he does, it does nothing. If there's some kind of OS issue blocking the background window from displaying/working properly (e.g. some "always in foreground" window obstructing it) - well, then your app is hanging around, waiting for input event that's not likely to happen due to the fact nobody can use the input dialog itself.
For anybody affected - the Dialog is there. I've tested this code on Java 8 on Windows XP, Java 8 on Windows 7 and some other configurations - in all of the cases the Dialog was created, possibly hidden in the background of the desktop due to focus loss. It's not shown in the taskbar, but it does show in the Alt+Tab list. Try running it outside of IDE, they tend to do strange things with app focus when console input is required (due to "real" console getting redirected to IDE output window etc). Feeding it data through the < pipe instead of using "real" console will propably prevent this behaviour from happening too.
As far as my imagination goes, workarounds would require either forcing the focus to the app's Frame or creating an on-the-fly always-on-top Frame to keep the focus where it should be (on the Dialog). Since the Dialog of JFileChooser (or any other similar Dialog to speak of) is itself a fire-and-forget object, it's kind of difficult to force it to foreground without specifying a parent. Therefore, I think that the easiest way to go, for parent-less Dialogs is to just use code like:
Exemplary solution
Scanner scan = new Scanner( System.in );
System.out.print( "Enter something and press Enter: " );
scan.nextLine();
scan.close();
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JFrame jf = new JFrame( "Dialog" ); // added
jf.setAlwaysOnTop( true ); // added
JFileChooser fileChooser = new JFileChooser();
int result = fileChooser.showOpenDialog( jf ); // changed
//System.out.print( "result: " + result );
jf.dispose(); // added
}
} );
or, if you prefer to interfere with the Dialog itself, by subclassing it and applying the aforementioned setAlwaysOnTop(true) in the createDialog() call (note that createDialog() has protected access in JFileChooser, making it impossible to change the behaviour without extending the class, similarly to all the Dialog related stuff there), e.g.
int result = new JFileChooser() {
#Override
protected JDialog createDialog( Component parent ) throws HeadlessException {
JDialog jDialog = super.createDialog( parent );
jDialog.setAlwaysOnTop( true );
return jDialog;
}
}.showOpenDialog( null );
or even by creating a regular utility class extending JFileChooser doing just that.
NB switching to/fro EDT does nothing here, as this is not threading related by itself (though the multithreading of UI is the source of the problem, kind of).
I believe the answer by vaxquis to be quite correct in that the chooser is being shown, just behind the current window as it retains focus. It's all to do with Dialog modality and which windows take precedence over others on the various OS's (possibly in combination with some IDE's).
I have found the following workaround - keeping all your other code exactly the same, extend JFileChooser and then use MyFileChooser instead of JFileChooser in the getSaveTo() method. I've tested on my setup (Windows 8, Java 7). YMMV
class MyFileChooser extends JFileChooser {
protected JDialog createDialog(Component parent) throws HeadlessException {
JDialog dialog = super.createDialog(parent);
dialog.setAlwaysOnTop(true);
return dialog;
}
}
This allows you to use the original construction (.openSaveDialog(null)) and has the nice added advantage that you can set up other features of the dialog box in your overridden createDialog() method should you wish.
EDIT
I see vaxquis has appended this method to their answer now too, so that answer is, I would say, canonical.
I've been making a program that reads from a file, identifies common "posts" in the file, and makes a summary of these. My problem is that the GUI-event that allows the user to specify the name and search-term of the post, does not interrupt the running of the program, like I want it to.
I can make it stop, but then the GUI will not be correctly displayed. I have tried some solutions, which will be specified at the bottom of the post.
EDIT: removed codedump and added something resembeling an SSCCE:
class SSCCE{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
new Gui();
}
});
}
}
class Gui implements ActionListener{
boolean runn=true;
JFrame wind2;
JTextField nameF, searchtermF;
JButton done;
Gui(){
runEx();
}
public void runEx(){
int i =0;
while(runn){
if(i==10) break;
System.out.println("Open window and record information given! One at the time!!!");
System.out.println(" ");
giveName("test");
i++;
}
}
public void giveName(String s){
JLabel nameL = new JLabel("Give this post a name:");
JLabel searchL = new JLabel("What do you want the searchterm to be?");
wind2 = new JFrame("EazyMoney");
wind2.setLayout(new BorderLayout());
JPanel all = new JPanel();
all.setLayout(new GridLayout(2,2));
searchtermF = new JTextField(30);
nameF=new JTextField(30);
all.add(nameL);
all.add(nameF);
all.add(searchL);
all.add(searchtermF);
done = new JButton("Press when you have filled in the information!");
done.addActionListener(this);
String prn = "The post in question: " + s;
JLabel header = new JLabel(prn);
wind2.add(header, BorderLayout.NORTH);
all.setVisible(true);
wind2.add(all, BorderLayout.CENTER);
wind2.add(done, BorderLayout.SOUTH);
wind2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
wind2.pack();
wind2.setLocationRelativeTo(null);
wind2.validate();
wind2.setVisible(true);
}
public void actionPerformed(ActionEvent e){
System.out.println("Action recorded, new window can now be shown. All information stored.");
System.out.println(" ");
}
}
The solutions I have tried is:
A simple block, that does a while(true){} and sets the variable to true after the first instance of g.giveName() have been called. I used the ActionListener to call a method that then changed the variable to false again, when the necessary input was given. This resulted in a gray box, with nothing in it.
Making a cyclic barrier that did the same as the block above. Used a separate thread to call g.giveName() and then call the await() from the action listener. Same result as above.
Making readFile be run by a separate thread and call invokeAndWait() on the g.giveName() function. Gave cannot call invokeAndWait() from the EDT-thread, even though it was run from a new thread.
I can not give examples of the code used in instances above, as I have tried a lot of different solutions and do not have it any more. Please take into account that it might have been implemented wrong, and thus might be a valid answer to my question, even though I could not seem to get it to work!
Final note: all work can be found here, if you wish to test the code:
https://github.com/Robiq/EazyMoneyWork
The way to avoid blocking the EDT if you need to execute something else on the same thread it is to temporarily create a new event queue. Here is some example code. In this case it blocks the current thread waiting for some other event to be signalled but you could replace this with whichever long running process is required.
First check if you are running on the EDT: SwingUtilities.isEventDispatchThread. Then if you are:
EventQueue tempEventQueue = new EventQueue();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(tempEventQueue);
try {
wait();
} catch (InterruptedException ex) {
// stop waiting on interrupt
} finally {
tempEventQueue.pop();
}
Something similar to this is how modal dialogs work in Swing. However in general it's not good practice. Much better is to understand which events to listen for to perform specific actions. In your case the user event should not 'stop' your program - it should disable inappropriate components until the user has responded and then re-enable them.
There are two windows: a GUI for user input and Output window for list of filenames found. Execution must be user-stoppable via a keypress and must leave both windows open because the program processes subdirectories, so it can run a long time, possibly stepping thru 100_000 files, either producing tons of output or none at all, depending on how user's filename pattern matches files encountered in the selected starting node.
Here's my question:
How do I look for a keypress (e.g., ESC or CTRL-C) to allow user to terminate? (Clicking red X isn't an option since that closes windows; user needs to see what's been found before termination. Doing so does not close either window anyway since all buttons are disabled once tree walk begins.)
I've tried putting keyListeners in several places, but once the "Start" button is clicked, all the swing components are disabled.
This seems like such a common situation that I'm surprised I can't find any textbook, thread, or Google info that directly answers the question. So I'm afraid it's not gonna be at all easy. That would be no surprise. I may have found a clue here but I can't get it to compile and the link contained there doesn't lead to that code snippet.
The search begins when the Search button is clicked:
private void jbSearchActionPerformed(ActionEvent evt) {
SearchyGUI.doIt();
}
The doIt() method walks the directory tree by an extension of SimplefileVisitor:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> {
...
}
public static void doIt(){
try {
visitor = new OverriddenFileVisitor();
info.setVisible(true);
Files.walkFileTree(SearchyGUI.p , visitor);
}
catch (Exception e) { }
}
}
Output is written to jTextArea1 via the report() method:
public static void report(String s){
Output.jTextArea1.append(s + "\n");
}
This is done primarily in the visitFile() method of SimpleFileVisitor:
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
report(foundkt + "--" + f.getFileName().toString());
return FileVisitResult.CONTINUE;
}
Here's the main class:
public class SearchyGUI {
static Output info;
static Path p ;
static FileVisitor visitor ;
static GUI gui
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
gui = new GUI();
gui.setVisible(true);
}
});
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
info = new Output();
}
});
}
The problem is you are hogging the GUI thread, so the GUI thread can't process any events originating from the user.
You need to create a new Thread and do the work in there. Then, to display output from that thread, you can use SwingUtilities.invokeLater or something like that.
The Key Bindings API is probably the best choice for monitoring key strokes.
I would also add a [Cancel] button to the UI, which shared the same action...
public class CancelAction extends AbstractAction {
public CancelAction() {
putValue(NAME, "Cancel");
}
public void actionPerformed(ActionEvent evt) {
// Perform the cancel operation...
}
}
Then some where else in your code...
CancelAction cancelAction = new CancelAction();
JButton cancelButton = new JButton(cancelAction);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
am.put("Cancel", am);
Now the other problem you're going to have is the fact that you look like you are running a long running task within the context of the Event Dispatching Thread. This is going to prevent your program from being able to update the UI or allow the user to interact with the UI.
If you need to make changes to the UI (ie, show the output of the file processing), you should try a SwingWorker.
The main reason being is that it allows you to execute the long running task in another thread, but provides the mechanism for re-syncing updates back to the EDT, where it is safe to make changes to the UI.
Take a look at Concurrency in Swing for more details.
Regardless of which direction you take, you're going to need to supply a reference to the object that is carrying out the task and provide some kind of "cancel" flag, which the "task" object will need to monitor
The way I had left this program last night was unsatisfactory since Exit resulted in user not being able to see the output so far displayed (it could be useful). So I established window listeners and used the close event to set a boolean aborted to true to prevent further output to the window, but the thread kept running, which led to intermittent problems if another search was started before the thread ended.
Here's how I fixed it.
The FileVisitor interface has 4 methods to implement to walk the tree--two for each file visited, two for each directory. Each returns a FileVisitResult which is normally FileVisitResult.CONTINUE. By changing the return value to FileVisitResult.TERMINATE in the file visitor thread, it terminates appropriately! That is, I set a flag that the thread could check and take appropriate action, which is exactly what #MadProgrammer suggested.
public static FileVisitResult disposition = FileVisitResult.CONTINUE;
...
private static void report(String s){
if (! aborted)
try{
Output.jTextArea1.append(s + "\n");
}
catch (Exception e){
aborted = true ;
disposition = FileVisitResult.TERMINATE;
}
}
...
#Override
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
f1 = new File(f.getParent().toString() + "\\" + f.getFileName().toString());
long filesize = f1.length();
report(f.getFileName().toString() + "\t found in " + f.getParent().toString());
return disposition;
}
I am one happy camper! Thank you BOTH for your ideas and input.
Well, I made it stop. I guess if you wander the woods long enough you'll find a gnome. I read Robin's hint last week and sort of gave up. Then I read some more and more. And then more. But Robin assured me that gnomes DO exist in these here woods!
The code I used was a modification of some I found for a MatLab/Java app. (Why'd I even look at it?? Best apparent Google hint.)
I made the "file visitor" (directory tree walker component) startable as a thread as Robin advised:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> implements Runnable{
// ................................................................^^^^^^^^^^^^^^^^^^^
In doIt() I made a couple of changes, moving the lines that process the directory to the now-runnable class and started the file visitor as its own thread in doIt():
public static void doIt(){
try {
new OverriddenFileVisitor().startTh();
//^^^^^^^^^^
//(moved) Files.walkFileTree(SearchyGUI.p , visitor);
...
I added the new method in the previous line to OverriddenFileVisitor class: (This is the main part of the MatLab/Java code that made sense to me so I used and modified it.)
public void startTh() {
Thread t = new Thread(this);
t.start();
}
And I inserted the overridden run() method for the class:
public void run() {
try {
Files.walkFileTree(SearchyGUI.p , this); // Used to be in doIt().
}
catch (IOException ex) { }
}
It ran and gave correct results and stopped when I hit Exit button, which "became" enabled after revising the file visitor to run in its own thread, which is what #Robin Green was saying. I almost feel like I know what I've done.
P.S. Note that I already was able to get my output via invokeLater()--last several lines of original question.
It's not finished but it's much more satisfactory.
Context: I am reading data from a serial port at 115.2 Kbaud. The read data is printed using a PrintWriter that I then have appending to a JTextArea.
Everything works well, but the text in the JTextArea does not appear until the method sending the stream from the serial port to my PrintWriter finishes. I'd like it to display closer to real-time, as I will at times be receiving upwards of 20-30 MB of text at a time, and how the general flow of text changes as the program executes would be valuable.
I am using the PrintWriter to JTextArea method here. I think the solution probably has to do with Threads and PipedWriter/PipedReader, but every attempt I've made to implement that has failed miserably.
Thank you for your help.
//code calling method; VerifierReader does not inherit from Reader
//or any such class. it's wholly homegrown. I send it the PrintWriter
//as out, telling it to output there
verifierInstance=new VerifierReader("COM3", verifierOutputLocString.getText());
verifierInstance.setSysOutWriter(out);
verifierInstance.readVerifierStream();
// and the relevant code from VerifierReader
public void setSysOutWriter (PrintWriter outWriter) {
sysOutWriter=new PrintWriter(outWriter);
}
public void readVerifierStream() throws SerialPortException,
InterruptedException{
try{
sysOutWriter.println("Listening for verifier...");
//sysOutWriter.flush();
verifierPort.addEventListener(new verifierListener());
lastReadTimer=System.currentTimeMillis();
while(verifierPort.isOpened()) {
Thread.sleep(1000);
//System.out.println(timeOut);
if( ((long)(System.currentTimeMillis()-lastReadTimer))>timeOut){
sysOutWriter.println("Finished");
verifierPort.removeEventListener();
verifierPort.closePort();
}
}
}
finally {
if (verifierPort.isOpened()) {
verifierPort.closePort();
}
bfrFile.close();
}
}
private class verifierListener implements SerialPortEventListener{
String outBuffer;
public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR()){//If data is available
timeOut=200;
lastReadTimer=System.currentTimeMillis();
if(event.getEventValue() > 0){//Check bytes count in the input buffer
try {
byte[] buffer = verifierPort.readBytes(event.getEventValue());
outBuffer=new String(buffer);
bfrFile.print(outBuffer);
sysOutWriter.print(outBuffer);
//bfrFile.flush();
//sysOutWriter.flush();
}
catch (SerialPortException ex) {
sysOutWriter.println(ex);
}
}
}
}
}
Edit:
I've attempted what was recommended below, and have made the following changes:
private class VerifierTask extends SwingWorker<Void, String> {
public VerifierTask() throws IOException, SerialPortException, InterruptedException{
verifierInstance= new VerifierReader(streamReader);
verifierInstance.setReaderIO("COM3", verifierOutputLocString.getText());
verifierInstance.readVerifierStream();
}
#Override
protected Void doInBackground() throws IOException{
int charItem;
char[] charBuff = new char[10];
String passString;
while ((charItem = streamReader.read(charBuff, 0, 10)) !=-1) {
passString = new String(charBuff);
publish(passString);
}
return null;
}
#Override
protected void process(List<String> outList) {
for (String output : outList) {
outputArea.append(output);
}
}
}
was added, and I changed my button to immediately invoke a new instance of the VerifierTask class, in addition to making VerifierReader implement a PipedWriter for output (with all of that being Strings).
I'm not sure what I'm doing wrong here. When this code is executed the Java process just freezes indefinitely.
Am I assuming correctly that a VerifierReader created in any VerifierTask thread is tied to that thread, and thus my thread.sleep and while(true) statements no longer pose a problem?
Don't call Thread.sleep or do while (true) on the main Swing event thread, the EDT. Ever. Instead do this sort of thing in a background thread such as one provided via a SwingWorker. You would use the publish/process method pair to get intermediate results to your JTextArea.
For more on this, please check out the tutorial: Concurrency in Swing.