I have a threading problem of some kind outputting to a JTextArea. I have a BufferedWriter set up to write to this text area, but for some reason it is not writing to this area until the current function is done. For example, I have this code that checks a list of URLs to see if they are good or bad:
for( final String s : urls ){
String sContent = IO.getURLContent( s );
if( sContent == null ){
writeLine( "Error " + s );
} else {
writeLine( "Worked " + s );
}
}
void writeLine( String sLineText ) throws java.io.IOException {
mbwriter.write(sLineText);
mbwriter.newLine();
mbwriter.flush();
}
The output is only occurring AFTER the loop is done, even though the writer is being flushed after every URL is checked. I tried putting the IO operation in a separate thread like this:
for( final String s : urls ){
SwingUtilities.invokeLater( new Runnable() {
public void run(){
String sContent = IO.getURLContent( s );
if( sContent == null ){
writeLine( "Error " + s );
} else {
writeLine( "Worked " + s );
}
}
}
} );
}
But it makes no difference, the output is still blocked until the entire loop is complete.
Note that the ENTIRE Swing interface is hung during this operation. You can't click on anything, even the window closer. The interface is completely frozen.
This would be expected.
Swing is a single threaded environment, that is, if you block the Event Dispatching Thread with long running process or block method calls, it will be unable to process new events posted to the Event Queue, effectively making your UI "hang"
It sounds like your first example is running within the context of the EDT and your second example is still running within the context of the EDT, but is schedule an update at some time in the future, after the EDT is able to, once again, process the Event Queue...
invokeLater is placing an event on the Event Queue for the EDT to process "later"
Consider using a SwingWorker instead, it provides the ability to run code in the background but provides methods that allow you to synchronise the updates to the EDT via it's publish and process methods
See Concurrency in Swing and
Worker Threads and SwingWorker for more details
As an example...
public class URLWorker extends SwingWorker<List<String>, String> {
private List<String> urls;
private BufferedWriter bw;
public URLWorker(List<String> urls, BufferedWriter bw) {
this.urls = urls;
this.bw = bw;
}
#Override
protected void process(List<String> chunks) {
for (String result : chunks) {
try {
bw.write(result);
bw.newLine();
} catch (IOException exp) {
firePropertyChange("error", null, ex);
}
}
try {
bw.flush();
} catch (IOException ex) {
firePropertyChange("error", null, ex);
}
}
#Override
protected List<String> doInBackground() throws Exception {
List<String> results = new ArrayList<String>(urls.size());
for (final String s : urls) {
String sContent = IO.getURLContent(s);
StringBuilder result = new StringBuilder(s);
if (sContent == null) {
result.insert(0, "Error ");
} else {
result.insert(0, "Worked ");
}
publish(result.toString());
results.add(result.toString());
}
return results;
}
}
Related
I am currently making a boardgame that allows two player through a socket. I am also trying to add in a game chat, but reguardless I need a way for the Frame class to act as normal (Jbuttons, enter text into a jscroll area, etc) but also send & look for incoming objects. I figured implementing runnable would be the best option.
here is my run method:
public void run() {
while(running){
Object obj = null;
try {
obj = connection.receiveObjects();
} catch (ClassNotFoundException) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
running = false;
if(obj != null){
if(obj instanceof String){
showMessage((String)obj);
}
}
}
}
My connection class is just a server or a client, and uses these methods:
public void sendObjects(Object obj) throws IOException{
output.writeObject(obj);
output.flush();
}
public Object receiveObjects() throws IOException, ClassNotFoundException{
return input.readObject();
}
At the Frame class's constructor I call
Thread thread = new Thread(this);
thread.start();
In hopes that the run method will not interfere with the actionPerformed method. But to my dismay, when I click a jbutton (all it does is call the send() method), the program freezes, and nothing is sent over.
Here is the other necessary code:
private JTextArea textBox;
private JScrollPane pane;
private JTextArea userText;
private JScrollPane userPane;
private void send(){
String message = "YOU- " + userText.getText();
userText.setText("");
String totalMessage = textBox.getText();
textBox.setText(totalMessage + "/n" + message);
try {
connection.sendObjects(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void showMessage(String s){
String totalMessage = "Opponent- " + textBox.getText();
textBox.setText(totalMessage + "/n" + s);
}
My hope is that once I get the chat working, I can also add in sending over the pieces and calling other methods to do everything else I need (via instanceof). I could probably make a separate class that implements runnable to just deal with data transfer, but I was hoping I could do it all in one class, and do not see why I cannot. I did spend at least an hour and a half looking up multithreading, but I could not figure my problem out. I apologize If I am looking over anything apparent, but mutithreading and sockets are far beyond my comfort zone and AP Comp sci class education.
Besides the UI thread, you need:
one thread to constantly listen for incoming data
one thread to send data
You have #1 but not #2, so modify send() method as follows:
private void send() {
// Do the UI tasks on the UI thread
final String message = "YOU- " + userText.getText();
userText.setText("");
String totalMessage = textBox.getText();
textBox.setText(totalMessage + "\n" + message);
// Start new thread to do the networking (send data)
new Thread(new Runnable() {
#Override
public void run() {
try {
connection.sendObjects(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
am developing a java application in which I am using swings to develop GUI screens. i am supposed to run some application files. which I did by connecting to command prompt by using Runtime.exec() method. if my application failes to execute properly then a GUI frame will come up asking weather to run that file again or to skip.
here my problem is when I say run that file again the control should return to the point where the frame is called using ui.setvisible(true);
if not the swing frame what can i use to make my code work
public static boolean runFormat(String format,String buildNumber) throws Exception
{
try{
ProcessExecutor process = new ProcessExecutor();
process.executeCommand(format+"\\Scripts"+File.separator+"Step1.bat"+""+"02_00"+" "+format);
process.waitForCompletion();
File file = new File(format+File.separator+"Results1.log");
BufferedReader read = new BufferedReader (new FileReader(file));
String line;
while((line=read.readLine())!=null)
{
if(line.contains("Successful exit."))
{
return true;
}
}
return false;
}
catch(Exception e)
{
System.out.println("EXCEPTION OCCURED..................");
System.out.println("JTag has failed for "+format);
e.printStackTrace();
}
return true;
}
void run(Set<String> formats)
{
try
{
for(String ar : formats)
{
boolean b =runFormat(ar,"001");
if(b==false)
{
ExampleUi ui = new ExampleUi();
ui.setVisible(true);
}
}
}
catch(Exception e)
{
}
}
Thanks in advance
The short answer is no.
The long answer would involve using a SwingWorker and making the decisions about what to do within it's done method
Take a look at Worker Threads and SwingWorker for more details...
public class ProcessWorker extends SwingWorker<Boolean, Void> {
public Boolean doInBackground() throws Exception {
ProcessBuilder pb = new ProcessBuilder(...);
Process p = pb.start();
// Read the input stream in separate thread...
return p.waitFor() == 0;
}
public void done() {
try {
boolean okay = get();
if (!okay) {
// Re-run....?
}
} catch (Exception exp) {
// Show error message, maybe in a JOptionPane
}
}
}
I built a basic web parser that uses hadoop to hand of urls to multiple threads. This works pretty well until I reach the end of my input file, Hadoop declares itself done while there are still threads running. This results in the error org.apache.hadoop.fs.FSError: java.io.IOException: Stream Closed. Is there anyway to keep the stream open long enough for the threads to finish up? (I can with reasonable accuracy predict the maximum amount of time the thread will spend on a single url).
Heres how I execute the threads
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
private Text word = new Text();
private URLPile pile = new URLPile();
private MSLiteThread[] Threads = new MSLiteThread[16];
private boolean once = true;
#Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
String url = value.toString();
StringTokenizer urls = new StringTokenizer(url);
Config.LoggerProvider = LoggerProvider.DISABLED;
System.out.println("In Mapper");
if (once) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
once = false;
}
while (urls.hasMoreTokens()) {
try {
word.set(urls.nextToken());
String currenturl = word.toString();
pile.addUrl(currenturl, output);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
The threads themselves get the urls like this
public void run(){
try {
sleep(3000);
while(!done()){
try {
System.out.println("in thread");
MSLiteURL tempURL = pile.getNextURL();
String currenturl = tempURL.getURL();
urlParser.parse(currenturl);
urlText.set("");
titleText.set(currenturl+urlParser.export());
System.out.println(urlText.toString()+titleText.toString());
tempURL.getOutput().collect(urlText, titleText);
pile.doneParsing();
sleep(30);
} catch (Exception e) {
pile.doneParsing();
e.printStackTrace();
continue;
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread done");
}
And the relevant methods in urlpile are
public synchronized void addUrl(String url,OutputCollector<Text, Text> output) throws InterruptedException {
while(queue.size()>16){
System.out.println("queue full");
wait();
}
finishedParcing--;
queue.add(new MSLiteURL(output,url));
notifyAll();
}
private Queue<MSLiteURL> queue = new LinkedList<MSLiteURL>();
private int sent = 0;
private int finishedParcing = 0;
public synchronized MSLiteURL getNextURL() throws InterruptedException {
notifyAll();
sent++;
//System.out.println(queue.peek());
return queue.remove();
}
As I can infer from the comments below, you can probably do this in each of the map() function to make things easy.
I saw you do the following, to pre-create some idle threads.
You can move the following code to
if (once) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
once = false;
}
to,
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
#Override
public void configure(JobConf job) {
for (MSLiteThread thread : Threads) {
System.out.println("created thread");
thread = new MSLiteThread(pile);
thread.start();
}
}
#Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
}
}
So, that this could get initialized once and for that matter, don't need the 'once' condition check anymore.
Moreover, you don't need to do make idle threads as above.
I don't know how much performance gain you'll get creating 16 idle threads as such.
Anyways, here is a solution (may not be perfect though)
You can use something like a countdownlatch Read more here to process your urls in batches of N or more and block off until they are done. This is because, if you release each incoming url record to a thread, the next url will be fetched immediately and chances are that when you are processing the last url the same way, the map() function will return even if you have threads remaining in the queue to process. You'll inevitably get the exception you mentioned.
Here in an example of how probably you can block off using a countdownlatch.
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, Text> {
#Override
public void map(LongWritable key, Text value,
OutputCollector<Text, Text> output, Reporter reporter) {
String url = value.toString();
StringTokenizer urls = new StringTokenizer(url);
Config.LoggerProvider = LoggerProvider.DISABLED;
//setting countdownlatch to urls.countTokens() to block off that many threads.
final CountDownLatch latch = new CountDownLatch(urls.countTokens());
while (urls.hasMoreTokens()) {
try {
word.set(urls.nextToken());
String currenturl = word.toString();
//create thread and fire for current URL here
thread = new URLProcessingThread(currentURL, latch);
thread.start();
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
latch.await();//wait for 16 threads to complete execution
//sleep here for sometime if you wish
}
}
Finally, in URLProcessingThread as soon as a URL is processed decrease the latch counter,
public class URLProcessingThread implments Runnable {
CountDownLatch latch;
URL url;
public URLProcessingThread(URL url, CountDownLatch latch){
this.latch = latch;
this.url = url;
}
void run() {
//process url here
//after everything finishes decrement the latch
latch.countDown();//reduce count of CountDownLatch by 1
}
}
Probably problems seen with your code:
At pile.addUrl(currenturl, output);, when you add a new url, in the meantime all the 16 threads will get the update (I'm not very sure), because the same pile object is passed to the 16 threads. There is a chance that your urls get re-processed or you can probably get some other side effects (I'm not very sure about that).
Other suggestion:
Additionally you may want to increase map task timeout using
mapred.task.timeout
(default=600000ms) = 10mins
Description: The number of milliseconds before a task will be terminated if it neither reads an input, writes an output, nor updates
its status string.
You can add/override this property in mapred-site.xml
I have to send a set of files to several computers through a certain port. The fact is that, each time that the method that sends the files is called, the destination data (address and port) is calculated. Therefore, using a loop that creates a thread for each method call, and surround the method call with a try-catch statement for a BindException to process the situation of the program trying to use a port which is already in use (different destination addresses may receive the message through the same port) telling the thread to wait some seconds and then restart to retry, and keep trying until the exception is not thrown (the shipping is successfully performed).
I didn't know why (although I could guess it when I first saw it), Netbeans warned me about that sleeping a Thread object inside a loop is not the best choice. Then I googled a bit for further information and found this link to another stackoverflow post, which looked so interesting (I had never heard of the ThreadPoolExecutor class). I've been reading both that link and the API in order to try to improve my program, but I'm not yet pretty sure about how am I supposed to apply that in my program. Could anybody give a helping hand on this please?
EDIT: The important code:
for (Iterator<String> it = ConnectionsPanel.list.getSelectedValuesList().iterator(); it.hasNext();) {
final String x = it.next();
new Thread() {
#Override
public void run() {
ConnectionsPanel.singleAddVideos(x);
}
}.start();
}
private static void singleAddVideos(String connName) {
String newVideosInfo = "";
for (Iterator<Video> it = ConnectionsPanel.videosToSend.iterator(); it.hasNext();) {
newVideosInfo = newVideosInfo.concat(it.next().toString());
}
try {
MassiveDesktopClient.sendMessage("hi", connName);
if (MassiveDesktopClient.receiveMessage(connName).matches("hello")) {
MassiveDesktopClient.sendMessage(newVideosInfo, connName);
}
} catch (BindException ex) {
MassiveDesktopClient.println("Attempted to use a port which is already being used. Waiting and retrying...", new Exception().getStackTrace()[0].getLineNumber());
try {
Thread.sleep(MassiveDesktopClient.PORT_BUSY_DELAY_SECONDS * 1000);
} catch (InterruptedException ex1) {
JOptionPane.showMessageDialog(null, ex1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
}
ConnectionsPanel.singleAddVideos(connName);
return;
}
for (Iterator<Video> it = ConnectionsPanel.videosToSend.iterator(); it.hasNext();) {
try {
MassiveDesktopClient.sendFile(it.next().getAttribute("name"), connName);
} catch (BindException ex) {
MassiveDesktopClient.println("Attempted to use a port which is already being used. Waiting and retrying...", new Exception().getStackTrace()[0].getLineNumber());
try {
Thread.sleep(MassiveDesktopClient.PORT_BUSY_DELAY_SECONDS * 1000);
} catch (InterruptedException ex1) {
JOptionPane.showMessageDialog(null, ex1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
}
ConnectionsPanel.singleAddVideos(connName);
return;
}
}
}
Your question is not very clear - I understand that you want to rerun your task until it succeeds (no BindException). To do that, you could:
try to run your code without catching the exception
capture the exception from the future
reschedule the task a bit later if it fails
A simplified code would be as below - add error messages and refine as needed:
public static void main(String[] args) throws Exception {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(corePoolSize);
final String x = "video";
Callable<Void> yourTask = new Callable<Void>() {
#Override
public Void call() throws BindException {
ConnectionsPanel.singleAddVideos(x);
return null;
}
};
Future f = scheduler.submit(yourTask);
boolean added = false; //it will retry until success
//you might use an int instead to retry
//n times only and avoid the risk of infinite loop
while (!added) {
try {
f.get();
added = true; //added set to true if no exception caught
} catch (ExecutionException e) {
if (e.getCause() instanceof BindException) {
scheduler.schedule(yourTask, 3, TimeUnit.SECONDS); //reschedule in 3 seconds
} else {
//another exception was thrown => handle it
}
}
}
}
public static class ConnectionsPanel {
private static void singleAddVideos(String connName) throws BindException {
String newVideosInfo = "";
for (Iterator<Video> it = ConnectionsPanel.videosToSend.iterator(); it.hasNext();) {
newVideosInfo = newVideosInfo.concat(it.next().toString());
}
MassiveDesktopClient.sendMessage("hi", connName);
if (MassiveDesktopClient.receiveMessage(connName).matches("hello")) {
MassiveDesktopClient.sendMessage(newVideosInfo, connName);
}
for (Iterator<Video> it = ConnectionsPanel.videosToSend.iterator(); it.hasNext();) {
MassiveDesktopClient.sendFile(it.next().getAttribute("name"), connName);
}
}
}
Consider this code:
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new SwingWorker<File, Void>() {
private String location = url.getText();
#Override
protected File doInBackground() throws Exception {
File file = new File("out.txt");
Writer writer = null;
try {
writer = new FileWriter(file);
creator.write(location, writer);
} finally {
if (writer != null) {
writer.close();
}
}
return file;
}
#Override
protected void done() {
setEnabled(true);
try {
File file = get();
JOptionPane.showMessageDialog(FileInputFrame.this,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
Desktop.getDesktop().open(file);
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Thread interupted, process aborting.", ex);
Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
Throwable cause = ex.getCause() == null ? ex : ex.getCause();
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(FileInputFrame.this, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
} catch (IOException ex) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
}
}.execute();
url is a JTextField and 'creator' is an injected interface for writing the file (so that part is under test). The location where the file is written is hard coded on purpose because this is intended as an example. And java.util.logging is used simply to avoid an external dependency.
How would you chunk this up to make it unit-testable (including abandoning SwingWorker if needed, but then replacing its functionality, at least as used here).
The way I look at it, the doInBackground is basically alright. The fundamental mechanics are creating a writer and closing it, which is almost too simple to test and the real work is under test. However, the done method is quote problematic, including its coupling with the actionPerformed method the parent class and coordinating the enabling and disabling of the button.
However, pulling that apart is not obvious. Injecting some kind of SwingWorkerFactory makes capturing the GUI fields a lot harder to maintain (it is hard to see how it would be a design improvement). The JOpitonPane and the Desktop have all the "goodness" of Singletons, and exception handling makes it impossible to wrap the get easily.
So what would be a good solution to bring this code under test?
IMHO, that's complicated for an anonymous class. My approach would be to refactor the anonymous class to something like this:
public class FileWriterWorker extends SwingWorker<File, Void> {
private final String location;
private final Response target;
private final Object creator;
public FileWriterWorker(Object creator, String location, Response target) {
this.creator = creator;
this.location = location;
this.target = target;
}
#Override
protected File doInBackground() throws Exception {
File file = new File("out.txt");
Writer writer = null;
try {
writer = new FileWriter(file);
creator.write(location, writer);
}
finally {
if (writer != null) {
writer.close();
}
}
return file;
}
#Override
protected void done() {
try {
File file = get();
target.success(file);
}
catch (InterruptedException ex) {
target.failure(new BackgroundException(ex));
}
catch (ExecutionException ex) {
target.failure(new BackgroundException(ex));
}
}
public interface Response {
void success(File f);
void failure(BackgroundException ex);
}
public class BackgroundException extends Exception {
public BackgroundException(Throwable cause) {
super(cause);
}
}
}
That allows the file writing functionality to be tested independent of a GUI
Then, the actionPerformed becomes something like this:
public void actionPerformed(ActionEvent e) {
setEnabled(false);
Object creator;
new FileWriterWorker(creator, url.getText(), new FileWriterWorker.Response() {
#Override
public void failure(FileWriterWorker.BackgroundException ex) {
setEnabled(true);
Throwable bgCause = ex.getCause();
if (bgCause instanceof InterruptedException) {
logger.log(Level.INFO, "Thread interupted, process aborting.", bgCause);
Thread.currentThread().interrupt();
}
else if (cause instanceof ExecutionException) {
Throwable cause = bgCause.getCause() == null ? bgCause : bgCause.getCause();
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(FileInputFrame.this, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
#Override
public void success(File f) {
setEnabled(true);
JOptionPane.showMessageDialog(FileInputFrame.this,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
try {
Desktop.getDesktop().open(file);
}
catch (IOException iOException) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
}
}).execute();
}
Additionally, the instance of FileWriterWorker.Response can be assigned to a variable and tested independent of FileWriterWorker.
The current implementation couples together threading concerns, UI and file writing - and as you've discovered that coupling makes it hard to test the individual components in isolation.
This is quite a long response, but it boils down to pulling out these three concerns from the current implementation into separate classes with a defined interface.
Factor out Application Logic
To start with, focus on the core application logic and move that into a separate class/interface. An interface allows easier mocking, and use of other swing-threading frameworks. The separation means you can test your application logic entirely independently from the other concerns.
interface FileWriter
{
void writeFile(File outputFile, String location, Creator creator)
throws IOException;
// you could also create your own exception type to avoid the checked exception.
// a request object allows all the params to be encapsulated in one object.
// this makes chaining services easier. See later.
void writeFile(FileWriteRequest writeRequest);
}
class FileWriteRequest
{
File outputFile;
String location;
Creator creator;
// constructor, getters etc..
}
class DefualtFileWriter implements FileWriter
{
// this is basically the code from doInBackground()
public File writeFile(File outputFile, String location, Creator creator)
throws IOException
{
Writer writer = null;
try {
writer = new FileWriter(outputFile);
creator.write(location, writer);
} finally {
if (writer != null) {
writer.close();
}
}
return file;
}
public void writeFile(FileWriterRequest request) {
writeFile(request.outputFile, request.location, request.creator);
}
}
Separate out UI
With the application logic now separate, we then factor out the success and error handling. This means that the UI can be tested without actually doing the file writing. In particular, error handling can be tested without actually need to provoke those errors. Here, the errors are quite simple, but often some errors can be very difficult to provoke. By separating out the error handling, there is also chance for reuse, or replacing how the errors are handled. E.g. using a JXErrorPane later.
interface FileWriterHandler {
void done();
void handleFileWritten(File file);
void handleFileWriteError(Throwable t);
}
class FileWriterJOptionPaneOpenDesktopHandler implements FileWriterHandler
{
private JFrame owner;
private JComponent enableMe;
public void done() { enableMe.setEnabled(true); }
public void handleFileWritten(File file) {
try {
JOptionPane.showMessageDialog(owner,
"File has been retrieved and saved to:\n"
+ file.getAbsolutePath());
Desktop.getDesktop().open(file);
}
catch (IOException ex) {
handleDesktopOpenError(ex);
}
}
public void handleDesktopOpenError(IOException ex) {
logger.log(Level.INFO, "Unable to open file for viewing.", ex);
}
public void handleFileWriteError(Throwable t) {
if (t instanceof InterruptedException) {
logger.log(Level.INFO, "Thread interupted, process aborting.", ex);
// no point interrupting the EDT thread
}
else if (t instanceof ExecutionException) {
Throwable cause = ex.getCause() == null ? ex : ex.getCause();
handleGeneralError(cause);
}
else
handleGeneralError(t);
}
public void handleGeneralError(Throwable cause) {
logger.log(Level.SEVERE, "An exception occurred that was "
+ "not supposed to happen.", cause);
JOptionPane.showMessageDialog(owner, "Error: "
+ cause.getClass().getSimpleName() + " "
+ cause.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
Separate out Threading
Finally, we can also separate out the threading concerns with a FileWriterService. Using a FileWriteRequest above makes coding this simpler.
interface FileWriterService
{
// rather than have separate parms for file writing, it is
void handleWriteRequest(FileWriteRequest request, FileWriter writer, FileWriterHandler handler);
}
class SwingWorkerFileWriterService
implements FileWriterService
{
void handleWriteRequest(FileWriteRequest request, FileWriter writer, FileWriterHandler handler) {
Worker worker = new Worker(request, fileWriter, fileWriterHandler);
worker.execute();
}
static class Worker extends SwingWorker<File,Void> {
// set in constructor
private FileWriter fileWriter;
private FileWriterHandler fileWriterHandler;
private FileWriterRequest fileWriterRequest;
protected File doInBackground() {
return fileWriter.writeFile(fileWriterRequest);
}
protected void done() {
fileWriterHandler.done();
try
{
File f = get();
fileWriterHandler.handleFileWritten(f);
}
catch (Exception ex)
{
// you could also specifically unwrap the ExecutorException here, since that
// is specific to the service implementation using SwingWorker/Executors.
fileWriterHandler.handleFileError(ex);
}
}
}
}
Each part of the system is separately testable - the application logic, the presentation (success and error handling) and the threading implementation is also a separate concern.
This may seem like a lot of interfaces, but the implementation is mostly cut-and-paste from your original code. The interfaces provide the separation that is needed to make these classes testable.
I'm not much of a fan of SwingWorker's so keeping them behind an interface helps keep the clutter they produce out of the code. It also allows you to use a different implementation for implementing the separate UI/background threads. For example, to use Spin, you only need to provide a new implementation of FileWriterService.
Easy solution : a simple timer is best ; you lanch your timer, you launch your actionPerformed, and at the timeout the bouton must be enabled and so on.
Here is an very littel exemple with a java.util.Timer :
package goodies;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JButton;
public class SWTest
{
static class WithButton
{
JButton button = new JButton();
class Worker extends javax.swing.SwingWorker<Void, Void>
{
#Override
protected Void doInBackground() throws Exception
{
synchronized (this)
{
wait(4000);
}
return null;
}
#Override
protected void done()
{
button.setEnabled(true);
}
}
void startWorker()
{
Worker work = new Worker();
work.execute();
}
}
public static void main(String[] args)
{
final WithButton with;
TimerTask verif;
with = new WithButton();
with.button.setEnabled(false);
Timer tim = new Timer();
verif = new java.util.TimerTask()
{
#Override
public void run()
{
if (!with.button.isEnabled())
System.out.println("BAD");
else
System.out.println("GOOD");
System.exit(0);
}};
tim.schedule(verif, 5000);
with.startWorker();
}
}
Supposed Expert solution : a Swing Worker is a RunnableFuture, inside it a FutureTask imbeded in a callable, so you can use your own executor to launch it (the RunableFuture). To do that, you need a SwingWorker with a name class, not an anonymous. With your own executor and a name class, you can test all you want, the supposed expert says.