This class provides generic filtering and sorting capabilities which are compatible with the standard Java collections.
Filtering is a fairly straightforward concept. Given a collection of objects, iterate over the collection and determine if the specific object should be included or discarded based on specific filter criteria.
Sorting is also pretty straightforward. Given an ordered
collection of objects, reorder the objects such that each
one is relative to the others based on specific criteria.
The Java collections already support basic sorting via the
java.lang.Comparable
and
java.util.Comparator
interfaces. This package
provides a way to dynamically order collections without
actually writing the comparison routines, based on the
values of given object properties.
This package supports different types of filtering, depending on where the filtering should occur. The first, and simplest, type of filter allows the user to provide a class implementing the Filter interface to provide a specific, hard-coded test.
For example, given a List instance containing String instances, the application would like to only display those strings which start with the literal 'filterme'. While the {@link com.townleyenterprises.filter.Filters} class provides a static method to encapsulate the details, an alternate solution to the problem could be provided by the following code:
Iterator collIterator = stringList.iterator(); FilteredIterator fi = new FilteredIterator(collIterator, new Filter() { public boolean doFilter(Object o) { if(!(o instanceof String)) return false; String s = (String)o; return s.startsWith("filterme"); } }); // print our strings while(fi.hasNext()) System.out.println(fi.next());
The mechanics of applying the filter are encapsulated in the
{@link com.townleyenterprises.filter.FilteredIterator}
instance which is used to provide a Decorator or Wrapper for
a standard Java Iterator instance. Internally, the
FilteredIterator
is accessing the next element
and applying the filter. If the filter succeeds, the
element is returned when the user of the iterator calls the
next
method.
The full power of the filtering mechanism in the package comes from the various logical filters. Using combinations of the AND, OR and NOT filters, complicated expression parse trees may be created and applied as easily as the simple filter shown in Example 1.
To apply the logical filters to a collection of user defined objects in order to select only a part of the collection, the same procedure is employed as for the simple filters. This approach is illustrated in Example 2 below:
// filter1 and filter2 are defined earlier LogicalAndFilter and = new LogicalAndFilter(); and.addFilter(filter1); and.addFilter(filter2); FilteredIterator fi = new FilteredIterator(list.iterator(), and); while(fi.hasNext()) System.out.println(fi.next());
In Example 2, only those objects in the collection where both filter1 and filter2 return true will be printed. The logical OR filter is equivalent in setup and use to the logical AND filter, but it will include objects in the results if either filter evaluates true.
The filters described in Example 1 and Example 2 are tightly bound to the application in which they are used. The filter implementation must know the underlying structure of the objects so that it can perform the filtering. This approach is not always desirable for general use selections such as a string property being equal to another. For this reason, the package provides an implementation of the QueryObject design pattern to address these general cases.
The dynamic filters rely heavily on the java.lang.Comparable
interface and can only be used with classes which implement
it. Fortunately, all of the interesting types in the J2SDK
implement it, so it works automatically with all of the
primitive and built in wrappers for the primitive types.
The {@link com.townleyenterprises.filter.QueryFilter} class
can be used for all but substring searches if exact matches
are required. For example, given a custom class with
properties code
and value
, to find
all of the objects which had a code of 'EUR'
and values of less than 1000.00, a filter could be created
and applied to the collection as illustrated in Example 3.
QueryFilter code = new QueryFilter(Widget.class, "code", QueryOperator.EQ, "EUR"); QueryFilter value = new QueryFilter(Widget.class, "value", QueryOperator.LT, new BigDecimal("1000.00")); LogicalAndFilter and = new LogicalAndFilter(); and.addFilter(code); and.addFilter(value); Collection widgets = Filters.filter(list, and);
The resulting collection of widgets would all have currency codes of 'EUR' and have values of less than 1000.00.
Sorting is often used in conjunction with filtering, but the
sorting facilities do not depend on the filtering
mechanisms. Like the QueryFilter
, the {@link
com.townleyenterprises.filter.PropertySorter} class depends
on the underlying objects implementing the
java.lang.Comparable
interface. Like the
QueryFilter
, the PropertySorter
automatically works with any of the base types provided by
the Java language.
An example of using the sorting facilities outside of
filtering would be when working with a Java Swing table
whose underlying table model is represented by a list of
custom objects. For simplicity, take the Widget class used
for Example 3. If the Widget instances were displayed in a
sortable table. The available properties are Stock
No
, Description
, Quantity
and Color
in addition to the Code
and Value
from above. In order to sort the
columns to see which ones should be discounted, the
following code could be used:
// define the sort so we can se the largest quantity // on hand at the top of the list SortSpecification[] sort = { new SortSpecification("quantity", SortOrder.DESCENDING), new SortSpecification("description"), new SortSpecification("code"), new SortSpecification("value") }; Collections.sort(tableData, new PropertySorter(Widget.class, sort));
Additionally, filtering and sorting can be done at the same time using the static {@link com.townleyenterprises.filter.Filters} utility class. To combine Examples 3 and 4, only the following changes would need to be made:
QueryFilter code = new QueryFilter(Widget.class, "code", QueryOperator.EQ, "EUR"); QueryFilter value = new QueryFilter(Widget.class, "value", QueryOperator.LT, new BigDecimal("1000.00")); LogicalAndFilter and = new LogicalAndFilter(); and.addFilter(code); and.addFilter(value); // define the sort so we can se the largest quantity // on hand at the top of the list SortSpecification[] sort = { new SortSpecification("quantity", SortOrder.DESCENDING), new SortSpecification("description"), new SortSpecification("code"), new SortSpecification("value") }; List widgets = Filters.filter(list, and, sort);
At the present time, the following limitations/issues are present in the package: