Thursday, June 19, 2008

Metawidget and Name/Value Pairs

I was recently asked whether Metawidget 'covered the scenario where my object contains a list of properties (name/value pairs) stored in a collection'?

The answer is yes! Metawidget can read properties from almost any source. You don't need a physical Class. Metawidget can even combine properties from multiple sources (using CompositeInspector). With respect to name/value pairs in a collection:

Step 1: Write an Inspector

Write an Inspector suited to your particular architecture. Here's an example:

package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;
import java.util.Map;
import org.metawidget.inspector.iface.*;
import org.metawidget.util.*;
import org.w3c.dom.*;

public class MapInspector implements Inspector {
   private Map<String, Map<String, Class<?>>> mObjectProperties = CollectionUtils.newHashMap();

   public MapInspector() {
      // Some dummy data
      Map<String, Class<?>> personProperties = CollectionUtils.newLinkedHashMap();
      personProperties.put( "name", String.class );
      personProperties.put( "age", int.class );
      personProperties.put( "retired", boolean.class );

      mObjectProperties.put( "person", personProperties );
   }

   public String inspect( Object toInspect, String type, String... names )
      throws InspectorException {
      Map<String, Class<?>> properties = mObjectProperties.get( type );

      if ( properties == null )
         return null;

      try {
         Document document = XmlUtils.newDocument();
         Element elementRoot = document.createElementNS( NAMESPACE, ROOT );
         document.appendChild( elementRoot );

         Element elementEntity = document.createElementNS( NAMESPACE, ENTITY );
         elementEntity.setAttribute( TYPE, type );
         elementRoot.appendChild( elementEntity );

         for ( Map.Entry<String, Class<?>> entry : properties.entrySet() ) {
            Element element = document.createElementNS( NAMESPACE, PROPERTY );
            element.setAttribute( NAME, entry.getKey() );
            element.setAttribute( TYPE, entry.getValue().getName() );
            elementEntity.appendChild( element );
         }

         return XmlUtils.documentToString( document, false );
      }
      catch ( Exception e ) {
         throw InspectorException.newException( e );
      }
   }
}

Step 2: Use the Inspector

You can try MapInspector by updating the code from the Metawidget tutorial:

package com.myapp;

import javax.swing.*;
import org.metawidget.swing.SwingMetawidget;

public class Main {

   public static void main( String[] args ) {
      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector( new MapInspector() );
      metawidget.setPath( "person" );


      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 210 );
      frame.setVisible( true );
   }
}

Running this will give you a UI driven by name/value pairs in a collection. And because Metawidget decouples its Inspectors from its Metawidgets, it can be equally applied to Spring, Struts, JSF, JSP, GWT, Android etc. for free!

Step 3: Change your Inspector

Of course, your particular take on name/value pairs may be different.

For example, you may not be storing the class in the 'value', you might be storing an Object instead. In that case you'll need to either use Object.getClass() as the type, or just return no type (in which case Metawidget will choose a textbox widget). Equally, you may be storing more than just the value - you may be storing whether the field is required, its maximum length etc. You'll need to add these as extra attributes to the XML elements you create.

Finally, you may want to think about how you're going from a Map to a piece of XML, and whether you can optimize that process. For example, where is the Map coming from? Wherever it is, can you have the Inspector get it from there directly?

4 comments:

gabriel said...

hi, i'm trying to use metawidget with web services. My code gets an object that is in JAXB format, and metawidget doen't get the name/value of the JAXB methods, just the names and displays it. ¿did i need to write an inspector to get it working? thanks in advance.

Richard said...

Payasin,

I can't be sure without seeing some code (can you send some?), but yes you will need to write some kind of a JAXBInspector that inspects your JAXB object and looks at its name/value pairs.

It is important to understand that in this blog entry I used 'value' to mean the type of the object (String, boolean etc). If instead you want to use 'value' as the actual value of the property, you will need to write a WidgetProcessor for that:

Your Inspector inspects the object and finds its properties. The WidgetBuilder chooses widgets for those properties. The WidgetProcessor puts values in those widgets.

Hope that helps.

Richard.

gabriel said...

of course, the entire source is too large for the comments, but it's something like a name-value pairs like this

@XmlElementRef(name = "apyNom", namespace = "http://inmoutils/xsd", type = JAXBElement.class, required = false)
protected JAXBElement apyNom;

public JAXBElement getApyNom() {
return apyNom;
}

public void setApyNom(JAXBElement value) {
this.apyNom = ((JAXBElement ) value);
}

what metawidget shows is "apynom" with no value. There's a stored value (i.e. Homer Simpson), and i need that this value also appears. If i understood you right i need to write a widgetprocessor, to get something like "apynom:homer simpson"
Thanks for your answer, and keep up the good work, the metawidget idea seems fantastic!



i'll take a look into the inspectors and widgetproccessors, if i understand what you say, i need a widgetprocessor, because the inspector maps the class right, but no value is show, in example the apynom field appears, but i need that the value stored also be showed. (like apynom: Homer Simpson)

Richard said...

Yes, you need to write a WidgetProcessor. Please see my reply here https://sourceforge.net/projects/metawidget/forums/forum/747623/topic/3709920.

Look forward to hearing how you go.

Regards,

Richard.