4.2 Property Inspectors

Being an OIM, a core strength of Metawidget is inspecting Objects. Metawidget is very flexible in this regard, with pluggable support for different languages and different implementation styles.

4.2.1 BaseObjectInspector

BaseObjectInspector underlies many of the Inspectors that inspect objects (as opposed to, say, XML files). It provides easy-to-override methods such as...

protected Map<String, String> inspectProperty( Property property )

...for inspecting properties, and...

protected Map<String, String> inspectAction( Action action )

...for inspecting actions, and finally...

protected Map<String, String> inspectTrait( Trait trait )

...for inspecting things that apply to both properties and actions (e.g. @UiLabel). Quite what constitutes a 'property' or an 'action' is decoupled into pluggable PropertyStyles and ActionStyles.

PropertyStyle

The PropertyStyle interface allows pluggable, fine-grained control over what is considered a 'property'. Different environments may have different approaches to defining what constitutes a property. For example, JavaBean-properties are convention-based, whereas Groovy has explicit property support. Equally, some environments may have framework-specific, base class properties that should be filtered out and excluded from the list of 'real' business model properties.

The default PropertyStyle is JavaBeanPropertyStyle. To change it within metawidget.xml:

<propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"
	config="org.metawidget.inspector.impl.BaseObjectInspectorConfig">
	<propertyStyle>
		<groovyPropertyStyle xmlns="java:org.metawidget.inspector.impl.propertystyle.groovy"/>
	</propertyStyle>
</propertyTypeInspector>

To change it programmatically:

BaseObjectInspectorConfig config = new BaseObjectInspectorConfig();
config.setPropertyStyle( new GroovyPropertyStyle() );
metawidget.setInspector( new PropertyTypeInspector( config ) );
JavaBeanPropertyStyle

The JavaBeanPropertyStyle is the default PropertyStyle used by all BaseObjectInspector subclasses (which includes all annotation inspectors).

This PropertyStyle recognizes JavaBean-convention getXXX, setXXX and isXXX methods. In addition, it maintains a cache of reflected classes for performance.

When using getter methods with private members, make sure you annotate the getter/setter not the private field. By default, JavaBeanPropertyStyle does not find annotations on private fields, because the JavaBean specification does not define a way to determine which private fields belong to which getter/setter methods. However, many developers adopt a naming convention (e.g. getter isFoo maps to field mFoo) and you can specify your preferred convention using JavaBeanPropertyStyleConfig.setPrivateFieldConvention. This method takes a MessageFormat parameter which you can use to format the property name accordingly. For example:

Format Private Field Name
{0} dateOfBirth, surname
'm'{1} mDateOfBirth, mSurname
'm_'{0} m_dateOfBirth, m_surname

If you need more control over the mapping, consider extending JavaBeanPropertyStyle and overriding getPrivateField. You could also use JavaBeanPropertyStyleConfig.setSupportPublicFields( true ) to recognize public fields directly, though this is not recommended.

GroovyPropertyStyle

The GroovyPropertyStyle recognizes GroovyBean properties.

Groovy tries hard to make its GroovyBean properties compatible with JavaBean getters/setters, and indeed one can almost use the default JavaBeanPropertyStyle to read them. Unfortunately, GroovyBeans differ in that:

  • annotations defined on properties are only attached to the (generated) private member variable, not the (generated) getter/setter methods.

  • GroovyBeans define an implicit getMetaClass method which, although matching the JavaBean signature, should not be treated as a business model property.

JavassistPropertyStyle

The JavassistPropertyStyle extends JavaBeanPropertyStyle and makes use of Javassist for those environments that have it available.

Javassist is used to inspect the debug line numbering information embedded in JVM bytecode to sort getters/setters according to their original declaration order in the source code. This saves domain objects having to use @UiComesAfter (or an XML file, or some other method) to impose an ordering.

However, a danger of this approach is that if the domain objects are ever recompiled without debug line numbering information (e.g. when moving from development to production) the UI fields will lose their ordering. Such a subtle bug may not be picked up, so as a safeguard JavassistPropertyStyle 'fails hard' with an InspectorException if line numbers are not available.

JavassistPropertyStyle uses the following sorting algorithm:

  • superclass public fields come first, sorted by name (if JavaBeanPropertyStyleConfig.setSupportPublicFields has been set).

  • superclass methods come next, sorted by getter line number (or, if no getter, setter line number).

  • public fields come next, sorted by name (if JavaBeanPropertyStyleConfig.setSupportPublicFields has been set).

  • methods come last, sorted by getter line number (or, if no getter, setter line number).

Note this algorithm is less flexible than @UiComesAfter, which can interleave superclass and subclass properties. However, it is possible to use both @UiComesAfter and JavassistPropertyStyle together to get the best of both worlds.

ScalaPropertyStyle

The ScalaPropertyStyle recognizes Scala properties.

Scala can make its properties compatible with JavaBean getters/setters, but only if you put special @BeanProperty annotations on them. This can be quite onerous. Instead, ScalaPropertyStyle is designed to access Scala properties natively.

Implementing Your Own PropertyStyle

All PropertyStyles must implement the PropertyStyle interface. BasePropertyStyle assists in caching properties per class (looking them up is often expensive, involving reflection or similar techniques) and in excluding properties based on name, type or base class. Here is an example of a custom PropertyStyle that identifies properties based on ResourceBundle i18n entries. It extends the code from the tutorial (see Section 1.1, “Part 1 (Java version) - The First Metawidget Application”).

package com.myapp;
				
import java.util.*;
import javax.swing.*;
import org.metawidget.inspector.iface.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.*;
import org.metawidget.inspector.impl.propertystyle.javabean.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*;
import org.metawidget.util.*;

public class Main {

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

		SwingMetawidget metawidget = new SwingMetawidget();
		metawidget.setInspector( new PropertyTypeInspector( new BaseObjectInspectorConfig()
			.setPropertyStyle( new BundlePropertyStyle() ) ) );
		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 BundlePropertyStyle
		extends JavaBeanPropertyStyle {

		protected Map<String, Property> inspectProperties( String type ) {
			try {
				Class<?> clazz = ClassUtils.niceForName( type );
				Map<String, Property> properties = CollectionUtils.newTreeMap();
				ResourceBundle bundle = ResourceBundle.getBundle( "MyBundle" );

				for ( Enumeration<String> e = bundle.getKeys(); e.hasMoreElements(); ) {
					String key = e.nextElement();
					properties.put( key, new FieldProperty( key, clazz.getField( key ) ) );
				}

				return properties;
			}
			catch ( Exception ex ) {
				throw InspectorException.newException( ex );
			}
		}
	}
}

For brevity, this example extends JavaBeanPropertyStyle. Normally, you would want to extend BasePropertyStyle and, as well as overriding inspectProperties to locate the properties, implement the Property interface with mechanisms for interrogating the property.

[Tip]Note
In this particular example, it may be useful to create a BundlePropertyStyleConfig class that implements NeedsResourceResolver (see Section 2.7.4, “Resolving Resources”). Then it could use ResourceResolver.openResource to locate the bundle in case it was in a specialized location (such as WEB-INF/).

ActionStyle

The ActionStyle interface allows pluggable, fine-grained control over what is considered an 'action'.

Different environments may have different approaches to defining what constitutes an action. For example, the Swing AppFramework uses an @org.jdesktop.application.Action annotation.

The default ActionStyle is MetawidgetActionStyle. To change it within metawidget.xml:

<metawidgetAnnotationInspector config="org.metawidget.inspector.impl.BaseObjectInspectorConfig">
	<actionStyle>
		<swingAppFrameworkActionStyle xmlns="java:org.metawidget.inspector.impl.actionstyle.swing">
	</actionStyle>
</metawidgetAnnotationInspector>

To change it programmatically:

BaseObjectInspectorConfig config = new BaseObjectInspectorConfig();
config.setActionStyle( SwingAppFrameworkActionStyle.class );
metawidget.setInspector( new MetawidgetAnnotationInspector( config ) );

Note these ActionStyles only apply to BaseObjectInspector and its subclasses. This covers most annotation-recognising Inspectors (e.g. JpaInspector, HibernateValidatorInspector) but not XML-based inspectors. For example, PageflowInspector recognizes actions in JBoss jBPM pageflow XML files without using any ActionStyle.

MetawidgetActionStyle

The default Metawidget ActionStyle recognizes any method annotated with @UiAction. Action methods must not accept any parameters in their signature.

SwingAppFrameworkActionStyle

The SwingAppFrameworkActionStyle recognises Swing AppFramework's @Action annotation as denoting an action.

4.2.2 PropertyTypeInspector (Java version)

PropertyTypeInspector extends BaseObjectInspector, and so inherits its features. In addition, it returns the following attributes for the following business properties:

Metawidget Attribute Property Type
lookup lookup of 'true, false' if the type is Boolean. Values of enums, as returned by .name(), if the type is Enum
lookup-labels lookup of 'Yes, No' if the type is Boolean. This will generally be localized by the Metawidget. Labels of enums, as returned by .toString(), if the type is Enum
no-setter if the property has no setXXX method. Note no-setter is distinct from read-only, because it is common to have no setter for a complex type (e.g. Person.getAddress) but this shouldn't make all its contents (e.g. Address.getStreet) read-only.
no-getter if the property has no getXXX method
parameterized-type if the property is using generics
type declared type of the property
actual-type if the actual type differs from the declared type (i.e. it is a subclass)

4.2.3 JsonSchemaInspector (Java version)

Inspector to look for metadata in JSON Schema files. Consider using in conjunction with JsonSchemaTypeMappingProcessorConfig to convert JSON types into Java types.

public static void main( String[] args ) {

	// Metawidget

	Display display = new Display();
	Shell shell = new Shell( display );
	shell.setText( "JSON Viewer" );
	shell.setLayout( new FillLayout() );

	String jsonSchema = "{ properties: { \"firstname\": { \"type\": \"string\", \"required\": true }, ";
	jsonSchema += "\"age\": { \"type\": \"number\" }, ";
	jsonSchema += "\"notes\": { \"type\": \"string\", \"large\": true }}}";

	final SwtMetawidget metawidget = new SwtMetawidget( shell, SWT.None );
	metawidget.setInspector( new JsonSchemaInspector(
		new JsonInspectorConfig().setInputStream( new ByteArrayInputStream( jsonSchema.getBytes() ))));
	metawidget.addInspectionResultProcessor(
		new TypeMappingInspectionResultProcessor<SwtMetawidget>(
			new JsonSchemaTypeMappingProcessorConfig() ));
	metawidget.setInspectionPath( "personObject" );

	// Shell

	shell.setVisible( true );
	shell.open();

	while ( !shell.isDisposed() ) {
		if ( !display.readAndDispatch() ) {
			display.sleep();
		}
	}

	display.dispose();
}

4.2.4 JsonSchemaInspector (JavaScript version)

Inspects JSON Schemas for their properties. Because Metawidget already uses JSON Schema internally as its inspection result format, this Inspector does not need to do much. However it adds support for JSON schemas that contain nested schemas. For example:

<html>
	<head>
		<script src="angular.min.js"></script>
		<script src="metawidget-core.min.js"></script>
		<script src="metawidget-angular.min.js"></script>
		<script>
			angular.module( 'myApp', [ 'metawidget' ] );

			function UserController( $scope ) {

			$scope.user = {};

			$scope.config = {
				inspector: new metawidget.inspector.JsonSchemaInspector( {
					properties: {
					   title: { type: 'string', enum: ['Mr', 'Mrs', 'Ms'] },
					   name: { type: 'string' },
					   email: { type: 'string', required: true },
					   birthday: { type: 'date' },
					   password: { type: 'string', masked: true },
					   address: {
							properties: {
							   street: { type: 'string' },
							   city: { type: 'string' },
							   postcode: { type: 'string' }
							}
					   },
					   notes: { type: 'string', large: true }
					}
				} )
			}
		}
		</script>
		
	</head>
	<body ng-app="myApp" ng-controller="UserController">
		<metawidget ng-model="user" config="config"/>
	</body>
</html>

Furthermore, JsonSchemaInspector adds support for JSON Schemas that describe array items, through the standard items property. For example:

<html>
	<head>
		<script src="metawidget-core.min.js"></script>
		<style>
			#metawidget {
				border: 1px solid #cccccc;
				width: 250px;
				border-radius: 10px;
				padding: 10px;
				margin: 50px auto;
			}
		</style>
	</head>
	<body>
		<div id="metawidget"/>
		<script type="text/javascript">
			var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
				inspector: new metawidget.inspector.CompositeInspector( [
					new metawidget.inspector.PropertyTypeInspector(),
					new metawidget.inspector.JsonSchemaInspector( {
						properties: {
							family: {
								items: {
									properties: {
										id: {
											type: 'string',
											hidden: true
										},
										employer: {
											type: 'string'
										}
									}
								}
							}
						}
					} )
				] )
			} );
			mw.toInspect = {
				firstname: 'Homer',
				surname: 'Simpson',
				age: 36,
				family: [ {
					id: 0,
					firstname: 'Marge',
					surname: 'Simpson'
				}, {
					id: 1,
					firstname: 'Bart',
					surname: 'Simpson'
				} ]
			};
			mw.buildWidgets();
		</script>
	</body>
</html>

4.2.5 PropertyTypeInspector (JavaScript version)

The JavaScript PropertyTypeInspector is much simplified compared to its Java-counterpart. It inspects JavaScript objects for their property names and types.

In principal, ordering of property names within JavaScript objects is not guaranteed. In practice, most browsers respect the original order that properties were defined in. However you may want to combine PropertyTypeInspector with a custom Inspector that imposes a defined ordering using propertyOrder attributes.