Justin Hill

Subscribe to Justin Hill: eMailAlertsEmail Alerts
Get Justin Hill: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Java Developer Magazine

Java Developer : Article

Customizing JFileChooser

Customizing JFileChooser

Front-end architecture and the art of developing GUIs that are functional and intuitive have been a challenge in this industry for quite some time. The advent of Java has made things a little easier; Java's extensive Swing package has assisted in the rapid creation of GUIs for application development. While the package provides a vast array of robust graphical widgets, there comes a point when a developer will stretch the limits of the component by attempting to provide functionality that is either not supported or nonexistent. JFileChooser is an example of one such component. The purpose of this article is to provide the reader with the knowledge and understanding to extend JFileChooser's functionality in order to display information from a generic directory service.

JFileChooser and FileSystemView
The default implementation of JFileChooser provides the user with a view of the local file system's directories and allows a user to select a file or multiple files. To provide this functionality, JFileChooser, along with its associated UI delegate, uses three helper classes located in the javax.swing.filechooser package: FileSystemView, FileView and FileFilter. To create a robust JFileChooser extension, an implementation should include modifications to all three classes, but as FileSystemView is the most crucial class, it deserves special mention.

FileSystemView is an abstract class that's the gateway to the file system. To create a file chooser that accesses an alternate file system, you'll need to provide an implementation of this class and pass it into the constructor of JFileChooser. When JFileChooser constructs itself, it checks to see if a FileSystemView has been provided; if so, it simply uses it. Otherwise, FileSystemView's getFileSystemView() method is invoked. This special method queries the local file system and determines the file separator ("/" in UNIX or "\" in Windows) in order to instantiate one of three default implementations provided in FileSystemView as inner classes.

This class doesn't actually access the local file system - or any file system, for that matter. Instead, FileSystemView directly interacts with File objects, which provide more direct access to the local file system. Note that some directory services may not represent themselves as a listing of File objects and therefore will need to be converted to representative File objects so as to enable interaction with FileSystemView and JFileChooser. We'll revisit this issue of converting generic directory service objects to File objects in a later section.

A Look Behind the Scenes
As previously mentioned, JFileChooser doesn't actually correspond with the hard disk of the computer, but instead delegates this responsibility to the File object. One of the first things that must happen to display the file chooser is the acquisition of the roots (in the case of the local file system, this would refer to A: through Z: on a Windows machine). FileSystemView's getRoots() and getHomeDirectory() are invoked to accomplish this task, and the results are placed in the file chooser's combo box. Once the roots have been obtained, the combo box's selected value is set to the home directory. Next, FileSystemView's getFiles() is invoked, and all the directories and files that are contained in the home directory are displayed in the list of the JFileChooser. This whole setup is performed for free, meaning that by simply instantiating JFileChooser this functionality is provided. As a result, it's imperative that we understand how and where this functionality is implemented to allow us to override it and add our directory service-specific features. Once the initial setup work is complete, the file chooser is ready to be displayed to the user.

As the user interacts with the file chooser, the bulk of the processing work is delegated to FileSystemView's getFiles() method. This method simply takes a File argument as its parameter (the file should be a directory), obtains the files that are in the directory and returns the results as an array of Files. Although a firmer understanding of FileSystemView is necessary, this is enough information to get us started on creating our own extension of JFileChooser.

Customizing JFileChooser
The extension to JFileChooser (see Figure 1) that I've created doesn't access the local file system. Instead, it connects to a generic directory service.

The concept of a directory service is an extension of a naming service in that it provides a mechanism for associating objects with a logical name within a searchable structure. A naming service simply maps names to objects. For example, the Domain Name System (DNS) maps people-friendly names (www.yahoo.com) to IP addresses (216.32.74.52). While a naming service allows the lookup of objects based on names, a directory service allows these objects to have elements (or attributes) associated with them. For example, in a directory service of employees, looking up the name Fred Jones would return an object that referred to Fred but also contained attributes like age, salary and job title. Directory services can exist in numerous environments:

  • Lightweight Directory Access Protocol (LDAP)
  • CORBA services (COS) - Naming services
  • Java Remote Method Invocation (RMI) - Registry
  • Domain Name System (DNS)
  • Enterprise JavaBean (EJB) - Entity beans
The test directory service created for the purpose of this article emulates a real directory service containing a sampling of baseball teams and players. The contents of the directory service are actually a series of Java objects that are associated with each other via parent-children relationships (see Table 1).

Adapter
During the initial stages of creating this JFileChooser extension there were two hurdles that needed to be overcome:

  1. The need to provide a way to allow for seamless communication between the directory service objects and JFileChooser's File objects
  2. The need to provide an interface that would allow the JFileChooser extension to connect to additional directory services

The interface DirectoryService (see Listing 1) and its concrete implementation (DirectoryServiceSportsAdapter) help to overcome these hurdles. Not only do these classes provide the abstraction layer that allows JFileChooser to connect to alternate directory services without a major code rewrite, they also act as the necessary middle layer communication connection. JFileChooser deals directly with File objects, so the adapter has the ability to convert the directory service objects (whatever they might be) to File objects needed by the file chooser. Furthermore, the adapter allows for asynchronous communication that allows JFileChooser to talk back to the directory service. This mode of communication is important because it allows JFileChooser to access the directory service so as to create new folders, find the root and so on.

DirectoryServiceSportsAdapter
DirectoryServiceSportsAdapter, a concrete implementation of DirectoryServiceAdapter, contains two important member variables: directorySession and HashMap. The former is a reference to the actual test directory service. One of the roles of the adapter is to provide a connection to the directory service and the directorySession variable fills this role. The latter variable is a java.util.HashMap that contains File objects as its keys and directory service-specific objects as its values. The storing of File objects and the directory service objects in the HashMap allows the relationship between the directory service and the file chooser to exist along with providing a local cache.

The concept of a local cache is important because it prevents having to constantly go back across the wire to get the object corresponding to the File. To guarantee a correct representation of the directory service, when the user selects a different directory, the directory's children are always obtained from the service. However, it's at this point that the directory service objects are cached, and from then on the cached copy is used to perform operations such as checking to see if the current filter allows the File, or obtaining the icon.

As mentioned earlier, the bulk of the work of populating JFileChooser is handled via FileSystemView's getFiles(). DirectoryServiceFileSystemView (see Listing 2) provides a specific implementation of FileSystemView by overriding the getFiles() method and delegating the functionality to DirectoryServiceAdapter's getChildren() method. The getChildren() performs the following:

Folder folder = (Folder)this.hashmap.get(aDir);
Node[] arrChildren = folder.getChildren();
for (int i=0; i<arrChildren.length; i++) {
File f = new File(aDir, arrChildren[i].getName());
this.hashmap.put(f, arrChildren[i]); }
If you want to use the extended file chooser widget and its adapter design to connect to an alternate directory service, you'll begin to see the benefits of the abstraction. The advantage of the adapter design pattern is now evident in that any customized directory service adapter implementation is pluggable into this framework.

DirectoryServiceFileChooser and the Helper Classes
DirectoryServiceFileChooser, a subclass of JFileChooser, acts as a factory for the file chooser creation. To provide a full implementation of JFileChooser and showcase all its features, I've created subclasses of the three helper classes (FileSystemView, FileView and FileFilter) named DirectoryServiceFileSystemView, DirectoryServiceFileView and DirectoryServiceFileFilter, respectively. The object-oriented concepts of abstraction and delegation dealing with the DirectoryServiceFileSystemView also pertain to DirectoryServiceFileView and DirectoryServiceFileFilter.

JFileChooser Is a JComponent
While customizing JFileChooser to provide interaction with directory services is the thrust of this article, the concept of embedding JFileChooser inside existing components is an interesting extension that should be covered. In most cases JFileChooser is displayed via one of its showDialog() methods and acts like a standard dialog. The following code snippet illustrates how JFileChooser is normally employed:

JFileChooser fc = new JFileChooser();
int returnVal = fc.showDialog(null, "Open");
if(returnVal == JFileChooser.APPROVE_OPTION)
File file = fc.getSelectedFile();
Although JFileChooser seems to act like a JDialog, it's actually a JComponent. As a result, when the showDialog() method is invoked, the JFileChooser is created and added to a JDialog, which in turn is handled internally by the JFileChooser.

Since JFileChooser is a JComponent, it's possible to do some interesting things with it. For example, you can embed JFileChooser in your own widget simply by creating it and adding it to a specific container. The following code illustrates how to place a JFileChooser in a JFrame:

JFrame frame = new JFrame("File Chooser in a Frame");
frame.getContentPane().add(new JFileChooser(), "Center");
frame.pack();
frame.setVisible(true);
The Selector Dialog
The Selector Dialog (see Figure 2) is a dialog with an embedded JFileChooser component. The purpose of this dialog is to allow the user to populate the list on the right side of the component with entities from the left side (the file chooser). One use of this dialog - applying the baseball directory service example - could allow users to specify a list of teams they'd like to search. For example, suppose a person wanted to find all the players in baseball that make more than $5 million a year and play for certain teams. The user(s) could utilize the Selector Dialog to traverse the sports directory service (via the file chooser) and add only the teams they wanted to search.

While the left side of the Selector Dialog is simply created using the factory method of DirectoryServiceFileChooser, the remainder of the dialog was constructed to interact with the file chooser (see DirectoryServiceSelectorDialog, available on the JDJ Web site).

The three buttons in the middle of the dialog (>>, << and <<<) correspond to Add, Remove and Remove All, respectively, and perform the following functions:

  • Add: Invokes DirectoryServiceFileChooser's getSelectedFiles() method and adds the results to the list. If the file already exists in the list, it is not re-added.
  • Remove: Removes the selected file(s) from the list.
  • Remove All: Clears the list of all files.

As you can see, the idea of embedding JFileChooser in another component can be both powerful and functional.

Future Enhancements
While DirectoryServiceFileChooser and its related classes provide a sturdy framework for creating file chooser graphical widgets that connect to generic directory services, there are three features that would provide extended functionality:

  1. Ability to Connect to Multiple Directory Services: In the current framework the DirectoryServiceFileChooser can connect to only one directory service at a time. However, a future enhancement could include the ability to have the file chooser widget display the contents of multiple directory services. This feature could be implemented using the Observer (Publish-Subscribe) design pattern whereby multiple directory services adapters could register themselves with DirectoryServiceFileChooser.
  2. Allowing for Runtime Connection to Directory Service: The ability to connect to a given directory service at runtime is nonexistent in the current framework. This task could nonetheless be accomplished through the use of Java Properties files. These files, which contain the parameters necessary for connecting to the directory service, could be read at runtime, thus allowing for dynamic connection to various directory services.
  3. Multithreading the List of Files: The current implementation of DirectoryServiceFileChooser doesn't use the concept of threading to obtain the files of a directory for display in the file chooser. Unfortunately, when dealing with large directories of hundreds of files, the user experience can suffer if a user must wait for the file chooser to finish loading the files. However, if multiple threads are used to retrieve and display the files in a cursorlike fashion (i.e., 50 at a time), the user could continue to interact with the chooser as the files loaded in separate threads.

Conclusion
Customization of components is one of Swing's greatest advantages. The ability to customize JFileChooser in order to connect to alternate data sources makes it a more robust graphical widget. The concept of connecting to a generic directory service using an adapter provides DirectoryServiceFileChooser with a pluggable functionality. The end result is a framework that averts the need for refactoring the file chooser widget by decoupling it from the specific nature of the directory services.

Resources and Acknowledgments

  1. Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
  2. Eckstein, R., Loy, M., and Wood, D. (1998). Java Swing. O'Reilly & Associates.
  3. Swing: www.spindoczine.com/sbe/
  4. The ASCltd team for their careful review and thoughtful suggestions.

More Stories By Justin Hill

Justin Hill, a Sun-certified Java programmer, works for Advanced Systems Consulting, a Java-centric consulting firm in Chicago. He’s currently working on a Java-based source control management front-end to be released to the open-source community.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.