Skip to main content

Should we generify Swing's filtering and sorting?

Posted by zixle on August 25, 2005 at 4:39 PM PDT

I was tempted to name this blog Tricks and Subtleties of generics but I figured more people would be interested in providing feedback on mustang sorting. Anyway...

One of the bigger features we've added to Swing for the 1.6 release is sorting and filtering for JTable. The sorting and filtering API were designed in a way so that it is not tied specifically to JTable. In other words in a future release the same APIs will be useful for JList, JTree and possibly JComboBox. If you haven't played with it yet head over to the JDK development page for mustang and download the latest bits. Or if you just want to look at the documentation for the major classes, look to RowSorter, DefaultRowSorter, TableRowSorter and RowFilter.

We took a distinctly different path with filtering/sorting than with similar aspects of Swing. In particular RowFilter takes an instance of Entry that is meant to wrap the underlying model. This allows for instances of RowFilter to be used regardless of the underying model. This is useful in that you could create a filter for regular expressions that would be useful regardless of the underlying model type. This is different from the renderers in which you have a distinct interface for each component type.

Here's a quick example that will only show rows where some column contain 'foo':

  TableRowSorter sorter = new TableRowSorter(tableModel);
  sorter.setRowFilter(RowFilter.regexFilter("foo")); *
  table.setRowSorter(sorter);

* In current mustang builds (as the time of this blog) the regexFilter uses match for inclusion, it's going to be changed to use find. Before this change is done this example would need to be ".*foo.*".

While RowFilter doesn't directly take the underlying model, you can dig if out if you need to. Here's the example in the javadoc:

  RowFilter ageFilter = new RowFilter() {
    public boolean include(Entry entry) {
      PersonModel personModel = (PersonModel)entry.getModel();
      Person person = personModel.getPerson((Integer)entry.getIdentifier());
      if (person.getAge() > 20) {
        // Returning true indicates this row should be shown.
        return true;
      }
      // Age is <= 20, don't show it.
      return false;
    }
  };

In the mustang forums Remi has suggested we consider generifying RowFilter. The hope with generifying is that it would avoid the casts, as well as adding type safety.

Here comes the fun, and were I would like your feedback.

RowFilter takes an instance of Entry (static inner class of RowFilter). Generification would have to happen here. One possible way to change it would be to go from:

  public static abstract class Entry {}

To:

  public static abstract class Entry<M, I> {
    public abstract M getModel();
    public abstract I getIdentifier();
  }

But to be useful RowFilter must also be similarly generified:

  public abstract class RowFilter<M,I> {
    public abstract boolean include(Entry<M, I> entry);
  }

And DefaultRowSorter would be generified around the type of model:

  public class TableRowSorter<M extends TableModel> extends DefaultRowSorter<M> {
    public M getTableModel();
    public void setRowFilter(RowFilter<M,Integer> filter);
  }

After all this the example in the javadoc would become:

  RowFilter ageFilter = new RowFilter<PersonModel,Integer>() {
    public boolean include(Entry<PersonModel,Integer> entry) {
      // Notice the cast to PersonModel is gone
      PersonModel personModel = entry.getModel();
      // And the cast here is gone to
      Person person = personModel.getPerson(entry.getIdentifier());
      if (person.getAge() > 20) {
        // Returning true indicates this row should be shown.
        return true;
      }
      // Age is <= 20, don't show it.
      return false;
    }
  };

Notice the casts are gone (although one could argue we've just shifted them to the constructors), and you have type safety. You couldn't set this filter on a rowsorter that was using a different model or providing a different identifier. Sounds good, right?

Well, turns out this generification isn't quite right. In particular a RowFilter that doesn't require a specific model or identifier would no longer work with this generification. For example, you couldn't use a RowFilter<Object,Object> with TableRowSorter<PersonModel>, which isn't right. Back to Gilad's tutorial for another pass.

A RowFilter should be able to filter any models that extend the declared model, or likewise extend the declared identifier. This implies:

  public abstract boolean include(Entry<? extends M, ? extends I> entry);

And similarly the setter for the RowFilter should allow any filters that can handle a superclass of the specified model, or superclass of Integer. For example, you should certainly be able to use a RowFilter<Object,Object> with a TableRowSorter<PersonModel>. This implies:

  public void setRowFilter(RowFilter<? super M,? super Integer> filter);

That leaves us with RowFilter as:

  public abstract class RowFilter<M,I> {
    public abstract boolean include(Entry<? extends M, ? extends I> entry);
  }

TableRowSorter as:

  public class TableRowSorter<M extends TableModel> extends DefaultRowSorter<M> {
    public M getTableModel();
    public void setRowFilter(RowFilter<? super M,? super Integer> filter);
  }

So, this will certainly allow you to have more type safety in your RowFilter, at the cost of readability. What do you think?

Other generics quirks:

As part of this change DefaultRowSorter will also be generified around the model type. DefaultRowSorter makes use a private inner class to aid in sorting:

    private class Row implements Comparable<Row> {

DefaultRowSorter creates an array of these:

  Row[] viewToModel = new Row[size];

But once I gerified DefaultRowSorter this array creation didn't work. In particular it produces a compile error. Why you ask? Turns out you can not create an array of a generic class and as DefaultRowSorter is considered a generic class, so is Row. The fix is to make it static and take an instance of the outer class:

  private static class Row implements Comparable<Row> {
    public Row(DefaultRowSorter sorter, int index) {}
  }

Painful and obscure to say the least. Alternatively if I were using a collection class, say an ArrayList<Row> this would not have been an issue.

Special thank to Peter von der Ahe and Scott Seligman for helping out with my various generics questions and proof reading. And another thanks to Graham for quickly pointing out I missed escaping all the < and >s.

    -Scott