2.4 Widget Processors

WidgetProcessors allow arbitrary processing of a widget following its building by a WidgetBuilder and before its inclusion in the Layout. This section covers WidgetProcessors in general. For in-depth documentation of individual WidgetProcessors see Chapter 6, WidgetProcessors.

2.4.1 Interface

All WidgetProcessors must implement the WidgetProcessor interface. This interface defines three methods:

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

void onEndBuild( M metawidget );
onStartBuild and onEndBuild are called once, processWidget is called for each widget

Figure 2.4. onStartBuild and onEndBuild are called once, processWidget is called for each widget


The first method, onStartBuild, is called at the start of the widget building process, before the WidgetBuilder is called. WidgetProcessors may wish to act on this event to initialize themselves ready for processing. However it is acceptable to do nothing. A convenience base class, BaseWidgetProcessor, is provided with a dummy implementation so that subclasses don't have to override this method.

The last method, onEndBuild, is called at the end of the widget building process, after all widgets have been built and added to the Layout. WidgetProcessors may wish to act on this event to clean themselves up following processing. However it is acceptable to do nothing. The convenience base class, BaseWidgetProcessor, has a dummy implementation so that subclasses don't have to override this method.

The middle method, processWidget is the most important. It is called for each widget. WidgetProcessors can modify the given widget according to the given elementName and various attributes. They can use the given metawidget to help them if needed (for example, to access a UI context with which to create validators).

The processWidget method must return the processed widget. This is typically the same as the given widget. The parent Metawidget then passes this to the next WidgetProcessor in the list as show in Figure 2.5.

Typical WidgetProcessor list

Figure 2.5. Typical WidgetProcessor list


In most cases the WidgetProcessor will simply be modifying the given widget (adding validators, changing styles and so on). However it can decide to swap the widget out by returning a different widget. This new widget will then be passed down the list as shown in Figure 2.6. For an example use of this capability, see HiddenFieldProcessor.

WidgetProcessors can substitute widgets

Figure 2.6. WidgetProcessors can substitute widgets


Alternatively, the WidgetProcessor can decide to exclude the widget entirely by returning null. Subsequent WidgetProcessors will not be called, as shown in Figure 2.7, and no widget will be added to the Layout.

WidgetProcessors can exclude widgets

Figure 2.7. WidgetProcessors can exclude widgets


The list of WidgetProcessors is maintained by the parent Metawidget, and is changeable (this is different to say, CompositeInspector or CompositeWidgetBuilder whose lists are immutable). This capability is designed to allow easy attaching of event handlers, and scenarios such 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();
			}
		}	
	}
}

2.4.2 Defaults

Most Metawidgets have default WidgetProcessors. Overriding the default means the default is no longer instantiated. Usually this is not what you want, so you should consider instantiating the default along with your new WidgetProcessor. You can see the default by looking in the Metawidget JAR for the file metawidget-xxx-default.xml (where xxx is your target platform, such as swing or struts).

For reference, the defaults are:

Platform Default
Android (none)
GWT (none)
JSF
<array>
	<requiredAttributeProcessor />
	<immediateAttributeProcessor />
	<standardBindingProcessor />
	<readableIdProcessor />
	<labelProcessor />
	<standardValidatorProcessor />
	<standardConverterProcessor />
	<cssStyleProcessor />							
</array>
JSP (none)
Spring (none)
Struts (none)
Swing
<array>
	<reflectionBindingProcessor />
</array>

2.4.3 Immutability

All WidgetProcessors are required to be threadsafe and immutable. This means you only need a single instance of a WidgetProcessor for your entire application. If you are using metawidget.xml then ConfigReader takes care of this for you, but if you are instantiating WidgetProcessors in Java code you should reuse instances.

Although individual WidgetProcessors are immutable, the List they are contained in can be changed. Methods such as addWidgetProcessor allows clients to dynamically add WidgetProcessors at runtime. This is useful for adding event handlers (see Section 2.4.1, “Interface”).

Note that immutable only means WidgetProcessors cannot be changed once instantiated - it does not mean they cannot be configured. Many WidgetProcessors have corresponding xxxConfig classes that allow them to be configured prior to instantation in a type-safe way. For example, a BeansBindingProcessor can be configured in code:

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

Or in metawidget.xml:

<beansBindingProcessor xmlns="java:org.metawidget.swing.widgetprocessor.binding.beansbinding"
	config="BeansBindingProcessorConfig">
	<updateStrategy>
		<string>READ_WRITE</string>
	</updateStrategy>
</beansBindingProcessor>

2.4.4 Implementing Your Own WidgetProcessor

Here is an example of a custom WidgetProcessor to add tooltips to add JComponents. It extends the code from the tutorial (see Section 1.1, “Part 1 - The First Metawidget Application”).

package com.myapp;
	
import static org.metawidget.inspector.InspectionResultConstants.*;
import java.util.*;
import javax.swing.*;
import org.metawidget.swing.*;
import org.metawidget.widgetprocessor.impl.*;

public class Main {

	public static void main( String[] args ) {
		Person person = new Person();

		SwingMetawidget metawidget = new SwingMetawidget();
		metawidget.addWidgetProcessor( new TooltipProcessor() );
		metawidget.setToInspect( person );

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

	static class TooltipProcessor
		extends BaseWidgetProcessor<JComponent, SwingMetawidget> {
		
		public JComponent processWidget( JComponent widget, String elementName,
										 Map<String, String> attributes, SwingMetawidget metawidget ) {
			widget.setToolTipText( attributes.get( NAME ) );
			return widget;
		}
	}
}

Like Inspectors, WidgetBuilders and Layouts, WidgetProcessors are required to be threadsafe and immutable. However you can still make them configurable by using xxxWidgetProcessorConfig classes. For example:

public class TooltipProcessorConfig {
	private String mPrefix;
	
	public TooltipProcessorConfig setPrefix( String prefix ) {
		mPrefix = prefix;
		return this;
	}

	public String getPrefix() {
		return mPrefix;
	}
	
	// ...must override equals and hashCode too...
}

These xxxWidgetProcessorConfig classes are then passed to the WidgetProcessor at construction time, and stored internally:

public class TooltipProcessor {
	private String mPrefix;
	
	public TooltipProcessor( TooltipProcessorConfig config ) {
		mPrefix = config.getPrefix();
	}
}

This mechanism can then be controlled either programmatically:

metawidget.addWidgetProcessor( new TooltipProcessor( new TooltipProcessorConfig().setPrefix("Tip:")));

Or through metawidget.xml:

<tooltipProcessor xmlns="java:com.foo" config="TooltipProcessorConfig">
	<prefix>
		<string>Tip:</string>
	</prefix>
</tooltipProcessor>
[Important]Config classes must override equals and hashCode
If you want your configurable WidgetProcessor to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxWidgetProcessorConfig class must override equals and hashCode.
[Tip]Generate an XML Schema
If you intend your WidgetProcessor to be configurable via metawidget.xml, consider defining an XML Schema for it. This is optional, but allows users to validate their use of your WidgetProcessor in their metawidget.xml at development time. There is an Ant task, org.metawidget.config.XmlSchemaGeneratorTask, provided in the source distribution that can help with this by auto-generating the schema. All the existing Metawidget schemas are generated using this Ant task.