Posted under Java
Permalink
Tags Generics, Gotcha, Java, Tip, Tutorial
This post refers to the section on page 16 of the Generics tutorial document by Gilad Bracha.
The example here uses the Cojen dynamic bytecode generation library in order to create custom sort comparators. The advantage of Cojen is that you end up with a fully compiled comparator and therefore do not incur the performance hit of reflective code. Furthermore, comparators generated by Cojen can be cached in a map so that they are only generated once. This particular example borrows some code originally used for Icefaces table sorting, which I adapted for use with Primefaces. Whilst Primefaces does have its own table sorting, I had some issues with it. I wanted visibility of the current sort column and direction, and I wanted control over the sorting – I wanted to reapply the sort to a table when rows were added to it from another table, so that it was always maintained in order. Furthermore, I wanted to maintain the current sort column and direction that had been selected by the user via the column headers when applying the sort when new rows were added. As I had trodden this route before for Icefaces, it was straightforward to move the code over and incorporate it into a standard table controller class which I was already developing.
One issue which I had not previously bottomed out in the Icefaces version was making the code fully generic and fully eliminating all the generic warnings when using a class literal as a run time type token. With Cojen, you call a static forClass method on its BeanComparator to construct a comparator, passing in the class and the comparator ordering details:-
if (beanComparator == null) {
beanComparator = BeanComparator.forClass(rowClass).orderBy(key);
comparatorMap.put(key, beanComparator);
}
In my first Icefaces attempt, I pulled the first row out of the current row list for the table, and passed that to Cojen to create the class. The problem with this was that I could not do it generically this way. My second attempt, with the Primefaces version, passed the class in to an init method generically. As this tied up all the generics loose ends correctly, everything was happy and the class was recognised as having the correct parameterised type. The cost of this was the need to pass it explicitly as an argument to an init method (or a setter), but this was simple and made the parameterised type clear and explicit. This is the correct approach and one that I will use from now on.
The following code fragments illustrate the mechanism:-
Call Site
…
private @Inject TableCtrl<Person, RowMetadata> personTable;
…
personTable.init(Person.class, RowMetadata.class, this);
TableCtrlImpl.java
@Dependent
public class TableCtrlImpl<C, D extends RowMetadata> implements Serializable, TableCtrl<C, D>, Iterable<C> {
…
private Class<C> rowClass;
…
private Map<String, BeanComparator<C>> comparatorMap = new HashMap<String, BeanComparator<C>>();
…
@Override public void init(Class<C> rowClass, Class<D> rowMetadataClass, TableCtrlEvent<C, D> tableCtrlEvent) {
rowMeta = new RowMetaMap<C, D>();
this.rowClass = rowClass;
this.rowMetadataClass = rowMetadataClass;
this.tableCtrlEvent = tableCtrlEvent;
}
…
@Override public void sort() {
if (sortColumn != null && !rows.isEmpty()) {
String key = (sortAscending ? “” : “-“) + sortColumn;
BeanComparator<C> beanComparator = comparatorMap.get(key);
if (beanComparator == null) {
beanComparator = BeanComparator.forClass(rowClass).orderBy(key);
comparatorMap.put(key, beanComparator);
}
Collections.sort(rows, beanComparator);
}
}
Leave a Reply
You must be logged in to post a comment.