Skip to main content

Complex Table Cell Rendering Made Simple

August 21, 2008

{cs.r.title}







Your client wants a table of his bank accounts. "Nothing fancy"
-- he says -- "the account number on one column and the balance on
the other." You nod professionally, say, "Sure, sir, we can do it,"
add a

"http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable

to the application, create two
"http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/DefaultTableCellRenderer.html">
DefaultTableCellRenderer
subclasses, wire everything, and
proudly ship it to your client. The next day, the phone rings. Your
client now wants the table to also list some stock portfolios he
has. You say, "Sure, sir, it is possible," and add your first
if (obj instanceof XXX) to the balance column's
renderer.

The next day, you get an email. Your client has thought about
this (why are they allowed to do that?) and now wants the table to
also list some oil wells and wind farms he recently bought. You
murmur something like "Okay" and add some more
instanceofs.

Two years and 759 requirement changes later, the balance column
cell renderer's getTableCellRendererComponent method
is a few hundreds lines long, and looks like this:

[prettify]
Component getTableCellRendererComponent(JTable table, Object obj,
                                        boolean isSelected,
                                        boolean hasFocus,
                                        int row, int column) {
   // Clear previous state
   setText("");
   setIcon(null);
   SetBackground( Color.WHITE );
   setBorder( null );

   // Render Specific classes
   if ( obj instanceof ClassA ) {
      ... render ClassA instance...
   } else if ( obj instanceof ClassB ) {
      ... render ClassB instance...
   } else if
   ...
   } else if ( obj instanceof ClassZ ) {
      ... render ClassZ instance...
   }

   ... possible post processing ...

   return this;
}
[/prettify]

On enterprise systems the situation is the same, plus the last
three people that worked on the renderer left the company ages
ago. Yours truly was once in charge of a cell renderer whose
getTableCellRendererComponent was about a thousand
lines long, setting the border at the top, the text 200 lines below
it, and possibly an icon 300 lines below that -- but my therapist
says I'm over most of it now.

What's Wrong with the Common Design

For simple cases, there's nothing wrong with subclassing
DefualtTableCellRenderer, but this design does not
scale well. As more types of objects are added, as the formatting
rules get more complex, the
getTableCellRendererComponent() method gets longer and
more brittle. This is because -- essentially -- the common design
mixes the decision on how to display the value and the
implementation of how to display it. The order of the
if clauses is highly critical, as the
instanceof operators have to be called in upwards
order in the class hierarchy. The renderer class is not reusable
(unless you regard copy and paste as a legitimate form of reuse).
It's very hard to unit-test such a renderer, and, of course,
there's the performance issue.

Performance is very important for displaying cells. Each
inefficiency in rendering a single cell is multiplied by the number
of cells displayed, which is quite a lot on one of today's big
screens. The runtime complexity of the common design is of

O(number
of classes)
-- and that's when you don't have a complex class
structure that makes you check interfaces that are not implemented,
as in
if ( (obj instanceof A) && ! (obj instanceof B)
)
. Some of the renderer's properties are set twice: to an
initial state at the top of the method, and later on when the
renderer is made to display the value.

Can we decouple the decision how to display a value and its
implementation without breaking Swing? Can we have reusable,
testable renderers without hindering performance? The short answer
is yes. For a longer answer, read on.

The Display Delegating Renderer: A Design Proposal

JTables use
"http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableCellRenderer.html">
TableCellRenderer
s to draw their cells. This is an
interface with only one method that gets six parameters and returns
a
"http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Component.html">Component
.
The class implementing TableCellRenderer itself does
not have to be a Component; this is a key concept in
the proposed design.

A DisplayDelegatingRenderer (DDR) holds a
collection of TableCellRenderers. When its
getTableCellRendererComponent() method is invoked, it
delegates the call to one of the renderers according to some
internal logic. We will look at two possible logics: class-based
and rule-based. Since a DisplayDelegatingRenderer is a
TableCellRenderer (composite design pattern), we can
cascade them, as we will see later.

Class-Based Display Delegation

A class-based DDR dispatches the data to a renderer according to
the class of the data. Its methods allow the users to map
Class objects to TableCellRenderers. The
setup of the renderer looks almost declarative:

[prettify]
ClassBasedDDR cbddr = new ClassBasedDDR();
cbddr.setRenderer( BankAccount.class,
                   new BankAccountCellRenderer() );
cbddr.setRenderer( OilWell.class,
                   new OilWellCellRenderer() );
cbddr.setRenderer( StockPortfolio.class,
                   new RealEstateCellRenderer() );
...
[/prettify]

As you would expect, the ClassBasedDDR is built
around a Map object, mapping class instances to
TableCellRenderers. When its
getTableCellRendererComponent method is invoked, the
ClassBasedDDR gets the appropriate renderer from the
map using the passed value's class, and invokes that renderer's
getTableCellRendererComponent method with the
parameters passed (hence "delegating renderer"). One would have
hoped for a single line implementation, say,
map.get(value.getClass()).getTableCellRendererComponent(...),
but life is never that simple.

Dealing with null

How should we display nulls? We can use the default
renderer, a dedicated renderer, or even throw a dedicated exception
-- each project to its own here. Returning a null might
seem like a compelling option, but bear in mind that while it
results in an empty cell when the standard swing look-and-feels are
used, it might result in a NullPointerException with
other LaFs (the Java API for TableCellRenderer does
not touch on this subject).

One would have hoped this sums up the corner cases, but life is
never that simple.

Dealing with Inheritance

When used in any medium-to-large application, our renderer will
have to deal with new subclasses that were not known to the
original programmer that set it up. For example, if a colleague of
ours decides to introduce an OffshoreOilWell class,
which extends OilWell, and instances of it start
popping up in the table model, the renderer will have to deal with
them in a proper manner.

The ClassBasedDDR uses a slightly altered "http://en.wikipedia.org/wiki/Breadth-first_search">breadth-first
search (BFS)
up the class hierarchy to find a class for which a
renderer was explicitly specified by the client code. The search
begins at the instance's class. The BFS properties ensure that the
renderer will find a closest match to the class passed (there might
be a few classes with the same distance from it). A renderer for
the class Object is specified at the constructor, to make
sure we always find a renderer eventually. The chosen renderer is
cached in a
Map,TableCellRenderer>, to allow
faster retrieval in subsequent cases. The actual code is short, but
still too long to be listed here; look at the
getExplicitRenderer( Class valClass ) method
of ClassBasedDDR in the sample code for the altered
BFS implementation.

The slight alteration to the BFS is that we consider the
interfaces implemented by a class before we consider its super
class. While this might seem counter-intuitive, it makes perfect
sense when considering the following case:

[prettify]
class Implementor implements AnInterface {
    ...
}
Implementor imp = new Implementor();

... // put imp in the table model

ClassBasedDDR cbddr = new ClassBasedDDR();
cbddr.setRendererForClass( AnInterface.class,
                           new InterfaceCellRenderer() );

... // display the table.
[/prettify]

Is imp more an Object or an
AnInterface? I'd say the latter. By considering
imp's interfaces before we consider its super class,
we dispatch the display request to the explicitly specified
renderer, rather than to the default renderer mapped to
Object.

While the BFS is a "heavy" algorithm, possibly running at
O(number-of-classes), it ensures a good choice of renderer when no
explicit specification exists. Since we cache the result, the BFS
is only run once per unspecified class. The cache allows the
ClassBasedDDR to dispatch the vast majority of the
display requests at a runtime complexity of O(1), regardless of the
class hierarchy structure.

But life is never that simple.

Invalidating the Cache

The above solution works well when there are no changes to the
class-to-renderer mapping. However, when such changes occur, the
cache needs to be invalidated. In the above code,
cbddr's cache map might include the mapping
Implementor

InterfaceCellRenderer
instance
. If the client code would change the renderer
mapping for AnInterface, the mapping for
Implementor should change as well.

In order to solve this problem, ClassBasedDDR holds
two maps. One map keeps the explicitly set mappings, and is used by
the BFS when cache misses happen. The other is a cache, and is
cleared whenever a change to the explicit map is made. This
approach is easy to implement and actually fares well when the
changes to the explicit map are infrequent. Another approach might be
to have a single map, keep score of why each entry is there
(explicitly set or cached after a search), and remove the entries
accordingly. However, I'm not sure that all this bookkeeping would
result in a more efficient code.

Class-Based Example

Back to the client. We want to create a table of the assets the
client has, with names in the first column and relevant information
in the second column. The second column should display different
types of values in different ways, depending on the asset type. And
by the way, while you were reading, your client left a message
saying he wants the non-renewable energy assets displayed as a
progress bar, showing the percentage left in them. Figure 1 shows
the class hierarchy of the assets and the display requirements.

<br "Class diagram of the assets" />
Figure 1. Class diagram of the assets

The first implementation stage is to create a different table
cell renderer for each display requirement -- we will call them
"specific renderers." Since each renderer deals with one class
only, they will usually be very short and with very simple
behavior, making them easier to reuse and test (the length of the
getTableCellRendererComponent() method of the
renderers presented here is four to six lines). Note that if you decide to
subclass a component that is not
DefaultTableCellRenderer, you need to override
validate, invalidate,
revalidate, repaint, and the
firePropertyChange methods to no-ops for performance
reasons (on the ProgressBar subclass, I got about a
twofold performance rise on a Windows XP machine). If you are using
a container with subcomponents, you'll want to leave the
revalidation methods alone, though.

After the renderers are created, all there is to do is to create
an instance of the ClassBasedDDR, and assign renderer
instances to classes:

[prettify]
ClassBasedDDR cbd = new ClassBasedDDR();
cbd.setRenderer( BankAccount.class, new BankAccountTCR() );
cbd.setRenderer( StockPortfolio.class, new StockPortfolioTCR() );
cbd.setRenderer( Energy.class, new EnergyTCR() );
cbd.setRenderer( RenewableEnergy.class, new RenewableEnergyTCR() );

... // Assign the renderer to the right table column and display
[/prettify]

The results can be seen in Figure 2.

<br "The Class-Based DDR in action" />
Figure 2. The class-based DDR in action

Here is the main method of BankAccountTCR:

[prettify]
public Component getTableCellRendererComponent(JTable table, Object obj,
      boolean isSelected, boolean hasFocus,
  int row, int col)
{
    BankAccount value = (BankAccount)obj;
    setText( getFormat().format( value.getBalance() ) );
    adjustBackgroundColor(isSelected, table); // in superclass
    return this;
}
[/prettify]

Experienced Java programmers might frown at the fact that we
do not check whether the value we get is indeed an
instanceof the BankAccount class.
However, this is perfectly safe; we registered this renderer with
BankAccount.class so the class-based DDR ensures that
all the instances this renderer will get are instances of
BankAccount (or one of its subclasses). No need for an
if clause here.

In fact, there are no ifs in the specific
renderers
. "http://weblogs.java.net/blog/fabriziogiudici/archive/2007/12/if_you_loved_re.html"
target="_blank">Some people
(including yours truly) think this
is a good idea.

As an aside, storing executable code in maps allows the
programmer to get a switch-like branching behavior
based on any type of key. It is useful for more than just cell
rendering: XML parsers could "switch" based on the type of the node
being parsed, code going over a recordset could "switch" based on a
value of a certain column, etc. In addition, it allows us to modify
the branching conditions at runtime easily. The options are
endless! There is a reason why the recent Vogue Programming
Style
issue declared that "Map<> is the new switch()."
(OK, you got me, that didn't really happen.)

Let's take a closer look at how to change the branching logic at
runtime. In this case, we will change the rendering of all the
instances of a certain class.

Rule-Based Display Dispatch Renderer

Having seen too many soap operas, your client does not want his
evil stepbrother to know the exact amount of money he has. He asks
you to add a "verbal mode" to the application, where the balance in
the bank accounts is displayed by imprecise words (e.g.
"zilch,""not much," "quite a few").

The ClassBasedDDR cannot help us here; objects
cannot change their class on the fly. Enter rule-based display
delegation. A rule-based DDR maintains a list of rules. A rule is a
TableCellRenderer whose
getTableCellRendererComponent() method returns
null if the value passed does not match a certain
predicate. The rule-based DDR iterates over its list of rules,
passing each one the value, until some rule returns a component. In
case all the rules return null, the
RuleBasedDDR passes the value to a default cell
renderer to get some reasonable result. The main method is
therefore rather straightforward:

[prettify]
public Component getTableCellRendererComponent(JTable table, 
                   Object value, boolean isSelected, 
                   boolean hasFocus, int row, int column) {

   for ( TableCellRenderer r : renderers ) {
     Component c = r.getTableCellRendererComponent( table, value,
                     isSelected, hasFocus, row, column);
     if ( c != null ) {
       return c; // we found a rule that applies.
     }
   }
   
   // if we got here, no rule applied, use the default.
   return defaultRenderer.getTableCellRendererComponent( table, 
                       value, isSelected, hasFocus, row, column);
}    
[/prettify]

As before, the first implementation step to create the specific
renderers. Our example uses a single class, a subclass of
DefaultTableCellRenderer that holds a lower and upper
bounds of the balance it applies to; a string describing the
balance; and a color. We create a RuleBasedDDR, and
instantiate the rules like so (note that the order of the rules
matters):

[prettify]
RuleBasedDDR rbd = new RuleBasedDDR();
rbd.addRenderer( new BankAccountRule(-10,10,
                                    "Zilch",Color.LIGHT_GRAY) );
rbd.addRenderer( new BankAccountRule(-1000,0,
                                    "Ouch",Color.ORANGE) );
rbd.addRenderer( new BankAccountRule(-100000,0,
                                    "Oh Dear",Color.MAGENTA) );
rbd.addRenderer( new BankAccountRule(Float.NEGATIVE_INFINITY,0,
                                    "Good Grief",Color.RED) );
rbd.addRenderer( new BankAccountRule(0,1000,
                                    "A little",Color.BLACK) );
rbd.addRenderer( new BankAccountRule(0,100000,
                                    "Quite a bit",Color.GREEN) );
rbd.addRenderer( new BankAccountRule(0,Float.POSITIVE_INFINITY,
                                    "Gazzilion",Color.BLUE) );
[/prettify]

In order to switch between the verbal and numerical modes, our
application holds a reference to RuleBasedDDR and a
numerical BankAccountTCR used in the last example. It
also holds a reference to the ClassBasedDDR used in
the main table. After the initial setup, changing the display of
all the bank accounts in the table is as simple as:

[prettify]
protected void setVerbalMode( boolean isVerbal ) {
  ddcr.setRenderer(BankAccount.class,
                    isVerbal ? verbalRenderer : numericRenderer);
  table.repaint();
}
[/prettify]

The final result can be seen in Figure 3.

<br "The Class-Based DDR in action, with a rule based DDR for the BankAccount" />

Figure 3. The class-based DDR in action, with a rule-based DDR for
the BankAccount

Rule-based display dispatch renderers find the appropriate
renderer at O( number-of-rules ) in the worst-case scenario.
Thus, the ordering of the rules is important for the performance of
the display -- put rules that are likely to apply to more values at
the front of the list.

Conclusion

Class-based dispatching renderers allow us to create reusable
and testable cell renderers, and scale well with the number of
classes. They allow us to easily make on-the-fly changes to the
table display logic and makes the code more readable. (No
ifs!) They may even relieve the renderers from the
pesky state cleanup at the top of the
getTableCellRendererComponent() method, which is
usually messed up below anyway (double setBackground()
calls, etc.). The rule-based delegating renderer is a useful way of
dealing with complex formatting requirements, as long as the rule
evaluation is fast enough.

Rule- and class-based display delegation are just two
variations on a larger theme: having a collection of simple
renderers and a logic to delegate the rendering request to the
proper one. As long as the logic is fast enough and does not create
too much garbage to collect, this design can make the programmer's
life a bit simpler.

Resources

The author wishes to thank David Bock for his help in making
this article fit for human consumption.


width="1" height="1" border="0" alt=" " />
Michael Bar-Sinai is a Senior Software Architect at Be'eri Print
Related Topics >> Swing   |   

Comments

It's a very nice solution.

It's a very nice solution.

Is it possible to use the same patern for CellEditors? I ...

Is it possible to use the same patern for CellEditors?
I tried, but it seems there are some differences. When implementing TableCellEditor interface, there are more methods, that need to be overriden, and it's not so clear how, and when extending DefaultCellEditor a constructor requires a component.

Thank you for this very interesting contribution, I really ...

Thank you for this very interesting contribution, I really like the flexibility and tidiness of the code. Recently I wanted to add indeterminate progress bars as well and ran into some problems. First, a NPE was thrown similar to http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=4576f4cecc12b6d39...

It boils down to the following problem:

//
// If sizeChanged() returns true, updateSizes() is called
//
protected Rectangle getBox(Rectangle r) {
        int currentFrame = getAnimationIndex();
        int middleFrame = numFrames/2;

        if (sizeChanged() || delta == 0.0 || maxPosition == 0.0) {
            updateSizes();
        }
        ....
}

//
// sizeChanged() returns true if 'componentInnards' is null
//
private boolean sizeChanged() {
        if ((oldComponentInnards == null) || componentInnards == null)) {
    return true;
}
        ....
}

//
// In updateSizes(), componentInnards.width and componentInnards.height are used.
//
private void updateSizes() {
        int length = 0;

        if (progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
            length = getBoxLength(componentInnards.width,
                                                      componentInnards.height);
            maxPosition = componentInnards.x + componentInnards.width
                          - length;

        }
        ....
}

For some reason, when using indeterminate progress bars (I merely created a new class for the class based approach discussed above) the componentInnards become null and a NPE is thrown. The link above contains a workaround as well (simply overriding the ProgressBarUI to catch the NullPointerExceptions) which lets me compile the code - but the progress bar wont animate properly. Any idea on how to fix that? Updating the GUI is done on the EDT of course, so I'm guessing the problem lies somewhere else? I'd really appreciate any help or suggestions!

~Clemens

You probably got this fixed by now, but for future ...

You probably got this fixed by now, but for future readers:
The components seen in a JTable are not really there - the JTable uses renderers to draw itself. This works like a rubber stamps: in order to draw the cells, a component is updated, resized and repainted over and over again, once for each cell. By default, this is the javax.swing.table.DefaultTableCellRenderer - but in this article we see other options.

So, to achieve an animation, you need to have the JTable repaint the appropriate cells continuously. This can be done by either calling repaint() on the JTable (you can also specify the rectangle to be repainted to lessen the overhead), or by calling the fireXXXupdated method of the table's model. Do this in a timer every 250 milliseconds, and you get an animation effect.