Complex Table Cell Rendering Made Simple Blog

Version 2


    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 JTableto the application, create two DefaultTableCellRenderersubclasses, 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 moreinstanceofs.

    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:

    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; } 

    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 whosegetTableCellRendererComponent 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 subclassingDefualtTableCellRenderer, but this design does not scale well. As more types of objects are added, as the formatting rules get more complex, thegetTableCellRendererComponent() method gets longer and more brittle. This is because -- essentially -- the common design mixes the decision on how to display the value and theimplementation of how to display it. The order of theif clauses is highly critical, as theinstanceof 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 ofO(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 TableCellRenderers to draw their cells. This is an interface with only one method that gets six parameters and returns a 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 itsgetTableCellRendererComponent() 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 aTableCellRenderer (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 mapClass objects to TableCellRenderers. The setup of the renderer looks almost declarative:

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

    As you would expect, the ClassBasedDDR is built around a Map object, mapping class instances toTableCellRenderers. When itsgetTableCellRendererComponent method is invoked, theClassBasedDDR gets the appropriate renderer from the map using the passed value's class, and invokes that renderer'sgetTableCellRendererComponent 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 nullmight 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 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 aMap<Class<?>,TableCellRenderer>, to allow faster retrieval in subsequent cases. The actual code is short, but still too long to be listed here; look at thegetExplicitRenderer( 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:

    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. 

    Is imp more an Object or anAnInterface? I'd say the latter. By consideringimp'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 toObject.

    While the BFS is a "heavy" algorithm, possibly running atO(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 ofO(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 mappingImplementorInterfaceCellRenderer instance. If the client code would change the renderer mapping for AnInterface, the mapping forImplementor 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.

    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 thegetTableCellRendererComponent() method of the renderers presented here is four to six lines). Note that if you decide to subclass a component that is notDefaultTableCellRenderer, you need to overridevalidate, invalidate,revalidate, repaint, and thefirePropertyChange 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:

    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 

    The results can be seen in Figure 2.

    The Class-Based DDR in action
    Figure 2. The class-based DDR in action

    Here is the main method of BankAccountTCR:

    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; } 

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

    In fact, there are no ifs in the specific renderers. 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 newswitch()." (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 aTableCellRenderer whosegetTableCellRendererComponent() method returnsnull 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, theRuleBasedDDR passes the value to a default cell renderer to get some reasonable result. The main method is therefore rather straightforward:

    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); } 

    As before, the first implementation step to create the specific renderers. Our example uses a single class, a subclass ofDefaultTableCellRenderer 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):

    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) ); 

    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:

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

    The final result can be seen in Figure 3.

    The Class-Based DDR in action, with a rule based DDR for the <code>BankAccount</code>
    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.


    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. (Noifs!) They may even relieve the renderers from the pesky state cleanup at the top of thegetTableCellRendererComponent() 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.


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