A Simple SwingWorker Example to Save a File

Here is a simple SwingWorker example to save text in JTextArea to a file. Before we go into the program, let me introduce the SwingWorker. The use of the swing worker is to improve the performance of the GUI application by leaving the non GUI part (or the part that doesn't need to run in the EDT) to a thread called the worker thread. If the non GUI part (usually the lengthy back end stuff) runs in the EDT, then the GUI might go unresponsive. The program discussed below, however it isn't a perfect example because saving a simple text file isn't a big task for most computers today. However, this is just an attempt to show what is SwingWorker and how it smooths our life.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.concurrent.*;
import javax.swing.event.*;
class SwingWorkerExample extends JFrame
{
JPanel jp;
JButton save;
JTextArea jt;
JScrollPane js;

    public SwingWorkerExample()
    {
        createAndShowGUI();
    }
   
    private void createAndShowGUI()
    {
        // Set frame properties
        setTitle("Save a file");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
       
        // Create a JPanel
        jp=new JPanel();
       
        // Set FlowLayout for jp
        jp.setLayout(new FlowLayout());
       
        // Create save button
        save=new JButton("Save");
       
        // Create JTextArea
        jt=new JTextArea();
       
        // Create scroll pane for jt
        js=new JScrollPane(jt);
       
        // Add the JScrollPane to center
        add(js);
       
        // Add the button to jp
        jp.add(save);
       
        // Add the JPanel to NORTH
        add(jp,BorderLayout.NORTH);
       
       
        // Set an action for the save button
        save.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent ae)
            {
                // Call the save() method to start saving
                save();
            }
        });
       
        // To make the save button enabled every time there is
        // a modification of text in the JTextArea's current document
        jt.getDocument().addDocumentListener(new DocumentListener(){
            public void insertUpdate(DocumentEvent de)
            {
                enableButton();
            }
           
            public void changedUpdate(DocumentEvent de)
            {
                enableButton();
            }
           
            public void removeUpdate(DocumentEvent de)
            {
                enableButton();
            }
        });
       
       
        setSize(400,400);
        setLocationRelativeTo(null);
        setVisible(true);
    }
   
    public void enableButton()
    {
        // Set the text and enable
        // the button
        save.setText("Save");
        save.setEnabled(true);
    }
   
    private void save()
    {
        // The worker thread that takes Integer,Void
       
        // Parameter desc
        // ---------------
        // The first type (here Integer) is the return type of
        // the doInBackground() method.
        // The next type (here Void) is the intermediate result
        // type, if there is no intermediate result, then it is set
        // to Void type.
       
        // The return type of the get() method is the return type
        // of the doInBackground() method
       
        SwingWorker<Integer,Void> worker=new SwingWorker<Integer,Void>(){
       
            // The Integer returned here is either -1 or 0
            // representing unsuccessful and successful save
            // respectively.
            protected Integer doInBackground()
            {
                // Disable the button
                save.setEnabled(false);
               
                System.out.println("doInBackground()");
               
                // Save the text as saving
                // This text is not mostly seen, because
                // the process is done so faster, that the
                // with in less time, done() method is reached
                // For a larger file, you can see the text
               
                // Remember, save.setText() isn't thread safe
                // It is a GUI operation that repaints the button
                // So, execute it in the EDT
                SwingUtilities.invokeLater(new Runnable(){
                    public void run(){
                    save.setText("Saving..");
                    }
                });
               
                // Create a File object
                File f=new File("saved.txt");
               
                try
                {
                    // Now do the operation
                    jt.write(new FileWriter(f));
                }catch(Exception e){
                    // When there occurs an exception, the file
                    // isn't saved, so return -1
                    return -1;
                }
           
            // If no exception occurs, the file is saved
            // successfully, meaning 0 should be returned
            return 0;
            }
           
            // This method is executed once after, the
            // doInBackground() method is executed
           
            // done() runs in the EDT
            protected void done()
            {
                System.out.println("done()");
                // The get() method throws various exceptions
                // More on it in the post page
                try
                {
                // If savigs is not done within 2 seconds
                int i=get(2L,TimeUnit.SECONDS);
               
                    // If 0 is returned, file is saved
                    if(i==0) save.setText("Saved.");
                   
                   
                    // Else, file isn't saved
                    // One way to see this text is to set the
                    // saved.txt to Read only
                    else save.setText("Unable to save.");
                   
                // Using multi catch specification
                }catch(InterruptedException|ExecutionException|TimeoutException e){
                    // If saving doesn't happen within the time, time is out.
                    // i.e. a TimeoutException is thrown
                    if(e instanceof TimeoutException) {
                        save.setEnabled(true);
                        save.setText("Save again.");
                    }
                }
            }
        };
       
        // Start the worker thread
        worker.execute();
    }
   
    public static void main(String args[])
    {
        SwingUtilities.invokeLater(new Runnable(){
            public void run()
            {
                new SwingWorkerExample();
            }
        });
    }
}

The SwingWorker class is a generic class taking two types generic parameters. The first parameter is the return type of the doInBackground() method and also the get() method. This return type is the type of the final result produced by the doInBackground() method.
The next parameter is the type of the intermediate (aka interim) result produced. An interim result is an intermediate result which might be useful in the future. As there is no intermediate results in this example, the second type is left to Void.

The doInBackground() method contains the code that needs to run in the background (the code that doesn't interact with the GUI or the non-thread-safe part of the swing) is written here. This method is not executed in the EDT, instead a separate thread does the job.

After the doInBackground() method is complete, the done() is called. This done() method is executed in the EDT because so that the programmer can show the updated status on the GUI components. The method get() which returns the value returned by the doInBackground() method throws several exceptions. The exceptions and their causes are given below. But before that, you need to learn that there are two overloaded versions of the get() method, get() and get(long l,TimeUnit) out of which second one is the most preferred to ensure smoother GUI performance.

The first version of the get() method throws two exceptions which are, InterruptedException - if the thread is interrupted before there is a result, ExecutionException - exception in computation.
The second version throws the above two exceptions along with the TimeoutException. It is wise to understand the difference between the get() and the get(long l,TimeUnit units) method.

The first version of the get() method waits until you get the result from the doInBackground() method. If you didn't get a result from the doInBackground() method due to thread interruption, then the get() method waits, the program is blocked until doInBackground() returns a value. As this method is called in the EDT, the GUI of the program goes unresponsive.

To overcome such situations, the second version of the get() method is used in which we can specify the time (either in seconds, minutes, milliseconds or whatever)  before which the we need to get the computation result. If we don't get the result within the specified time, the control goes on, thereby leaving the GUI responsive. Here, the time is set to 2 seconds. Which means that, here if i don't get a value (either 0 or -1) within 2 seconds, then a TimeoutException is thrown.

No comments: