Java and directory trees - The joy of implementing a simple filemanager

Six weeks without internet access can really cause you to do funny things. I, for myself decided to pass the time by finally rewriting an old project of mine that was, to put it mildly, an embarrassment to show to anyone. I am still not quite finished with it yet, but since I haven't been able to blog for a while and there are always people searching for code examples, solving common problems, I guess, I could as well work some snippets into blog posts.

Ok, motivation given, first problem with my to be rewritten project was adding a very simple file manager widget to the program, that would allow the user to select an image (PNG, BMP, GIF, etc.) from a directory or one of it's subdirectories. As an additional benefit, this file manager should also automatically sync with the file system, so the user does not have to be bothered with having to hit some kind of "reload" button after dropping a new image in one of the directories.

In essence, a simple problem with a seemingly easy solution. With the JTree class, Swing already offers a tree widget, so in theory the only thing left to do is to build a TreeModel from a directory tree and submit that to the JTree widget, right? Well, not quite. There are a couple of stumbling blocks to consider first:

  • We are used to think of directory trees as, well, trees. That assumption is correct most, but not all of the time. All modern file systems support the concept of softlinks, which can turn a tree into a cyclical graph. Traversing such a graph without checking for cycles, inevitably leads to sending the program into an infinite loop. The annoying thing about Java is, that the JRE (<v6.x) does not provide means for checking whether or not some File object is a regular file/directory or a soft link. So the only way of guarding against an infinite loop without going native, is to simply set a hard limit on the maximum directory depth (e.g. 20 subdirectories - most users don't bury anything deeper than that).
  • JTree does not provide a terrible lot of feedback concerning the type of it's entries. In fact, it only provides two different icons. One for leaf nodes and the other one is for containers. This really causes a problem because there is no way of telling, whether a leaf node refers to a file or an empty directory (at least not until you implement a custom renderer, which opens another can of worms concerning theming). Thats really not a good thing from a usability point of view and the problem gets worse, as there may be entire subdirectory trees in the file system, not containing a single file in them at all. The best solution would be to simply filter all empty directories away and not even bother the user about them.
  • Syncing the file manager with the file system is another problem. For once, monitoring must be done in a separate thread, which regularly polls the state of the file system and there needs to be some kind of criteria for deciding when it is time to rebuild the model of the JTree.
    The criteria is actually a rather trivial problem. The file system obviously changed when a file with a timestamp can be found, that is newer then any of the timestamps found when checking last. The bigger headache is the fact, that Swing is not thread safe, as is conveniently stated in the API docs of almost every Swing widget. Calling JTree.setModel() from the monitoring thread is therefore a big no-no. What has to be done instead is to invoke setModel() from the event thread, as if a "reload" button had been pressed by the user.

  • Last but not least, it would be nice if the file manager would automatically filter away any files that are not image files of a graphics format supported by the JRE and of course, nodes should appear alphabetically sorted.

So, let's put this all together. Easiest thing first. ImageFileFilter.java is a rather trivial implementation of a file filter, that will simply accept all image files supported by the current JRE according to suffix. Next stop is SimpleFileManager.java. This class will recursively traverse a directory tree (up to a maximum depth of 10 levels) and build a tree of nodes from it. It also provides support for getting the file object, that corresponds to a selected node. And finally there is FileMonitor.java a helper class that will take care of monitoring the directory tree.

The three classes can be used as follows:

import javax.swing.*;
import java.io.*;
import javax.swing.tree.*;
 
public class MyPanel extends JPanel implements Runnable {
 
  private JTree tree  = new JTree();
  private SimpleFileManager simpleFileManager;
 
  public MyPanel() throws IOException {
    add(new JScrollPane(tree));
    File root = new File(System.getProperty("user.home"));
    simpleFileManager = new SimpleFileManager(root, new ImageFileFilter());   
    tree.setModel(new DefaultTreeModel(simpleFileManager.getDirectoryTree()));
  }
 
  // This has to be a separate method, because it is dangerous to leak
  // a "this" pointer from a constructor!
  public void activateMonitor() {
    simpleFileManager.startMonitoring();
    FileMonitor fileMonitor = simpleFileManager.getFileMonitor();
    fileMonitor.addClient(this);
   
  }
 
  // The FileManager will call the run() method from the eventqueue, when
  // the tree needs to be rebuild.
  public void run() {
    simpleFileManager.refresh();
    tree.setModel(new DefaultTreeModel(simpleFileManager.getDirectoryTree()));
  }
 
  // Just for testing
  public static void main(String[] args) throws IOException{
    JFrame frame = new JFrame();
    MyPanel myPanel = new MyPanel();
    myPanel.activateMonitor();
    frame.setContentPane(myPanel);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }
}