InspectionResultProcessors allow arbitrary processing of the inspection result returned by the Inspector, before it is passed to the WidgetBuilder. This section covers InspectionResultProcessors in general. For in-depth documentation of individual InspectionResultProcessors see Chapter 5, InspectionResultProcessors.
All InspectionResultProcessors must implement the InspectionResultProcessor interface. This is a simple interface that defines only one method:
String processInspectionResult( String inspectionResult, M metawidget, Object toInspect, String type, String... names );
Where M is a Metawidget type (such as SwingMetawidget or UIMetawidget).
The InspectionResultProcessor must returned the processed inspection result as XML conforming to inspection-result-1.0.xsd. The parent Metwidget then passes this to the next InspectionResultProcessor in the list as shown in Figure 2.3.
In most cases the InspectionResultProcessor will modify the given inspectionResult and return a new String. This will then be passed down the list. Alternatively, the InspectionResultProcessor can return null to cancel inspection result processing entirely. No further InspectionResultProcessors or WidgetBuilders will be called, as shown in Figure 2.4.
Unless explicitly specified, each Metawidget will instantiate a default InspectionResultProcessor. Typically this will be a ComesAfterInspectionResultProcessor, which sorts business properties and actions according to their comes-after attribute.
This default behaviour can be overridden either in code:
metawidget.addInspectionResultProcessor( new MyInspectionResultProcessor() );
Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):
<swingMetawidget xmlns="java:org.metawidget.swing"> <inspectionResultProcessors> <array> <myInspectionResultProcessor xmlns="java:com.myapp"/> </array> </inspectionResultProcessors> </swingMetawidget>
This allows easy plugging in of alternate InspectionResultProcessors.
Most Metawidgets have default InspectionResultProcessors. Overriding the default means the default is no longer instantiated. This may not be what you want, so you may want to instantiate the default along with your new InspectionResultProcessor. 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 | <array> <comesAfterInspectionResultProcessor /> </array> |
AngularJS | new metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor(...) |
GWT | <array> <comesAfterInspectionResultProcessor /> </array> |
JavaScript (incl. JQuery UI) | (none) |
JSF | <array> <facesInspectionResultProcessor /> <comesAfterInspectionResultProcessor /> </array> |
JSP | <array> <jspInspectionResultProcessor /> <comesAfterInspectionResultProcessor /> </array> |
Spring | <array> <comesAfterInspectionResultProcessor /> </array> |
Struts | <array> <comesAfterInspectionResultProcessor /> </array> |
Swing | <array> <comesAfterInspectionResultProcessor /> </array> |
SWT | <array> <comesAfterInspectionResultProcessor /> </array> |
All InspectionResultProcessors are required to be immutable. This means you only need a single instance of an InspectionResultProcessor for your entire application. If you are using metawidget.xml then ConfigReader takes care of this for you, but if you are instantiating InspectionResultProcessors in Java code you should reuse instances.
Here is an example of a custom InspectionResultProcessor that chooses, and sorts, domain object properties based on a JComponent client property. It extends the code from the tutorial (see Section 1.1, “Part 1 (Java version) - The First Metawidget Application”).
package com.myapp; import static org.metawidget.inspector.InspectionResultConstants.*; import javax.swing.*; import org.metawidget.swing.*; import org.metawidget.inspectionresultprocessor.iface.*; import org.metawidget.util.*; import org.w3c.dom.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); metawidget.addInspectionResultProcessor( new IncludingInspectionResultProcessor() ); metawidget.putClientProperty( "include", new String[]{ "retired", "age" } ); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } static class IncludingInspectionResultProcessor implements InspectionResultProcessor<SwingMetawidget> { public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) { String[] includes = (String[]) metawidget.getClientProperty( "include" ); Document document = XmlUtils.documentFromString( inspectionResult ); Element entity = (Element) document.getDocumentElement().getFirstChild(); int propertiesToCleanup = entity.getChildNodes().getLength(); // Pull out the names in order for( String include : includes ) { Element property = XmlUtils.getChildWithAttributeValue( entity, NAME, include ); if ( property == null ) continue; entity.appendChild( property ); propertiesToCleanup--; } // Remove the rest for( int loop = 0; loop < propertiesToCleanup; loop++ ) { entity.removeChild( entity.getFirstChild() ); } return XmlUtils.documentToString( document, false ); } } }
Note | |
---|---|
We don't necessarily recommend this approach, as it requires hard-coding business property names into your UI screens and won't refactor well. See Chapter 9, How To's for other approaches. |
Like Inspectors, WidgetBuilders, WidgetProcessors and Layouts, InspectionResultProcessors are required to be immutable. However they will occasionally need to store some internal state, such as which sort order to use. This can be achieved in two ways:
For state that will remain constant throughout the life of the InspectionResultProcessor, such as which sort order to use, use xxxInspectionResultProcessorConfig classes. For example:
public class MyInspectionResultProcessorConfig { private boolean mSortAscending = true; public MyInspectionResultProcessorConfig setSortAscending( boolean sortAscending ) { mSortAscending = sortAscending; return this; } public boolean isSortAscending() { return mSortAscending; } // ...must override equals and hashCode too... }
These xxxInspectionResultProcessorConfig classes are then passed to the InspectionResultProcessor at construction time, and stored internally:
public class MyInspectionResultProcessor { private boolean mSortAscending; public MyInspectionResultProcessor( MyInspectionResultProcessorConfig config ) { mSortAscending = config.isSortAscending(); } }
This mechanism can then be controlled either programmatically:
metawidget.addInspectionResultProcessor( new MyInspectionResultProcessor( new MyInspectionResultProcessorConfig().setSortAscending( false )));
Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):
<myInspectionResultProcessor xmlns="java:com.foo" config="MyInspectionResultProcessorConfig"> <sortAscending> <boolean>false</boolean> </sortAscending> </myInspectionResultProcessor>
Config classes must override equals and hashCode | |
---|---|
If you want your configurable InspectionResultProcessor to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxInspectionResultProcessorConfig class must override equals and hashCode. |
Generate an XML Schema | |
---|---|
If you intend your InspectionResultProcessor 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 InspectionResultProcessor 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. |
For state that will change across multiple inspection result processings, store it in the Metawidget that is passed to processInspectionResult. You may want to further wrap the state in a small helper class. For example:
public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) { ...process inspection result... getState( metawidget ).previousType = type; } private State getState( SwingMetawidget metawidget ) { State state = (State) metawidget.getClientProperty( getClass() ); if ( state == null ) { state = new State(); metawidget.putClientProperty( getClass(), state ); } return state; } static class State { String previousType; ...other state variables... }
You could also consider storing the state in a ThreadLocal. This is straightforward because InspectionResultProcessors are not re-entrant.