Friday, October 2, 2009

Metawidget 0.8 to 0.85 Migration Guide

The next release of Metawidget (v0.85, due in late October) will represent a significant refactoring of the Metawidget internals, aimed at increasing API clarity and consistency, code reuse, type safety and extensibility - all in preparation for our 1.0 release. As such, there will be a number of breaking changes.

We apologise for this disruption and provide this Migration Guide to help in moving from v0.8 to v0.85. All of the existing documentation and examples have already been migrated, as a reference point.

WidgetProcessors

The big new change is the concept of a 'widget processor'. These are lightweight, pluggable classes who can 'process' a widget just after its building (by the WidgetBuilder) and before it is laid out (by the Layout).


The interface is simply:

W processWidget( W widget, String elementName, Map<String, String> attributes, M metawidget )

This is great because:

  • It allows you to plug-in arbitrary processing of a widget without touching the Metawidget class itself. For example you could write a widget processor to add a tooltip to every component:

    JComponent processWidget( JComponent widget, String elementName, Map attributes, SwingMetawidget metawidget ) {
       widget.setToolTipText( attributes.get( NAME ));
       return widget;
    }

  • It provides more ways to identify and process a widget than getComponent( String... names ), for situations where you don't know the component's name in advance

  • It allows you to attach Event Handlers as inner classes that have connections to their parent class:

    final Object someObject = ...;

    metawidget.addWidgetProcessor( new BaseWidgetProcessor() {
    JComponent processWidget( JComponent widget, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
       ...decide whether to attach event handler...

       widget.add( new AbstractAction() {
          public void actionPerformed( ActionEvent event ) {
             someObject.doSomething();
          }
       }
    }
    }

  • Standardizing this concept allows us to refactor a lot of previously ad-hoc functionality. For example all binding and validation functionality are now implemented as widget processors:


    They can be chained together either programmatically or in metawidget.xml:

    <widgetProcessors>
       <list>
          <processor:requiredAttributeProcessor />
          <processor:immediateAttributeProcessor />
          <processor:standardBindingProcessor />
          <processor:readableIdProcessor />
          <processor:labelProcessor />
          <processor:standardValidatorProcessor />
          <processor:standardConverterProcessor />
       </list>
    </widgetProcessors>

Refactored save(), rebind(), validate()

As mentioned, binding and validation have now been refactored into WidgetProcessors. This has the advantage of moving methods such as save() and validate() out of the Metawidget (where they weren't always applicable) and into the responsible WidgetProcessor (where they will always be applicable, by definition).

This unfortunately means a slightly more cumbersome API. Instead of...

metawidget.save()

...you now have to do...

metawidget.getWidgetProcessor( BeansBindingProcessor.class ).save( metawidget )
...however we think the tradeoff is worth it.

metawidget.xml now supports (and mostly uses) <array>

ConfigReader now natively supports an <array> element. This allows it to natively call vararg methods, removing the need for some of the overloaded methods in the xxxConfig classes.

Most current usages of <list> should be replaced with <array>. Lists (and Sets) are still supported for your custom use-cases.

Refactored setCreateHiddenFields

JSF hidden field creation has also been refactored into a WidgetProcessor. For example:

<widgetProcessors>
   <list>
      <hiddenFieldProcessor />
   </list>
</widgetProcessors>

Layouts must now be immutable

Layouts are now a standardized part of the Metawidget pipeline. They must implement the new org.metawidget.layout.iface.Layout interface, and must be threadsafe and immutable just like Inspectors, WidgetBuilders and WidgetProcessors. Custom Layouts that are currently thread-unsafe or mutable need to be refactored in a couple of ways:

  • Configuration settings, that do not change over the lifetime of the Layout and would previously have been retrieved via metawidget.getParameter (and possibly saved into member variables), should now be configured by an external xxxConfig class. For example:

    public GridBagLayout() {
       this( new GridBagLayoutConfig() );
    }

    public GridBagLayout( GridBagLayoutConfig config ) {
       mNumberOfColumns = config.getNumberOfColumns();
       mLabelAlignment = config.getLabelAlignment();
       mSectionStyle = config.getSectionStyle();
       mLabelSuffix = config.getLabelSuffix();
       mRequiredAlignment = config.getRequiredAlignment();
       mRequiredText = config.getRequiredText();
    }

    ...and configured either programmatically or in metawidget.xml:

    <layout>
       <htmlTableLayout xmlns="java:org.metawidget.jsp.tagext.html.layout" config="HtmlTableLayoutConfig">
          <tableStyle>
             <string>aTableStyle</string>
          </tableStyle>
       </htmlTableLayout>
    </layout>

  • Internal state that changes during layout and was stored in member variables should now be stored in the parent Metawidget. For example:

    State state = (State) metawidget.getClientProperty( GridBagLayout.class );
    state.currentRow++;

No more metawidget.setParameter or m:param

The previously (type unsafe) metawidget.setParameter methods and m:param tags have now been removed, in favour of typesafe configuration objects on the WidgetProcessors and Layouts. So instead of...

metawidget.setPropertyBindingClass( BeansBinding.class )
metawidget.setParameter( "updateStrategy", UpdateStrategy.READ )

...you now do...

metawidget.addWidgetProcessor( new BeansBindingProcessor(
   new BeansBindingProcessorConfig()
      .setUpdateStrategy( UpdateStrategy.READ )));

...or equivalent in metawidget.xml.

ConfigReader Moved

ConfigReader has now been moved out of org.metawidget.inspector and into the new org.metawidget.config. This reflects it outgrowing its roots as a purely Inspector-focussed mechanism, into a general purpose way to configure Metawidgets.

XSDs

Existing XSDs have now been moved under http://metawidget.org/xsd. In addition, all the XSDs for the Inspectors, WidgetBuilders etc have been generated.

PropertyStyles and ActionStyles are now objects

ConfigReader is now smart enough to reuse PropertyStyle and ActionStyles instances between Inspectors. This means they are now specified on the Inspector as an object, not their class. This opens the door to people writing their own, configrable property styles if they need to.

Ant Build

The formerly monolithic build.xml has been split into several smaller ones. The original (/build.xml) now only builds Metawidget itself. There is a separate /examples/build.xml (builds examples), /test/build.xml (runs unit tests), /src/doc/build.xml (builds documentation) and /src/web/build.xml (builds Web site).

Similarly, the lib folder has now been split into /lib, /examples/lib and /test/lib.

1 comments:

thierry said...

Very nice enhancement
WidgetProcessors with the ability to chain process is welcome