Level: Intermediate Mitch Goldstein (mdgoldstein@hotmail.com), Vice President/Chief Strategist, Ketherware Inc.
01 Feb 2001 This article discusses the technique of model filtering. You can use this technique with the Swing component set to provide alternative views of model data without altering the underlying data. Filters can alter the apparent content of data elements, exclude data from being viewed, include extra elements in a set of data, or present elements in a different order. You can apply filters to either data or state models, and you can layer filters to combine their effects.
Introduction
Model filtering is a technique that provides an additional level of power and flexibility within the Swing component architecture. One of the important innovations of the Swing architecture is adoption of the principles of Model/View/Controller (MVC), which allow the different roles of components to be separated. When the MVC separation is a characteristic of an architecture, the data and state of a component can be interpreted differently. This allows the programmer to interpose filter objects between the component and its models. Model filtering can modify the representation of data within a model, and can also change the apparent number and order of the data it is encapsulating. Two other characteristics of model filters are important to note:
- Model filtering operates without altering the underlying model data. This allows one set of data to be shared among multiple components, each of which may interpret the data in a
different manner.
- Filters can be layered, enabling model data to be interpreted through several different filter objects.
Delegates defined
To maximize the Java platform's support for object orientation, it is convenient to think of a component as being composed of several objects. These objects can be described by a general term -- delegates. A delegate is an object that implements a common Java interface, and that is associated with a particular component. The interface that a delegate implements defines the role a delegate plays in the MVC architecture. The idea of delegates seems to be intimidating to programmers newly introduced to Swing, but they have been a common characteristic of the AWT generation of components as well. For example, if you want to change the font that appears in a java.awt.Label component, you merely have to create or acquire an instance of the java.awt.Font class and associate it with the component by calling getFont(). The inner workings of the Font object may be intriguing, but a component simply requires a reference to an object of Font type to render itself appropriately. Even a straightforward concept such as the foreground color of the label is driven by delegation; the java.awt.Color class provides an object that is suitable as the value of the component's foreground. As a general rule, all component properties that have a non-primitive value can be considered delegates. Implementation of MVC in Swing is an elaboration of these concepts. Instead of using objects to represent only the values of a component's properties, they represent many of the aspects of a component's behavior as well. This scheme is flexible enough to allow for the realization of the Pluggable Look-and-Feel (PLAF) capability of Swing, which allows applications to appear to emulate a native platform or display components in a platform-neutral scheme. PLAF allows applications to appear as if they were native to Microsoft Windows, Mac OS, and X/Motif, or appear in a neutral Look-and-Feel, known as the "Java" or "Metal" LAF. The PLAF capabilities relate strictly to the appearance of the components. The model portion of the architectural scheme, which is independent of a component's appearance, is the main focus of this article.
Being a model (or just looking like one)
Each Swing component that supports data and state has a model interface associated with it. This interface contains methods that allow the component to programmatically query the contents of the model, whether it is interested in the data or the state encapsulated in the model. Each model interface provides two types of methods: those that provide access to data and state, and those that allow components or other objects to register and de-register event listeners. The types of listeners and the event objects they provide are defined by these methods. The Swing model interfaces have different types of class implementations. In many cases, an abstract implementation is given for a model; this is generally a canonical implementation that is incomplete, except for the presence of protected methods for firing the various event methods that the model represents. All models have a default implementation, which is concrete.
Nice and simple -- The ListModel interface
Before entering a discussion of filtering, it seems wise to provide a review of a typical model interface. The ListModel interface represents the data in a JList component. This is the simplest of the three collection models. (The other two are JTree and JTable.) ListModel contains two methods for retrieving the count of elements and the individual elements in the list and two methods for maintaining the list of interested listeners to list model changes. Simplified source for ListModel
package javax.swing;
import javax.swing.event.ListDataListener;
public interface ListModel
{
int getSize();
Object getElementAt(int index);
void addListDataListener(ListDataListener listener);
void removeListDataListener(ListDataListener listener);
}
|
In the ListModel interface, the methods getSize() and getElementAt() are used to traverse the elements in the model, while the other two methods are used to associate interested listeners for model changes. The ListDataListener interface supports three methods, which the model invokes when it detects changes in its underlying data. These methods are intervalAdded(), intervalRemoved(), and contentsChanged(), each of which takes a single ListDataEvent as an argument. Depending on the complexity of the changes to the model, the model implementation can use any of these methods to describe these changes. Generally, intervalAdded() and intervalRemoved() are used to describe an interval of changes; contentsChanged() is used when the changes are too complex to describe as a closed interval. To understand how model filtering operates, keep this in mind: the JList component is only interested in the implementation of the ListModel API. Where the data resides and how the data is internally organized is not of interest to the component at all. Whether the model is a default class, an extension of an abstract class, or a direct implementation of the ListModel interface
does not affect the way in which the JList component behaves.
Fundamentals of filtering
The essential concept of model filtering exploits a Swing component's lack of awareness of the underlying implementation of the model class. This diagram explains the typical relationship:

A model filter is a class that implements a model interface, but does not actually contain data. Instead, a model filter intercedes between a component and its model. The model filter can reinterpret the information that the model provides and can change the data itself, the number of data elements provided, or the order of the data.

In this example, the filter class is instantiated with an existing model class as the source of its data. In the general implementation of a model filter, the calls to the API methods will delegate to the source model. Because the API is consistently implemented, it is feasible to "stack" multiple filters between the component and its model. Note that each layer of filtering requires every API call to pass
through an additional layer of indirection; these layers could result in a performance bottleneck if they are overly complex.
The basic filter
The abstract class shown below is the base class for model filters that operate on the JList component. Its single constructor requires that each instance of the model filter refer to some
underlying model data. This data may or may not be another model filter; the behavior is the same in either case. Model filter base class
package com.ketherware.models;
import javax.swing.*;
public abstract class AbstractListModelFilter extends AbstractListModel
{
// Storage of reference to model being filtered
protected ListModel delegate;
// Constructor - takes a single parameter containing
// a reference to the model being filtered
public AbstractListModelFilter(ListModel delegate)
{
this.delegate = delegate;
}
public ListModel getDelegate()
{
return this.delegate;
}
public int getSize()
{
// delegate to filter target
return delegate.getSize();
}
public Object getElementAt(int index)
{
// delegate to filter target
return delegate.getElementAt(index);
}
public void addListDataListener(ListDataListener listener)
{
// delegate to filter target
delegate.addListDataListener(listener);
}
public void removeListDataListener(ListDataListener listener)
{
// delegate to filter target
delegate.removeListDataListener(listener);
}
}
|
This class is equivalent to a "null" filter, which does not alter any of the underlying data. Thus it is not particularly interesting. Practical implementation of the ListModel filter class will override the methods in this abstract class to present the underlying data differently. You can implement filters to change the nature of the underlying data events. To simplify this discussion of model filters, the examples in this article are for immutable data models, that is, classes that provide no firing of any model events. The default models are suitable for average, low-demand uses. Realize, however, that these default classes are designed to be generic, and generally do not do well under heavy performance conditions. Also, many of the commonly used models are implemented to be mutable, that is, the data in the model is expected to change over time. These additional behaviors may be unwanted when data is known to be static. Thus, you may want to construct additional model classes that omit the additional overhead incurred by event propagation.
Immutable models
In many cases, it is useful to categorize models depending on whether their underlying data is capable of changing. In the event that the data is unlikely to change, you can implement an immutable data model in which the data change listeners are not implemented. The default implementation of the Swing model interfaces are presumed to be mutable. Creating an immutable model is fairly straightforward. You can create a concrete class that provides the model interface, but implements no-op methods for all event-related activity. Implement the immutable model as an abstract or concrete class, depending on whether the model is intended to be general or specific. The example below of an immutable list model is designed to be very generic and allows any object that supports the java.util.List collection interface to serve as the data source. The data returned is an opaque Object type; it is up to the JList and its associated renderer to interpret how to display the object. Immutable model example
package com.ketherware.models;
import java.util.*;
import javax.swing.*;
public abstract class ImmutableListModelFilter extends AbstractListModel
{
// Storage of reference to model being filtered
protected List collection;
// Constructor - takes a single parameter containing
// a reference to the model being filtered
public AbstractListModelFilter(List collection)
{
this.collection = collection;
}
public List getCollection()
{
return this.collection;
}
public int getSize()
{
// delegate to collection
return collection.size();
}
public Object getElementAt(int index)
{
// delegate to filter target
return collection.get(index);
}
public void addListDataListener(ListDataListener listener)
{
// Override to a 'no-op'
}
public void removeListDataListener(ListDataListener listener)
{
// Override to a 'no-op'
}
}
|
We will now discuss four types of filters: alteration, ordering, exclusion, and inclusion.
Alteration filters
The purpose of an alteration filter is to reinterpret model data and represent it by varying the object element that is returned. This type of filter does not change the order of the data elements, nor does it eliminate or create additional data. The example below of an alteration filter adds a numeric index to each item in the underlying model. The only change is an override of a single method. Alteration filter example
package com.ketherware.models;
import javax.swing.*;
public abstract class IndexingListModelFilter extends AbstractListModelFilter
{
public Object getElementAt(int index)
{
// delegate to filter target
String element = delegate.getElementAt(index).toString();
return Integer.toString(index) + _) _ + element;
}
}
|
In many cases, it might be more appropriate to introduce added features, such as adding a row index, in the renderer. You can provide a filter that interacts with a renderer to provide additional graphic representation. The advantage of using a filter instead of a renderer is that the indexed data can be displayed by a component without the need for association with a renderer. An alteration filter typically does not override getSize() and does not change the order of elements returned.
Ordering filters
Ordering filters represent the next level of complexity. They are similar to alteration filters in that they do not change the number of elements that are represented. Ordering filters change the sequence in which elements in the model are indexed. The essential technique is to create an alternative index of the model's elements, which is used instead of the actual order. A common type of ordering filter is a sorting filter, which re-indexes data based on some defined sorting sequence. The example below alphabetically sorts the content of any ListModel implementation. Ordering filter example
package com.ketherware.models;
import java.util.*;
import javax.swing.*;
public abstract class AlphaSortingListModelFilter extends
AbstractListModel
{
// Array of sorted indices
protected ArrayList sortedIndex;
public AlphaSortingListModelFilter(ListModel delegate)
{
this.delegate = delegate;
resort();
}
// This sort algorithm is called an 'insertion sort' and is
// suitable for dealing with data of less than a couple of
// hundred records. It is a 'stackless' sort.
protected synchronized void resort()
{
sortedIndex = new ArrayList();
nextElement:
for (int x=0; x < delegate.getSize(); x++)
{
for (int y=0; y < x; y++)
{
String current =
delegate.getElementAt(x).toString();
int compareIndex =
((Integer) sortedIndex.get(y)).intValue();
String compare =
sortedIndex.get(compareIndex).toString();
if (current.compareTo(compare) < 0)
{
sortedList.add(new Integer(x), y);
continue nextElement;
}
}
sortedList.add(new Integer(x));
}
}
public Object getElementAt(int index)
{
// delegate to filter target, but use the sorted index
return delegate.getElementAt(sortedIndex[index]);
}
}
|
You can use an ordering filter with the JTable component to provide column-oriented sorting of tabular data; the code for that filter would be similar to the example code above. This filter can be further enhanced by modification of the JTable header and table model components. Note that the above example is only valid for immutable list models. If the data were changing dynamically, additional support would be required to modify the indices that are propagated by the ListDataEvent objects when events are fired. This does complicate the filter significantly, and its implementation is left to you as an exercise. The primary characteristic of ordering filters is that they do not increase or decrease the
number of visible elements in a model, so getSize() will delegate to the filtered model. They generally do not alter the data elements, but simply interpret the index of the element according to some alternative sequence.
Exclusion filters
The last two types of filters are quite similar, but have very different purposes. Both inclusion and exclusion filters allow the data elements of a model to be restricted or enhanced with additional elements.
Exclusion filters make certain elements of a model appear to be non-existent. These filters are exceptionally useful in situations where a single source of model data is available,
but the implementation only requires a subset of the data to be displayed. For an example of a typical exclusion filter, see
TerritoryListModelFilter.java
. The scenario presents a list of sales territories, each of which is associated with a salesperson. When the name of the salesperson is selected, the filter presents only the territories with which the salesperson is associated.

The advantage of this scenario is clear: without filtering, it would be necessary to reload the data model each time a different salesperson is selected, or keep a number of cached model instances. The filter can even allow two different components to view the same base model with two interpretations.
Inclusion filters
Although not as broadly applicable as exclusion filters, inclusion filters can be created to add information into a model. The best use for these filters is in reporting applications, since this type of filter can be used for totaling or subtotaling. A filter that does totaling creates a virtual element that appears at the end of the list model. To accomplish this, the filter tweaks the value of the model size by one, and routes requests for all elements but the last to the delegate. The example in
SalesTotalListModelFilter.java
assumes the list data is immutable; list data events are ignored by the filter. The TerritoryListModel in the previous example is used again here.

Conclusion
These examples have shown some of the applications of model filtering. Filtering is a concept that has much wider applicability than the relatively simple cases in this article. As you begin to implement filters, keep in mind these points:
- Filtering can provide different views of data models to different components and can reduce the number of full-fledged model instances that an application must support.
- Filtering can be applied to other models that Swing supports, including selection models.
- You can construct very intricate filtering schemes for dealing with mutable or dynamic models. To accomplish this, the events propagated by the delegate model can be processed by a filter.
- You can nest, or layer, filters without limit, but each layer adds an additional quantum of processing each time the model is modified or queried.
Resources
About the author  | 
|  |
Mitch Goldstein is a consultant, lecturer, author, and instructor, specializing in design and development of applications using the Java language and the Swing component architecture. Mitch is the author of Hardcore JFC - Conquering the Swing Architecture. He is a popular speaker on various Java programming topics. Mitch can be reached at mdgoldstein@hotmail.com. |
Rate this page
|