Please excuse liberties taken in my code samples for the sake of brevity, eg. omitting reflection exception handling in the above example. I'm actually having to type this stuff by hand you know!
So the configure() method uses reflection to invoke column.configure(field) on the columns declared in the table model.
The table column
So let's check out the GTableColumn implementation, in particular its configure(field) method.
public class GTableColumn<Value> extends TableColumn {
...
public void configure(Field field) {
String propertyName = field.getName(); // eg. "realNameColumn"
propertyName = removeEndsWith(propertyName, "Column"); // eg. to "realName"
setBindingPropertyName(propertyName);
TableColumnProperties properties = field.getAnnotation(TableColumnProperties.class);
if (properties != null) configure(properties);
... // configure from resource bundle, using propertyName
... // configure from preferences, using propertyName
}
...
}
So the configure(field) method sets the default "binding property name" for binding this component to a property in the bean, eg. CowBean.realName. It uses the component's field name as a clue, eg. from "realNameColumn" we cunningly deduce that the default binding property name might be "realName". "Dr Watson, do you concur?"
Also, the column can configure itself using annotations. In our table model code sample above, remember we specified a default width and label for the column using a TableColumnProperties annotation. Hopefully we are on the same page here. Because this article has only one page, so...
At this stage, we would also want to configure our column via resource bundles and user preferences. For example, our default column label might be translated in a resource bundle.
We might want to set the width of the column to the user's preference from last time the user ran the application. That is, when they use the mouse to change the column width, we might wanna remember that for next time using the Preferences API.
So we configure the column from externalised properties and preferences, using the field name as the key. In the case of resource bundles, this is fragile, since if you rename the column field, then you need to remember to rename the key in the resource bundles as well. Darn, we just can't win!
Unit testing the fragile bits
This brings us to unit tests we should implement. "I'll give you two to start with, and if you need more, then lemme know. Globalisation, Streamlining..." (From the "The Office" heh heh)
First we check that our binding is valid. For example, if we have a realNameColumn component bound to CowBean.class with a binding property name of "realName" then, upon reflection, we should find a CowBean.realName property.
The thing is that when we rename a property in the bean, we have to remember to rename components that will be bound to that property eg. realNameColumn. And we will forget, so we need unit tests to remind us, because the IDE and the compiler aint gonna.
Also we need to remember to rename keys in our resource bundles. So we should have unit tests that confirm that all our labels are translated in all our resource bundles. "With all those things out there? Well you can count me out!" (Bill Paxton's character in the movie "Aliens" heh heh)
For example, since we have a realNameColumn component, our resource bundles should contain a key like "CowTableModel.realNameColumn.label".
This is fragile because as soon as we rename components, all our resource bundles break. "Dammit to hell, Leeroy, why'd you rush in there like that and rename things?!"
Conclusion
We present an approach for binding GUI components to data beans, without using string references.
This is achieved using reflection on the component names, and assuming that by default, their names match the binding property names in the backing bean, eg. realNameColumn and realNameField are bound to realName.
However, when we rename components, or properties, we need to take care to rename them in multiple places, including in the resource bundles.
That is the price we pay, if we wish not to use string literal references, as a rule. Those are similarly fragile, and not readily toolable.
We hope that Dolphin will make things better via property references. In the meantime, if you can think of a better way, or some tweaks, please comment.
I look forward to continuing this series, in particular, applying a similar approach to object-relational mapping, to enable native queries. So look out for that article which is coming up.
Postscript. I miss writing tongue-in-cheek articles. You know, trying to get all riled up, emotions flying off the handle, and old gripes bursting out of my chest like a scifi movie. Goddamn, this technical article is so dry and boring! Sorry about that. Hey I'll spice it up before I publish it. And the next one too. Hey that'll be easy, it'll be about persistence so I can slag off Hibernate and slaughter some other sacred cows while I'm at it, so check it out, cow! :)
In the meantime if you are looking for some heavy reading, then "Swing trashes Ajax" was made for you!