public class Person {
@Id
private int mId;
@Column( nullable = false )
private String mName;
private int mAge;
public String getName() {
return mName;
}
public void setName( String name ) {
mName = name;
}
@UiComesAfter( "name" )
public String getAge() {
return mAge;
}
public void setAge( int age ) {
mAge = age;
}
}
@Id
private int mId;
@Column( nullable = false )
private String mName;
private int mAge;
public String getName() {
return mName;
}
public void setName( String name ) {
mName = name;
}
@UiComesAfter( "name" )
public String getAge() {
return mAge;
}
public void setAge( int age ) {
mAge = age;
}
}
Here we have some JPA annotations (@Id, @Column) on our private fields and some UI annotations (@UiComesAfter) on our public getters/setters. This is quite a common situation, but it's very hard to support because the JavaBean specification doesn't define a relationship between public getters/setters and which private field they relate to. This is pretty obvious when you consider that some getters/setters don't have any private field. For example, a getAge method might calculate its value based on getDateOfBirth rather than have an mAge field per se.
So how do JPA, and other frameworks like Hibernate Validator, support this? Well, they cheat: using reflection to set the fields directly. But this doesn't work for most UI technologies, such as Swing's BeansBinding, or JSF, or Spring. Most UI technologies expect publically accessible getters/setters.
So we need to key off public getters/setters, but we want to annotate private fields. Implementations like GroovyPropertyStyle and ScalaPropertyStyle support this nicely, because those environments do define a mapping between getter/setter and private field. But JavaBeans do not. So how can JavaBeanPropertyStyle support it?
Well, let's be pragmatic: although not enforced, most developers adopt some kind of convention for how their getters/setters are named versus how their private fields are named. There are variations, but we can make this configurable. For v1.05 I've adopted a simple approach based on MessageFormat. So:
- {0} (eg. dateOfBirth, surname)
- 'm'{1} (eg. mDateOfBirth, mSurname)
- 'm_'{0} (eg. m_dateOfBirth, m_surname)
package com.myapp;
import java.text.MessageFormat;
import java.util.List;
import javax.swing.JFrame;
import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.javabean.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.util.CollectionUtils;
public class Main {
public static void main( String[] args ) {
// Model
Person person = new Person();
// Metawidget
SwingMetawidget metawidget = new SwingMetawidget();
JavaBeanPropertyStyleConfig propertyStyleConfig = new JavaBeanPropertyStyleConfig();
propertyStyleConfig.setPrivateFieldConvention( new MessageFormat( "'m'{1}" ) );
BaseObjectInspectorConfig inspectorConfig = new BaseObjectInspectorConfig();
inspectorConfig.setPropertyStyle( new JavaBeanPropertyStyle( propertyStyleConfig ) );
metawidget.setInspector( new CompositeInspector(
new CompositeInspectorConfig().setInspectors(
new PropertyTypeInspector( inspectorConfig ),
new MetawidgetAnnotationInspector( inspectorConfig ) )));
metawidget.setToInspect( person );
// Frame
JFrame frame = new JFrame( "Example" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add( metawidget );
frame.setSize( 400, 250 );
frame.setVisible( true );
}
/**
* Model
*/
public static class Person {
private String mName;
@UiComesAfter( "name" )
private int mAge;
@UiComesAfter( "age" )
private boolean mRetired;
@UiComesAfter( "retired" )
private List<Address> mAddresses = CollectionUtils.newArrayList();
@UiLarge
@UiComesAfter( "addresses" )
private String mNotes;
public String getName() {
return mName;
}
public void setName( String name ) {
mName = name;
}
public int getAge() {
return mAge;
}
public void setAge( int age ) {
mAge = age;
}
public boolean isRetired() {
return mRetired;
}
public void setRetired( boolean retired ) {
mRetired = retired;
}
public List<Address> getAddresses() {
return mAddresses;
}
public void setAddresses( List<Address> addresses ) {
mAddresses = addresses;
}
public String getNotes() {
return mNotes;
}
public void setNotes( String notes ) {
mNotes = notes;
}
}
public static class Address {
private String mStreet;
@UiComesAfter( "street" )
private String mCity;
@UiComesAfter( "city" )
private String mState;
public Address( String street, String city, String state ) {
mStreet = street;
mCity = city;
mState = state;
}
public String getStreet() {
return mStreet;
}
public void setStreet( String street ) {
mStreet = street;
}
public String getCity() {
return mCity;
}
public void setCity( String city ) {
mCity = city;
}
public String getState() {
return mState;
}
public void setState( String state ) {
mState = state;
}
}
}
import java.text.MessageFormat;
import java.util.List;
import javax.swing.JFrame;
import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.javabean.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.util.CollectionUtils;
public class Main {
public static void main( String[] args ) {
// Model
Person person = new Person();
// Metawidget
SwingMetawidget metawidget = new SwingMetawidget();
JavaBeanPropertyStyleConfig propertyStyleConfig = new JavaBeanPropertyStyleConfig();
propertyStyleConfig.setPrivateFieldConvention( new MessageFormat( "'m'{1}" ) );
BaseObjectInspectorConfig inspectorConfig = new BaseObjectInspectorConfig();
inspectorConfig.setPropertyStyle( new JavaBeanPropertyStyle( propertyStyleConfig ) );
metawidget.setInspector( new CompositeInspector(
new CompositeInspectorConfig().setInspectors(
new PropertyTypeInspector( inspectorConfig ),
new MetawidgetAnnotationInspector( inspectorConfig ) )));
metawidget.setToInspect( person );
// Frame
JFrame frame = new JFrame( "Example" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add( metawidget );
frame.setSize( 400, 250 );
frame.setVisible( true );
}
/**
* Model
*/
public static class Person {
private String mName;
@UiComesAfter( "name" )
private int mAge;
@UiComesAfter( "age" )
private boolean mRetired;
@UiComesAfter( "retired" )
private List<Address> mAddresses = CollectionUtils.newArrayList();
@UiLarge
@UiComesAfter( "addresses" )
private String mNotes;
public String getName() {
return mName;
}
public void setName( String name ) {
mName = name;
}
public int getAge() {
return mAge;
}
public void setAge( int age ) {
mAge = age;
}
public boolean isRetired() {
return mRetired;
}
public void setRetired( boolean retired ) {
mRetired = retired;
}
public List<Address> getAddresses() {
return mAddresses;
}
public void setAddresses( List<Address> addresses ) {
mAddresses = addresses;
}
public String getNotes() {
return mNotes;
}
public void setNotes( String notes ) {
mNotes = notes;
}
}
public static class Address {
private String mStreet;
@UiComesAfter( "street" )
private String mCity;
@UiComesAfter( "city" )
private String mState;
public Address( String street, String city, String state ) {
mStreet = street;
mCity = city;
mState = state;
}
public String getStreet() {
return mStreet;
}
public void setStreet( String street ) {
mStreet = street;
}
public String getCity() {
return mCity;
}
public void setCity( String city ) {
mCity = city;
}
public String getState() {
return mState;
}
public void setState( String state ) {
mState = state;
}
}
}
Hopefully this will work for most use cases. For those needing more control, consider extending JavaBeanPropertyStyle and overriding getPrivateField.
Feedback welcome!
1 comments:
Very interesting feature!
I've used a similar approach in order to call some setter methods (all starting with a "preInit" prefix name) before calling the init() method in an injector toolbar framework developed with my collegues some times ago.
I'll try it as soon as I can!
Bye,
Simone
Post a Comment