Wednesday, February 2, 2011

Metawidget: Defining Business Objects Using Maps

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

9 comments:

André said...

Hi,
Thanks for your powerful tool.
Any update on this since metawidget-1.30?
The AdvancedWidgetProcessor interface has completely changed and your project zip doesn't work with that version.
I've also tried with the version 1.10 and I can't get to download for version 1.05

André said...

Hi,
Thanks for your powerful tool.
Any update on this since metawidget-1.30?
The AdvancedWidgetProcessor interface has completely changed and your project zip doesn't work with that version.
I've also tried with the version 1.10 and I can't get to download for version 1.05

Richard said...

Hi Andre,

Thanks for your interest.

I'm not aware the AdvancedWidgetProcessor interface has changed since version 1.05? I just tried the project zip again with version 1.30 and it worked fine for me?

Could you please explain what error you are seeing?

Regards,

Richard.

André said...

Thank you Richard for your reactivity.
I'm new to Java technologies, I come from the Python world :p
I'm using your software for a school project (rich client).
The problem I had seems to be related to the jar files.
The definition is not the same as in the sources. Even when I uncompile it with a decompiler (had).

This is what I get:
public interface AdvancedWidgetProcessor extends WidgetProcessor {}
Rather than:
public interface AdvancedWidgetProcessor extends WidgetProcessor {}

So I'm now linking the metawidget-all.jar file to the metawidget-all-1.30-sources.jar in Netbeans and I can implement AdvancedWidgetProcessor in my code. The only silly thing is that Netbeans still underlines the code like it wasn't respecting the interface but it does compile.

Thank you for your piece of software.

Richard said...

The two lines you pasted in the blog comment above are the same?!

At a guess, I think Blogger may have lost the <...>. Are you saying that the generics information is being lost? This is actually a NetBeans bug I think. Can you try it in Eclipse?

Regards,

Richard.

André said...

Hi Richard,
Sorry for the late reply, I'm having insane weeks at school at the moment.
Yes the brackets disappeared in the comment but yes you got it, the generics was lost, this is what I meant.
I'll try with Eclipse later this week and let you know.
Cheers,
Andre

Richard said...

Hi Andre,

FYI: this issue should now be fixed (for NetBeans) in the latest 2.0-SNAPSHOT.

I'd be most grateful if you could try it and report back.

Regards,

Richard.

André said...

Hi Richard,

I could finally make some testing today.

The first thing was to try on my Gentoo with Eclipse 3.5 and Metawidget 1.30, like you suggested before and it does work seamlessly.

Then, I tried again with Netbeans 6.8, still on Gentoo and Metawidget 1.30, but the issue disappeared.
I'm not entirely sure which version of Netbeans I used the first time but I think it was the 6.9 or 7.0. The first one was removed from the Gentoo tree (unmaintained) while the latter one is marked as testing by the Gentoo team. Only Netbeans 6.8 is currently considered stable.

Finally, I tried with the binary build zipped version of Netbeans 7.0 (http://netbeans.org/downloads/7.0.1/zip.html) on Ubuntu 11.10 and Metawidget 1.30 and I eventually got a similar issue. It even failed to build stating that the signature of the WidgetBuilder interface wasn't correct.
So from this setup (Ubuntu 11.10 and Netbeans 7.0) I give it a go with the Nightly build metawidget-all-2.0-20120121.040105-7.jar and it worked like a charm, even without attaching the sources (metawidget-all-2.0-20120121.040105-7-sources.jar).

This school project was very short and is over now, but I would definitely use your product for my next Java-based GUI applications.

Thanks for your support.

Andre

Richard said...

Thanks for your help, and your diligence in reproducing the problem. Glad to hear it's now working.

Regards,

Richard.