Easy high performance tables with reflection data binding

Article code can be found at project code site, http://code.google.com/p/swingtools/.

Swing’s JTable is an extremely powerful component. It allows you do to almost everything with it, include any other components, draw and edit in each cell differently etc. Its model, TableModel, is not complex, but it requires certain amount of very boring and boilerplate code to implement a table for many situations. Most of the time, you have a list of objects where a single object presents one row in a table, and columns values are mapped to properties of that object. And for every new object type and new table, you have to write your TableModel yourself, repeating these lines of code again and again:

public class SimpleTableModel extends AbstractTableModel {

    private List data = new ArrayList();

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public int getColumnCount() {
        return 10;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        switch (columnIndex) {
            case 0:
                // either toString() or a slightly different data processing
                return data.get(rowIndex).toString();
                // many more case,case,case for each column...
            default:
                return "";
        }
    }
}

In general case, you only need those three methods to have a working table, if you’re satisfied with Excel-like column titles ‘A’, ‘B’,…,’AB’ etc. Most of the time titles are needed to be implemented as well, so this means another annoying ‘switch/case’ or preparing a list of column names so that they could be obtained by the index. There are no helper methods to obtain them directly from resources and that means you need to maintain table column names both in the code and in the resource bundle.

 

For smaller tables it seems OK to use the technique above, but if you have quite a few of them, writing the same code again and again become boring and that’s a good ground for starting to use notorious ‘copy-paste’ which means a lot of subtle bugs later. For large tables the code is monotonous set of large ‘switch/case’s, ‘if’s etc., which again is extremely low-level work and a fertile soil for a lot of bugs and mistypes. The worse thing is changing such models later, when you have different column order, names, or underlying data object has been re-factored and its properties are changed. It’s quite easy to introduce bugs and forget about many small things.

 

The obvious solution would be assigning columns directly to properties of an underlying data bean. The properties themselves have type which could become a type of a column – and that means automatic selection of an appropriate renderer and editor. It’s not hard at all to build such a table model and use it later for most of your table implementations.

public class BeanPropertyTableModel<T> extends AbstractTableModel {
    // class of the data in rows
    private final Class beanClass;
    // collection of table rows
    private List<T> data = new ArrayList<T>();
    // collection of column property descriptors
    private List<PropertyDescriptor> columns = new ArrayList<PropertyDescriptor>();

    public BeanPropertyTableModel(Class beanClass) {
        if (beanClass == null) {
            throw new IllegalArgumentException("Bean class required, cannot be null");
        }
        this.beanClass = beanClass;
        populateColumns();
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
        fireTableDataChanged();
    }

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columns.get(columnIndex).getPropertyType();
    }

    @Override
    public String getColumnName(int column) {
        return columns.get(column).getDisplayName();
    }

    @Override
    public int getColumnCount() {
        return columns.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        PropertyDescriptor descriptor = columns.get(columnIndex);
        T bean = data.get(rowIndex);
        return DynamicBeanUtils.getPropertyValue(
          bean, descriptor);
    }

    private void populateColumns() {
        BeanInfo info = null;
        try {
            info = Introspector.getBeanInfo(beanClass);
        } catch (IntrospectionException ex) {
            throw new RuntimeException(
              "Unable to introspect bean class", ex);
        }
        PropertyDescriptor[] pds = info.getPropertyDescriptors();
        columns.addAll(Arrays.asList(pds));
    }
}

This is again a simple table model derived from AbstractTableModel class which does some event support and stub methods for us. We have to pass a class of the bean we’re going to show in the table into the constructor as generics type erasure won’t let us know the type of T. Upon creating a model we get all properties available in the bean with the help of core JDK BeanInfo class. Every property is described by a PropertyDescriptor. Numbers of properties in the bean becomes number of columns. When passed a new list of beans into setData(), the model notifies listeners that its data has been changed.

 

The only interesting thing is how to get a property value. This is pretty easy and separated to be in own utility class as we may need it later:

public class DynamicBeanUtils {
    public static <T> T getPropertyValue(Object instance, PropertyDescriptor descriptor) {
        try {
            Method m = descriptor.getReadMethod();
            Object result = m.invoke(instance);
            return (T) result;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

There are no reasons to catch any exceptions here and they shouldn’t be thrown as bean properties are supposed to be accessible and public, so we’re converting them into unchecked ones.

This very initial and simple implementation is already able to show list of arbitrary beans with all of their properties as columns in the table. For example, if we take the following test Java bean class:

public class TableBean {
    private String name, surname;
    private Date date;
    ...
    getters/setters
}

And create a large list of its instances with randomly generated strings and data, we’ll see the following:

 

 

The only code to create this table is:

final JTable table = new JTable();
add(new JScrollPane(table));
BeanPropertyTableModel<TableBean> model = new BeanPropertyTableModel<TableBean>(TableBean.class);
model.setOrderedProperties(Arrays.asList(“name”,”surname”,”date”));
model.setData(TableBean.generateList(100));
table.setModel(model);

 

We see that date property is recognized and properly rendered thanks to its column class, and property names are used as column names. The bad thing is BeanInfo class returns property descriptor for ‘class’ property which of course exists in any Java object (there’s getClass() method) but doesn’t belong to actual properties of an object. It makes sense to add a concept of excluded properties which would allow us to exclude certain properties from showing as columns. ‘class’ property will always be excluded.

 

The other thing is the order of columns on the screen. It’s not strictly specified by the language or compiler or virtual machine, but since the very early days of Java beans and reflection it’s always been alphabetic order of their names. Order of properties in the source file is discarded when compiler does its work. Most of the time users want to see certain order of columns on screen, so another feature of bean property model should be an ability to specify order of its properties. This is just a simple list of names and it makes sense to load it from application resource file. Also, in order to specify custom rendering for certain columns and manipulate them in other way we need to know which index has the column mapped to specific property.

 

And the last important thing would be reading column names from resource bundle using some prefix and property name. Most of the time you’ll have to do that as property names rarely work well as screen names even if you split them by words using appropriate cases.

 

Having added all of that quite primitive but useful functionality into our new BeanPropertyTableModel, we end up with the following laconic API to build flexible and powerful tables with any data in a matter of seconds:

 

setResourceBundle(ResourceBundle)

setResourcePrefix(String)

addExcludedProperty(String)

setOrderedProperties(List<String>)

 

Having all the API in place and adding just one new line

model.setOrderedProperties(Arrays.asList(“name”,”surname”,”date”));

 

We can enjoy grown up table which behaves exactly just like we’d described it s model completely from scratch for a particular Java bean.

 

 

Tables often become victim of poor performance, especially for large number of columns and complex data types. What about our new property table model? Some may remember the rumors on Java reflection poor performance, and for the large, many columns and rows table we definitely will use a lot of reflection calls to access the properties. But, in recent versions of JDK reflection performs really well and won’t be an issue comparing to usual graphics/painting overhead, more than that, JDK will replace reflection calls with bytecode pretty quickly as it discovers we’re using a lot of calls to the same methods to get the properties values. You may find it interesting to see an article on reflection performance and benchmarking here: http://buzdin.blogspot.com/2011/01/is-java-reflection-really-slow.html.

Are there any similar alternatives? One of the common solutions to quickly build tables and bind them to properties is Beans Binding library, which does not allow you to quickly show all of the bean properties as our model does, but you can add column using EL language syntax and even access inner properties inside of properties.

However, some of my experiments clearly showed insufficient performances of Beans Binding for large tables, especially if tables are read only and the bean itself does not notify listeners on changes on every single property which is supposed to be extremely fast. Simple and small table model from this article perform much faster and you can visibly see the difference, especially in scrolling. The most painful part with Beans Binding is reloading the whole data list – it takes time on event dispatch thread to re-build listeners and visibly slows down your application whenever large list of rows for a table arrives.  There is no way you can make your table as fast as manually created table which gets its data from directly calling methods of objects known at compile time when you use Beans Binding, but the simple reflection model used in this article comes very close and saves tons of time especially when it comes to prototyping or hundreds of tables in the application. Use this model for the tables of any size and any frequency of updates and still have power of binding and dynamic at hand.

The code for the bean properties based table model and other useful Swing Tools can soon be found on Google Code, under SwingTools project (http://code.google.com/p/swingtools/).

 

This entry was posted in Uncategorized. Bookmark the permalink.

20 Responses to Easy high performance tables with reflection data binding

  1. Matilda says:

    Son of a gun, this is so heplufl!

  2. 3662400 says:

    3662400 beers on the wall. sck was here

  3. Hola! I’ve been following your site for a long time now and finally got the courage to go ahead and give you a shout out from Atascocita Tx! Just wanted to mention keep up the excellent job! Baufinanzierung Online

  4. a330136 says:

    I’ve said that least 330136 times. The problem this like that is they are just too compilcated for the average bird, if you know what I mean

  5. Panic Away says:

    Somebody essentially assist to make severely posts I’d state. This is the very first time I frequented your website page and so far? I surprised with the research you made to make this actual publish extraordinary. Magnificent process!

  6. I’m extremely inspired with your writing skills as smartly as with the format in your blog. Is this a paid subject or did you modify it yourself? Either way stay up the excellent quality writing, it is uncommon to see a nice blog like this one nowadays..

  7. There’s noticeably a bundle to find out about this. I assume you made certain good points in features also. wrist watches

  8. Spot on with this write-up, I actually assume this website needs much more consideration. I

  9. Amy says:

    Hi, some good points here, took me some time to find you again however.

  10. You need to participate in a contest for among the best blogs on the web. I will recommend this web site! moncler piumini

  11. Simply desire to say your article is as astounding. The clarity in your put up is just cool and that i can suppose you are a professional in this subject. Well along with your permission allow me to grab your RSS feed to keep updated with impending post. Thanks a million and please continue the gratifying work.

  12. I’m curious to find out what blog system you’re using? I’m experiencing some minor security issues with my latest site and I would like to find something more safe. Do you have any recommendations? Kredit ohne Schufa

  13. This kind of reading material isn’t something I’m usually interested in. However, I thoroughly enjoyed reading your content. It’s obvious you put a lot of work into this. Good work. http://www.samsung1080phdtv.net/

  14. Hi! I’m at work surfing around your blog from my new iphone 3gs! Just wanted to say I love reading your blog and look forward to all your posts! Keep up the outstanding work! Kredit ohne Schufa

  15. Howdy this is kinda of off topic but I was wanting to know if blogs use WYSIWYG editors or if you have to manually code with HTML. I’m starting a blog soon but have no coding expertise so I wanted to get guidance from someone with experience. Any help would be enormously appreciated! Sofortkredit ohne Schufa

  16. Hey there! Would you mind if I share your blog with my zynga group? There’s a lot of people that I think would really appreciate your content. Please let me know. Thanks Sofortkredit ohne Schufa Online

  17. Hello, Great, Brilliant post Thanks!

  18. Hi there! I know this is kind of off-topic however I had to ask. Does building a well-established website such as yours require a massive amount work? I am completely new to operating a blog but I do write in my diary everyday. I’d like to start a blog so I can share my personal experience and thoughts online. Please let me know if you have any recommendations or tips for brand new aspiring bloggers. Appreciate it! Sofortkredit Schufa

  19. We absolutely love your blog and find many of your post’s to be just what I’m looking for. Does one offer guest writers to write content for you? I wouldn’t mind composing a post or elaborating on most of the subjects you write in relation to here. Again, awesome web log! Sofortkredit ohne Schufa Online

  20. I’m curious to find out what blog system you’re using? I’m experiencing some minor security issues with my latest site and I would like to find something more safe. Do you have any recommendations? haftpflichtversicherung vergleich