Monday, January 28, 2008

Wild Java Properties, The Road So Far

First I'd like to thank Alex Miller for keeping good track of links regarding properties in Java 7,
I don't have to repeat them!

I'll just add a few links:
  • The kijaro project has the first version of Remi Forax's Property implementation.
  • The google doc by Remi that documents this first usage of property as a language feature (as far as I know it's the only one).
  • The usage of this implementation with Beans Binding in Remi's blog entry.
After integrating my work on Abstract Enum in the kijaro project (abstract enum branch, to access it you need to be a kijaro reader), I started merging Remi Forax's branch with one simple goal: Having the list of properties of any class automatically generated (syntactic sugar) as an enum! One of Remi's comments about using enum for property enumeration complained on the inability to use Generics with enums. With Abstract Enum it is possible, and I'll show how here.

The benefits of using abstract enum to enumerate properties are:
  • Easy and type-safe properties introspection: MyClass.Property.values() returns the collection of MyClass.Property<MyClass,?>;
  • Easy and type safe conversion from a string to a property object: MyClass.Property.valueOf("firstName") returns MyClass.Property<MyClass,String> MyClass#firstName;
  • I can switch (aProperty) {case firstName:...};
  • MyClass#myProperty is an instance of MyClass.Property and so I can use == comparator with no risk.
  • I can use MyClass#myProperty in annotations that accept the abstract Enum PropertyAdaptor as parameter. Things like @LinkTo(Person#firstName).
Before I started coding I played, reviewed and analyzed the current implementation, and I will try to describe how it works. Here is the basic example using property keyword ("property" is not really a keyword since it can be used only before the type declaration of a member, but anyway):
public class Person {
public property String firstName;
public property String lastName;
public property int age;
}
Analyzing the syntactic sugar generated by Remi's implementation, I needed to clarify where are the Type1 (Bean-independent property: aka per-class, property-proxy, property adaptor) and Type2 (Bean-attached property: aka per-instance) properties classes and instances. These types are nicely defined in Stephen Colebourne's blog Java 7 - Properties terminology. Remi calls the Type1 a "Property Literal" and it is implemented with an anonymous class inheriting from a new class in the JDK: java.lang.Property.

For Type2 there is no need for a class since the basic property boilerplate code generated is:
private String firstName;
public String getFirstName() {return firstName;}
public void setFirstName(String param) {firstName = param;}
The above code is the syntactic sugar created by javac, and so not editable in any way. For the Type1 the generated code is:
public static Property<Person, String> firstName() {
return new Property<Person, String>(
"firstName",
String.class,
Person.class) {
public String get(Person person) {return person.getFirstName();}
public void set(Person person, String s) {person.setFirstName(s);}
};
}
And for Bean introspection (getting the list of properties) another piece is generated:
public static Property<Person,?>[] properties() {return $PROPERTIES;};
private static Property<Person,?>[] $PROPERTIES = new Property[]{firstName(),lastName(),age()};
This is the code for a basic property, but there are more features implemented like:
  • Generating a getter method only or a setter method only
  • Implementing getter and setter inline of the property declaration
  • And the very powerful 'bound' which allows automatic events on setXXX() without boilerplate code.
For example if lastName is read only with a provided block and age is bound, the Person class will be:
public class Person {
public property String firstName;
public property String lastName get { return "XXX"; };
public property int age bound;

protected <B,T> void propertyChanged(java.lang.Property<B,T> property,Object oldValue,Object newValue) {
System.out.println("property changed "+property+" "+oldValue+" "+newValue);
}
}
Here is the generated code:
public String getLastName() {return "XXX";}

public static Property<Person, String> lastName() {
return new Property<Person, String>(
"lastName",
String.class,
Person.class) {
public String get(Person person) {return person.getLastName();}
public void set(Person person, String s) {throw new UnsupportedOperationException();}
};
}

private int age;
public int getAge() {return age;}
public void setAge(int param) {
int oldValue = age;
age = param;
this.<Person,Integer>propertyChanged(age(),oldValue,param);
}

public static Property<Person, Integer> age() {
return new Property<Person, Integer>(
"age",
int.class,
Person.class) {
public Integer get(Person person) {return person.getAge();}
public void set(Person person, Integer s) {person.setAge(s);}
};
}
That's a lot of syntactic sugar, but that's the point of properties, isn't it: Value objects WTF!!?!!2!. This property implementation works very well for Swing and Beans Binding implementation like Remi's demo shows. You can also write very quickly generic equals and hashcode methods based on the properties list. But, as a backend developer I also need properties.

Property as language feature is not just syntactic sugar generation it's really solving a problem that just CANNOT be solved in Java today: How can I safely (which means maintaining type safety) pass a field or method (which are the elements that represent a property today - getter/setter) to a framework like JPA, WebBeans, Validations framework, Wicket, Swing Bindings, and all the others developed out there? Today, you NEED to pass a String (EL) and do reflection. That does not make sense. When I know a UI component is bound to Person.firstName, why do I need to write bind(Person.class, "firstName") and loose type safety, compiler existence check (whether or not firstName property exists), and so refactoring ability.

Next post, I'll expose how I wish to have properties for all ;-)

2 comments:

Larry Singer said...

There appears to be an error in the last bit of code where you have mixed up firstName and lastName properties.

Otherwise interesting post. I like it.

Frederic Simon said...

Thanks.
Fixed the error (I think ;-)