Disk space is now plentiful, and users have started to archive everything: emails, MP3s, pictures, documents, Webpages, and more. As the volume of information grows, it becomes increasingly difficult to manage. Filtering can help users get a handle on their archived information. This article demonstrates how Glazed Lists, an open source toolkit for list transformations, can be used to add filtering to your application.

Text filtering

Text filtering is a general filtering technique that is easy to use yet powerful. It is the filtering technique of choice for Apple iTunes and other programs. With text filtering, there is a data table and a search field—type a word in the search field and the data table is filtered to show only those elements containing that word.

Users can perform text filtering with different goals in mind. Suppose a data table contains customer names and locations. Text filtering can be used to search for a particular entry. You can find a customer's entry in the data table by filtering for his name. Text filtering can also select a subset of interest. Another example: when preparing a conference in Portland, an appropriate list of customers is obtained by filtering for "Portland," as shown in Figure 1.

Figure 1. Text filtering for Portland

In this example, text filtering is used to transform the original list into a list containing only customers from Portland. Other useful transformations exist as well. Sorting the list transforms it by reordering the elements. A list of unique cities is another transformation.

List transformations are powerful tools for data manipulation. They are nondestructive, allowing transformations to be created without affecting the original list. Therefore, a list can be transformed in multiple ways simultaneously. Given a list of customers, you could create a transformation filtered by name and a second transformation sorted by location.

List transformations are live. As a list changes, its transformations change automatically. For example, take a list of customers and a filtered transformation of that list. When a customer in the original list is removed, that customer is automatically removed from the filtered list.

List transformations can be chained. Multiple transformations can be combined to manipulate data, offering an elegant approach to separating concerns so sorting logic does not need to consider filtering logic or vice versa. Using the same customers example, you can first filter the customers list by location and then sort that list by name.

Glazed Lists

Glazed Lists is an open source toolkit for list transformations. For links to download the source or a jar file, see Resources. It has a Lesser General Public License, so it is safe to use in commercial applications.

If a developer is already familiar with ArrayList or Vector, she will feel at home with Glazed Lists. It uses the same well-understood java.util.List API throughout. This helps to soften the learning curve, especially when Glazed Lists is used in a multiple-developer project.

Glazed Lists enables the creation of list transformations. The transformation of interest for this article is filtering, but there are also transformations for sorting and removing duplicates. In addition, custom transformations specific to a particular application can be created.

Glazed Lists is designed for high-performance with large datasets, see the sidebar "Implementing Fast Filtering."

Event listeners implement list transformations in Glazed Lists. Event listeners implement the Observer pattern, the same mechanism Swing uses to send notification of button clicks and other events. When a new element is added to a list, the list sends a notification event to all of its listeners. Listeners are used for both list transformations and for updating the user interface. Take, for example, a list of customers that appears in a JTable—when we add to the list, the JTable receives a notification event and updates the display automatically.

Create a filterable application

Let's create a simple application that displays customers in a JTable. This table supports text filtering using Glazed Lists. See Resources to download this example's full source code.

Create an EventList

We are interested in creating an observable list of customers. This list sends notification events when it changes. These notifications update the transformed lists.

The EventList interface extends java.util.List with support for event listeners such as list transformations and Swing components. The BasicEventList class is an EventList, with the same add(), remove(), and set() methods as ArrayList.

For simplicity let's track only the customer's name and location:

public class Customer {
   public String name;
   public String location;
   public Customer(String cName, String cLocation)
      name = cName;
      location = cLocation;
   }
}

Now that there is a Customer class, create a list of customers. Elements can be obtained from Java Database Connectivity (JDBC), Web services, an XML file, or anywhere else. For this example, let's create a small list in code and add the elements to a BasicEventList:

import ca.odell.glazedlists.*;
public class CustomerBrowser {
   public static void main(String[] args) {
      BasicEventList customers = new BasicEventList();
      customers.add(new Customer("Jesse Wilson",   "Waterloo"));
      customers.add(new Customer("Wilson Harron",  "Guelph"));
      customers.add(new Customer("Naomi Williams", "Guelph"));
   }
}

Add filtering

We are now ready to transform the observable customers list. Create a text filtered transformed list from the original list. This transformed list shows only customers that match the current text filter. As the text filter is edited, the contents of the transformed list change to match. Should the original customers list change, the filtered transformed list observes the change and updates itself automatically.

The TextFilterList class is a transformed list that adds filtering to any EventList such as a BasicEventList. The TextFilterable interface enables the specification of the strings to be searched within a particular object.

From a list of customers, create a filtered transformation of that list. Text filtering is added in two steps: First, specify the strings—that Glazed Lists will search through—from the Customer object. This requires Customer implementing the TextFilterable interface. In the interface's only method, getFilterStrings(List), simply add all the strings in Customer to the parameter List:

import java.util.*;
import ca.odell.glazedlists.*;
public class Customer implements TextFilterable {
   ...
   public void getFilterStrings(List baseList) {
      baseList.add(name);
      baseList.add(location);
   }
}

The second step to building the filtered transformation list is constructing the filtered list itself. TextFilterList is in Glazed Lists' swing package because it comes ready-to-use with its own JTextField to type filter text in:

import ca.odell.glazedlists.*;
import ca.odell.glazedlists.swing.*;
public class CustomerBrowser {
   public static void main(String[] args) {
      BasicEventList customers = new BasicEventList();
      ...
      TextFilterList filteredCustomers = new TextFilterList(customers);
      JTextField filterEdit = filteredCustomers.getFilterEdit();
   }
}

JTable display

Just like the original customers list, the text filtered transformed list is observable. We display the transformed list in a JTable. When the transformed list changes, the JTable observes the change and repaints the display automatically.

The EventTableModel class is a simple TableModel that can be used to display any EventList in a JTable. The EventTableFormat interface allows you to specify how to divide an Object's fields into a table's columns.

We have populated a list and created a filtered transformation of that list. Now let's use Glazed Lists' EventTableModel to display the filtered list in a JTable. This class already maps elements of the list to the JTable's rows. We must write the code that specifies how the Customer fields shall map to the table's columns. The TableFormat interface codifies this column mapping. Three simple methods specify the number of columns, the column headers, and the values for each column:

import ca.odell.glazedlists.swing.TableFormat;
public class CustomerTableFormat implements TableFormat {
   public int getColumnCount() {
      return 2;
   }
   public String getColumnName(int column) {
      if(column == 0) return "Customer Name";
      else if(column == 1) return "Location";
      else return null;
   }
   public Object getColumnValue(Object baseObject, int column) {
      Customer customer = (Customer)baseObject;
      if(column == 0) return customer.name;
      else if(column == 1) return customer.location;
      else return null;
   }
}

Now construct an EventTableModel with the CustomerTableFormat and filtered list of customers:

import ca.odell.glazedlists.*;
import ca.odell.glazedlists.swing.*;
public class CustomerBrowser {
   public static void main(String[] args) {
      ...
      CustomerTableFormat tableFormat = new CustomerTableFormat();
      EventTableModel tableModel = new EventTableModel(filteredCustomers, tableFormat);
   }
}

Finally, create a JTable with the EventTableModel and lay out everything in a JFrame. Display the frame, and the program is complete—see Figure 2.

Figure 2. Screenshot of the completed application

Advanced features

I have demonstrated how to create a simple application that displays and filters a list of customers. Glazed Lists is useful for more sophisticated, real-world applications. My example has only touched the surface of possibilities.

TextFilterator

Suppose you need to text filter a particular class, but cannot implement the TextFilterable interface in it. This happens when the class's source is unavailable or when coupling the class to the Glazed Lists API is undesirable. That class's filter strings can still be specified with the TextFilterator interface. Just as Comparable can be used to externally specify a sort order, TextFilterator can be used to externally specify filter strings.

AbstractFilterList

Although text filtering is a useful, general technique, sometimes an application-specific filter is more appropriate. With the AbstractFilterList class, filters can be created for any criteria. Extending classes must implement only one method, filterMatches(Object). This method returns true or false based on whether the specified object matches the current filter. This class eases the development of custom filters for an application.

JComboBox and JList

Even if the application does not use a JTable, Glazed Lists is still useful. Instead of an EventTableModel for a JTable, use an EventListModel for a JList or an EventComboBoxModel for a JComboBox. Neither component uses columns, so a TableFormat is unnecessary. However, a toString() method is needed because that method's return value will display.

Concurrency

One of Swing's most prominent problems is that it complicates the development of concurrent applications. Fortunately, EventTableModel and the other Swing classes are designed for use with multiple threads. For example, when an EventList is modified on a worker thread, any JTables that display the list will be notified on the Swing event dispatch thread. But thread-safety is not fully automatic. Each EventList has a method, getReadWriteLock(), which allows it to be accessed from multiple threads.

EventSelectionModel

Java's DefaultListSelectionModel has a flaw that prevents it from cooperating with filtering. When a row is inserted into a JTable, it becomes selected if immediately inserted before a selected row. This proves problematic if the rows of a JTable are selected when its filter changes. When the filter is removed, some restored rows may incorrectly become selected.

Glazed Lists includes EventSelectionModel, which has more predictable behavior. This class also has a method, getEventList(), which contains the JTable's current selection. As rows are selected, the corresponding elements are automatically added to that list.

Conclusion

Text filtering is a simple and powerful technique for data-intensive applications, and can be implemented as a list transformation. Glazed Lists is an open source toolkit for list transformations. It includes a list transformation for text filtering, plus sorting and removing duplicates. By combining these with application-specific list transformations, you can present data in new and useful ways.

Jesse Wilson is the lead developer of Glazed Lists. The project started as a toolkit to support a project for his team at O'Dell Engineering Ltd. in Cambridge, Ontario. He has been working there since completing his computer science degree at the University of Waterloo. Prior to university, Wilson was a member of the Dolphins Swim Club in Regina, Saskatchewan. He enjoys heavy metal music, casual sports, and Roughriders football.
Page 2 of 2

Learn more about this topic