5

I'm having an issue with Threads using netbeans Swing GUI. This is my first time really trying to develop a GUI for a backup program using Java's File System Notifier. I have two files SyncUI.java and Sync.java.

Pretty much what I want to happen is you enter a directory path in the jTextField1 text field which creates a sync thread that creates a new sync object and then calls processEvents on that object. When a file in that directory is changed I want to add text about the change to the list.

In its current state the UI is no longer not responding, however the processEvents isn't adding anything into my list. Any idea what the problem is? Also since I'm just starting to use java any constructive criticism is welcomed.

SyncUI.java:

package sync;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.SwingUtilities;

public class SyncUI extends javax.swing.JFrame {

    public SyncUI() {
        initComponents();
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {
        jButton1 = new javax.swing.JButton();
        jTextField1 = new javax.swing.JTextField();
        list1 = new java.awt.List();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jButton1.setText("Start");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jTextField1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jTextField1ActionPerformed(evt);
            }
        });

        list1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                list1ActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(list1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .add(layout.createSequentialGroup()
        .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 329, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(jButton1)
        .add(0, 0, Short.MAX_VALUE)))
        .addContainerGap())
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
        .add(jButton1)
        .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(list1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 229, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(25, Short.MAX_VALUE))
    );

        pack();
    }// </editor-fold>                        

private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {                                            
    jButton1.doClick();
}                                           

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {  
        //I tried to use invokeLater, this solved the problem of the UI nonresponse issue                                       
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                //Creates a path dir that my Sync constructor needs to start file   notification watcher on that directory
                Path dir = Paths.get(jTextField1.getText());
                try
                {
                    //Creates a new sync object passing it the directory and the list in my GUI
                    Sync sync = new Sync(dir, list1);
                    try
                    {
                        sync.processEvents();
                    }
                    catch (InterruptedException ex)
                    {
                        Logger.getLogger(SyncUI.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                catch (IOException ex)
                {
                    Logger.getLogger(SyncUI.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        });
}                                        

private void list1ActionPerformed(java.awt.event.ActionEvent evt) {                                      
    // TODO add your handling code here:
}                                     

public static void main(String args[]) throws IOException
{
    java.awt.EventQueue.invokeLater(new Runnable()
    {
        public void run()
        {
            SyncUI s = new SyncUI();
            s.setVisible(true);               
        }
    });     
}

// Variables declaration - do not modify                     
private javax.swing.JButton jButton1;
private javax.swing.JTextField jTextField1;
private java.awt.List list1;
// End of variables declaration                   
}

Sync.java:

package sync;

import static java.nio.file.StandardWatchEventKinds.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;

public class Sync
{
    private final WatchService ws;
    private final Map<WatchKey,Path> keys;
    public java.awt.List list;

    public Sync(Path dir, java.awt.List list) throws IOException, InterruptedException
    {
        this.ws = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();
        this.list = list;
        recSet(dir);
        //this.processEvents();
    }

    private void register(Path dir) throws IOException
    {
        WatchKey key = dir.register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        keys.put(key, dir);
    }

    private void recSet(Path start) throws IOException
    {
        Files.walkFileTree(start, new SimpleFileVisitor<Path>()
        {     
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
            {
                if(!Files.isHidden(dir))
                {
                    register(dir);
                    System.out.println(dir);
                }
            return FileVisitResult.CONTINUE;
            }
        });
    }

    void processEvents() throws IOException, InterruptedException
    {
        System.out.println("Entered processEvents");
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                System.out.println("Entered run");
                list.add("test2");
                list.repaint();
                while(true)
                { 
                    WatchKey key;           
                    try
                    {
                        key = ws.take();
                    }
                    catch (InterruptedException x)
                    {
                        return;
                    }

                    Path dir = keys.get(key);
                    if (dir == null)
                    {
                        System.err.println("WatchKey not recognized");
                        continue;
                    }

                    for (WatchEvent<?> event: key.pollEvents())
                    {
                        WatchEvent.Kind<?> kind = event.kind();
                        WatchEvent<Path> ev = (WatchEvent<Path>)event;
                        Path filename = ev.context();
                        String name = dir.resolve(filename).toString();             

                        if (kind == OVERFLOW)
                            continue;               

                        if(kind == ENTRY_CREATE)
                        {
                            System.out.print("Entry Created: ");
                            File f = new File(name);

                            if(f.isDirectory())
                                try {
                                    register(dir.resolve(filename));
                                } catch (IOException ex) {
                                    Logger.getLogger(Sync.class.getName()).log(Level.SEVERE, null, ex);
                                }

                            System.out.println(name);
                            list.add(name);     
                        }
                        else if(kind == ENTRY_DELETE)
                        {
                            System.out.print("Entry Deleted: ");
                            System.out.println(name);                          
                        }
                        else if(kind == ENTRY_MODIFY)
                        {
                            File f = new File(name);
                            if(!f.isDirectory())
                        {
                            System.out.print("Entry Modify: ");
                            System.out.println(name);
                        }
                    }

                    boolean valid = key.reset();

                    if (!valid)
                        break;
                    }
                }
            }
        });       
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Greg
  • 819
  • 1
  • 8
  • 21
  • Can you dynamically add list items on button press? If not, this has nothing to do with NIO, and you should post that instead. For better help sooner, post an [SSCCE](http://sscce.org/). – Andrew Thompson Jul 20 '12 at 04:24
  • Yeah I was able to do list.add("stuff"); in my Button Press Action Listener and it did append my entry to the list. Also, if I return before my while(true) loop in processEvents it will append to the list. – Greg Jul 20 '12 at 04:27
  • Note that code review is better for constructive criticism, but note some things about ` jTextField1 = new javax.swing.JTextField();` 1) Give it a sensible name, such as `dirPath` or `dirPathTextField` 2) Offer a `JFileChooser` to select the directory. – Andrew Thompson Jul 20 '12 at 04:29
  • 1
    Oh right. Missed the `while(true)` bit. OK.. Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Use a `SwingWorker` for long running tasks. See [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) for more details. – Andrew Thompson Jul 20 '12 at 04:30
  • Yeah I know I need to make those changes. The gui is nothing like it will be just a rudimentary example I threw together to try to get the core functionality working. – Greg Jul 20 '12 at 04:30
  • *"rudimentary example"* For 'rudimentary' I'd hard code it, rather than offer a text field. – Andrew Thompson Jul 20 '12 at 04:32
  • Yeah I wanted to test it on a few different directories and I was running it on my Mac and Ubuntu machine so the location changed, made it easy to just copy and paste the new directory without having to recompile. – Greg Jul 20 '12 at 04:36
  • @GregoryBillings Please, don't mix light weight and heavy weight components together (java.awt.List is a heavy weight component). Sure, I know, it CAN be done, but it's always such a mess :P – MadProgrammer Jul 20 '12 at 04:58

3 Answers3

8

Swing is not thread safe, so, if you are tryng to update a UI in the same thread you will have the "application freeze" behavior. To solve this, you need to delegate the process of update the UI to another thread. This is made using SwingUtilities.invokeLater (Java 5 and prior) method and/or the SwingWorker class (since Java 6).

Some links:

Google search: https://www.google.com.br/search?q=swing+thread+safe

davidbuzatto
  • 9,207
  • 1
  • 43
  • 50
  • My UI doesn't freeze though. It did and I looked up this stuff and put in the: SwingUtilities.invokeLater(new Runnable() { public void run() { .... Stuff so the UI thread is no longer blocked. The issue is that now the list will not update. – Greg Jul 20 '12 at 04:34
  • GregoryBillings, take a look in the @MadProgrammer answer ;) – davidbuzatto Jul 20 '12 at 05:01
4

In support of davidbuzatto:

No, I think you miss under stood what InvokeLater does. InvokeLater ensures that the runnable is executed ON the ETD. So basically, from what I can read, you've gone an put your long running, Event Blocking code right back in the ETD. Only use InvokeLater when you want to update the UI, use Threads or SwingWorker when you want to actually do processing

void processEvents() throws IOException, InterruptedException
    {
        System.out.println("Entered processEvents");

        // PLEASE ETD, PUT THIS AT THE END OF THE QUEUE AND EXECUTE
        // SO I RUN WITHIN YOUR CONTEXT
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {

                // NOW RUNNING BACK ON THE ETD
                System.out.println("Entered run");
                list.add("test2");
                list.repaint();

                // NOW BLOCK THE ETD, SO NO MORE REPAINTS OR UPDATES WILL EVER
                // OCCUR
                while(true)
                { 
                    WatchKey key;           
                    try
                    {
                        key = ws.take();
                    }
                    catch (InterruptedException x)
                    {
                        return;
                    }

Sorry for the caps, but I wanted the comments to standout.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Gotcha, I'll give it a try. Any idea why the test2 wouldn't append to the list though? I would think it would since it occurs before the while loop. No prob with the caps :) – Greg Jul 20 '12 at 04:59
  • 1
    @GregoryBillings list is not been updated in this loop because you blocked the ETD BEFORE it completed this cycle of event processing. Repaint simply places a request onto the ETD for a job to occur in the future, but seen as you blocked the ETD, it never gets run ;) – MadProgrammer Jul 20 '12 at 05:00
2

1. Swing is Not Thread safe, but some methods like repaint(), setText() are Tread Safe.

2. The main() method in Swing is Not Long Lived. It schedules the construction of GUI in the Event Dispactcher Thread and then quits. Now its the responsibility of the EDT to handle the GUI.

3. You must keep your Non-UI work on your Non-UI thread, out off the GUI thread, ie EDT.

4. Your main() method should only do the work of making the JFrame visible, using EventQueue.invokeLater.

Eg:

    public static void main(String[] args){

       EventQueue.invokeLater(new Runnable(){

       public void run(){

       myFrame.setVisible(true);
     }
  }
}

5 SwingWorker is provided by Java to Synchronize the Work Output of the Non-UI thread on the GUI thread.

Kumar Vivek Mitra
  • 33,294
  • 6
  • 48
  • 75