Catalysoft   Turtle
home products articles about us contact us

Recent Articles

What's Good About Clojure?

Clojure is a relatively new language to appear on the Java Virtual Machine (JVM), although it draws on very mature roots in the form of the LISP langu ...

Should You Care About Requirements Engineering?

Recently, I (Adil) was invited to participate in a one day seminar on the subject of Requirements Engineering. Whilst I have no direct experience of t ...

Tips for Setting Up Your First Business Website

To attract all potential customers to your business you need a presence on the web. The problem is that if you haven't set up a website before, you p ...

What's Good about LISP?

LISP is a general-purpose programming language and is the second-oldest programming language still in use, but how much do you know about it? Did you ...

Open Source Tools for Developers: Why They Matter

From a developer's point of view use of open-source tools has advantages beyond the obvious economic ones. With the open-source database MySQL in mind ...

A TableModel with Class

Discuss this article >>>

Introduction

Anybody who has used Java's JTable component will have found out (probably through painful experience) that the DefaultTableModel (and AbstractTableModel) only ever returns Object.class in response to requests for the class of object contained in a column. In other words, the implementation does not live up to the promise of returning "the most specific superclass for the values in a column". This is particularly a problem when you start to use custom rendering of classes in the JTable, and is usually overcome by writing a custom TableModel that is pre-programmed to "know" the class of objects that it contains. I shall describe an alternative, generic approach to derive the most specific superclass, and to keep the return value of getColumnClass() consistent with its contents.

The javax.swing.table.TableModel interface is defined (in Java 1.4) as follows:

public interface TableModel {
    public int getRowCount();
    public int getColumnCount();
    public String getColumnName(int col);
    public Class getColumnClass(int col);
    public boolean isCellEditable(int row, int col);
    public Object getValueAt(int row, int col);
    public void setValueAt(Object aValue, int row, int col);
    public void addTableModelListener(TableModelListener l);
    public void removeTableModelListener(TableModelListener l);
}

The documentation for the getColumnClass() method states that it 'returns the most specific superclass for all the cell values in the column. This is used by the JTable to set up a default renderer and editor for the column.' However, it is disappointing to discover that the implementation of getColumnClass() in DefaultTableModel inherits the following method from AbstractTableModel:

public Class getColumnClass(int col) {
	return Object.class;
    }

It returns the Object class regardless of the contents of the table, and irrespective of the requested column! Of course, the Object class is the "root" class of all classes in Java, so the returned value is always meaningful. However, java.lang.Object is rarely the most specific superclass for the values in a column. In other words, AbstractTableModel makes no attempt to fulfil its obligation! It simply returns the Object class regardless of the contents of the table model. Generally, this deficiency is overcome by writing a custom TableModel that "knows" the class of object that it was designed to contain. However, this approach hard-codes the developer's design-time expectations into the source, and therefore limits reusability. In this article I show how to implement a generic table model that keeps its promise to return the common ancestor class of a column's instances.

A Naive Approach

The first step is to make the return value in some way dependent on the contents of the TableModel, and the easiest way to do this is to inspect the top cell of a column and return its class. We could therefore use the following method:

public Class getColumnClass(int col) {
  if (getRowCount() == 0) {
    return Object.class;
  } else {
    Object cellValue = getValueAt(0, col);
    return cellValue.getClass();
  }
}

Note the defensive style used to protect against the case when the table has no rows. This approach is fine provided the cells of your column are all of the same class (or, more precisely, assignable to the class of the first cell). This covers a large number of cases and has the advantage that it returns a value in constant time, regardless of the number of rows in the table. However, it is not general enough. There will be cases when the class of the first cell is not representative of the whole column, and this approach therefore returns the wrong result. Handling such cases requires more effort.

Computing the Correct Class

As an example, suppose we have three instances a, b, c of the respective classes A, B and C (which may or may not be different), and we would like to compute the most specific class for which all of a, b and c are (direct or indirect) instances. The first observation to make is that the class that we are looking for, which I shall call the least general unifier, could be one of A, B, C, it could be java.lang.Object, or it could be some other class (i.e., a superclass of each of A, B, C, but a subclass of java.lang.Object).

A sketch of an algorithm to determine the least general unifier is as follows. We begin by not knowing the least general unifier, so we assign a leastGeneralUnifier variable the value null. Then we inspect our values in turn, in this case, a, b and c. Upon inspecting a, the most specific class that covers all instances is the class A, so we change leastGeneralUnifier to become A. When we inspect b, we need to know whether its class, B, is a superclass, subclass or completely distinct from A. If B is the same class as A or a subclass of it, then our least general unifier already unifies all the instances so far encountered, so we leave it unaffected. If B is a superclass of A, however, then we make our leastGeneralUnifier B instead of A to cover all instances. If neither class is a superclass of the other, then we must determine the least general unifier by traversing upwards in the class hierarchy until we find a unifying class. We apply the same decision process when we inspect c, comparing C with the leastGeneralUnifier of a and b. Using this pairwise comparison procedure, we can generalize to any number of variables, and therefore find the least general unifier for a whole column of cell values in a TableModel.

So the general decision procedure is to carry out the first of the following actions that applies:

  • If we don't yet have a value for the least general unifier, then assign it the class of the current cell.
  • If the current cell's class is the same as the least general unifier or a subclass of it, then leave the value of the least general unifier unaffected.
  • If the current cell's class is a superclass of the least general unifier, then change the value of the least general unifier to be the class of the current cell.
  • If none of the above apply, then neither of the two classes is a superclass of the other. However, there may be a class, more specific than java.lang.Object, which is a common superclass of both classes. We can search for such a class by recursively traversing up the class hierarchy until we reach java.lang.Object, checking each class along the way to see if it unifies both classes. It doesn't matter which of the two classes we start from; in either case we shall either reach java.lang.Object or, if it exists, we shall find the more specific unifier.

A method that encodes this decision procedure is as follows:

public Class getColumnClass(int column) { 
  int rowCount = getRowCount(); 
  Class leastGeneralUnifier = null; 
  for (int row = 0; row < rowCount; row++) { 
    Object value = getValueAt(row, column); 
    if (value != null) { 
      Class cls = value.getClass(); 
      if (leastGeneralUnifier == null) {
        leastGeneralUnifier = cls;
      } else if (leastGeneralUnifier.isAssignableFrom(cls)) {
        ; // do nothing
      } else if (cls.isAssignableFrom(leastGeneralUnifier)) { 
        leastGeneralUnifier = cls; 
      } else { // traverse upwards in the class hierarchy
        leastGeneralunifier = traverseForClass(leastGeneralUnifier, cls);
      }
    } 
  } 
  if (leastGeneralUnifier == null) { 
    leastGeneralUnifier = Object.class; 
  } 
  
  return leastGeneralUnifier; 
}

The source code that performs the traversal up the class hierarchy described in the last step of the decision procedure is as follows:

protected Class traverseForClass(Class start, Class unifyWith) {
  if (start == Object.class) { // halt the recursion
     return Object.class;
  } else {
     if (start.isAssignableFrom(unifyWith)) {
        return start;
     } else { // recurse up the tree
        return traverseForClass(start.getSuperclass(), unifyWith);
     }
  }
}

Keeping the Correct Class

In general we should inspect all the values of the column and derive a least general, but nevertheless unifying, class. The problem is that for large tables, this may be computation intensive, so it shouldn't be performed too often. Requests to getColumnClass() should return quickly, so we certainly can't recompute the value every time. It would be much better to listen to changes to the table model and recompute the class of the column in response to those changes. A simplistic approach is to eagerly recompute the class of the column in response to any change. However, It is more efficient to recompute the class lazily; that is, cache the value of the column class and set a (column-specific) dirty flag after every change to the table model such that when a request is made to getColumnClass(), the class is recomputed only if the dirty flag is set. That way, if there are a number of table model changes followed by a single request to getColumnClass(), then the new column class is computed only once.

We should give special consideration to changing cell values, as in practise a change to a single cell value would not often merit the recalculation of the column class from all column cells. Instead, whenever a cell's value is changed, we should test whether the new value is of the same class as the currently cached column class. If it is (or if the new value is null), then the cached column class can remain unchanged; otherwise, there are two cases. Firstly, if the new value's class is a superclass of the cached column class, then we must adopt the new value's class as the new column class. Otherwise, the new value must be either a subclass of the cached column class or a completely unrelated class. Whichever is true, we should recompute the column class. This may seem surprising for the subclass case, but remember that the previously cached class was computed by using the same cell's previous value. If the change was such that the cell value moved down the class hierarchy, then it is possible that the column class should also move down the class hierachy.

The following methods use a column-class cache and update those values in response to changes to the table. They use a table model's standard method signatures. Note that for significant changes, such as a structural changes, or a number of rows being updated, the whole cache is reinitialised, so that the column class is recomputed on receiving the next request. When a cell is updated, the fireTableCellUpdated() method behaves more intelligently, only marking the cache to be dirty when necessary. Note how the cache is kept up-to-date with the setCachedColumnClass() method.

public void fireTableDataChanged() {
     initialiseClassCache();
     super.fireTableChanged(new TableModelEvent(this));
}
 
public void fireTableStructureChanged() {
     initialiseClassCache();
     super.fireTableChanged(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
}
 
public void fireTableRowsUpdated(int firstRow, int lastRow) {
     initialiseClassCache();
     super.fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
     TableModelEvent.ALL_COLUMNS, TableModelEvent.UPDATE));
}
 
public void fireTableRowsDeleted(int firstRow, int lastRow) {
     initialiseClassCache();
     super.fireTableChanged(new TableModelEvent(this, firstRow, lastRow,
     TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
}
 
public void fireTableCellUpdated(int row, int column) {
     Class currentUnifier = getColumnClass(column);
     Object value = getValueAt(row, column);
     if (value != null) {
         Class newCellClass = value.getClass();
         if (newCellClass == currentUnifier) {
             // leave the column class unaffected
         } else if (newCellClass.isAssignableFrom(currentUnifier)) {
             setCachedColumnClass(column, newCellClass);
         } else {
             setCachedColumnClass(column, computeColumnClass(column));
         }
     }
     
     super.fireTableChanged(new TableModelEvent(this, row, row, column));
}

The caching and lazy evaluation of the class is implemented by the following:

private boolean[] dirtyColumnClass;
private Class[] columnClass;

protected boolean isColumnClassDirty(int column) {
  return dirtyColumnClass[column];
}
    
protected void setColumnClassDirty(int column, boolean value) {
  dirtyColumnClass[column] = value;
}
    
protected Class getCachedColumnClass(int column) {
  return columnClass[column];
}
    
protected void setCachedColumnClass(int column, Class cls) {
  columnClass[column] = cls;
}
    
public Class getColumnClass(int column) {
  if (isColumnClassDirty(column)) {
    setCachedColumnClass(column, computeColumnClass(column));
    setColumnClassDirty(column, false);
  }
  return getCachedColumnClass(column);
}

Conclusion

We can be truer to the interface of a TableModel than Sun's default implementation. That is, instead of having to encode an application-specific TableModel class that has some "built-in" knowledge of the classes that the table contains, we can write a generic method that computes the classes of its columns from the values that they contain. However, it would not be fair for me to finish the article here, as there are a couple of caveats to this approach, which are probably the reasons why Sun did not provide this solution already.

Firstly, computing the column class in the way I have described takes up some CPU cycles at runtime in a way that "compiled" knowledge of a column's class does not. Furthermore, the number of CPU cycles that it takes up is dependent upon the number of rows in the table. (This is logical: the more values a column contains, the more values need to be inspected to determine the most specific class for the column.) In other words, it could be inefficient for very large tables. At the time of writing, I have not tested the algorithm against larger tables, but my belief is that for all practical purposes the algorithm should not be limiting. Nevertheless, this may have been an important consideration for the Java developers at Sun.

Secondly, the implementation of getColumnClass() only ever returns a class and will never return an interface, even though the signature of the method allows it. The problem is that if we widen the scope of the problem to allow the return of an interface that works as a least general unifier, then the problem suddenly becomes much harder. Each class represented by the instances in a column can have only one superclass, but can implement any number of interfaces. In other words, to find the least general unifying class or interface, we can't simply travel up to the root of the class hierarchy from the class that we are trying to unify and guarantee to find the least general unifier along the way. Instead, when unifying with a class that has not yet been encountered, we would need to consider all the classes and interfaces that had already been encountered, and, in the worst case, inspect all the possible paths from their classes and interfaces to the roots of their superclass hierarchies. This is not tractable in real time and could be another reason for simply returning the Object class by default.

Discuss this article >>>


Simon White