2.3 Inspection Result Processors

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.

2.3.1 Interface

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.

Typical InspectionResultProcessor list

Figure 2.3. Typical InspectionResultProcessor list


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.

An InspectionResultProcessor can abort the inspection result processing

Figure 2.4. An InspectionResultProcessor can abort the inspection result processing


2.3.2 Usage

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.

2.3.3 Defaults

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>

2.3.4 Immutability

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.

2.3.5 Implementing Your Own InspectionResultProcessor

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 );
		}
	}
}
[Tip]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:

  1. 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>
    [Important]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.
    [Tip]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.
  2. 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.