
I was
recently asked whether
Metawidget could inspect, load and save business objects based not on a JavaBean (or GroovyBean, ScalaBean etc) but on a
Map of names and attributes. The answer is
yes!
I've
sort of covered this before but last time I only blogged about
inspecting the
Map, not loading and saving the values.We already have a GWT version of this in the Metawidget distribution (see the
GWT Client Side example) but now
I've put together a Swing project you can download too. It's very similar to the
previous blog but adds a
MapWidgetProcessor that loads and saves values back into a
Map.
An important point to grok is that the 'Map of properties' (used by
MapInspector) is different to the 'Map of values' (used by
MapWidgetProcessor). This is in the same way that a 'Class' is different to an 'Object': one defines the type, one defines the instance values. So in the code you will see:
metawidget.setToInspect( values );
metawidget.setPath( "person" );
We call both
setToInspect (to tell Metawidget where the values are coming from)
and setPath (to tell Metawidget what the type is). Normally when using a JavaBean (or GroovyBean, ScalaBean etc) you don't need to do this, because Metawidget will internally call...
metawidget.setPath( metawidget.getToInspect().getClass() )
...on your behalf. But if you want to start using
Maps, these two concepts need to be explictly separate.
Take a
look for yourself here. Or, for the impatient, here's the most important piece of code:
/**
* MapWidgetProcessor uses the Metawidget's <code>toInspect</code> to retrieve/store values.
*/
public class MapWidgetProcessor
implements AdvancedWidgetProcessor<JComponent, SwingMetawidget> {
//
// Public methods
//
@Override
public void onStartBuild( SwingMetawidget metawidget ) {
getWrittenComponents( metawidget ).clear();
}
/**
* Retrieve the values from the Map and put them in the Components.
*/
@Override
public JComponent processWidget( JComponent component, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
String attributeName = attributes.get( NAME );
getWrittenComponents( metawidget ).put( attributeName, component );
// Fetch the value...
Map<String, Object> toInspect = metawidget.getToInspect();
Object value = toInspect.get( attributeName );
if ( value == null ) {
return component;
}
// ...and apply it to the component. For simplicity, we won't worry about converters
String componentProperty = metawidget.getValueProperty( component );
ClassUtils.setProperty( component, componentProperty, value );
return component;
}
@Override
public void onEndBuild( SwingMetawidget metawidget ) {
// Do nothing
}
/**
* Store the values from the Components back into the Map.
*/
public void save( SwingMetawidget metawidget ) {
Map<String, Object> toInspect = metawidget.getToInspect();
for ( Map.Entry<String,JComponent> entry : getWrittenComponents( metawidget ).entrySet() ) {
JComponent component = entry.getValue();
String componentProperty = metawidget.getValueProperty( component );
Object value = ClassUtils.getProperty( component, componentProperty );
toInspect.put( entry.getKey(), value );
}
}
//
// Private methods
//
/**
* During load-time we keep track of all the components. At save-time we write them all back
* again.
*/
private Map<String,JComponent> getWrittenComponents( SwingMetawidget metawidget ) {
@SuppressWarnings( "unchecked" )
Map<String,JComponent> writtenComponents = (Map<String,JComponent>) metawidget.getClientProperty( MapWidgetProcessor.class );
if ( writtenComponents == null ) {
writtenComponents = CollectionUtils.newHashMap();
metawidget.putClientProperty( MapWidgetProcessor.class, writtenComponents );
}
return writtenComponents;
}
}