Friday, November 2, 2007

Playing with the full abstract enum!

Since Neal Gafter released the closure prototype, Ricky Clarkson has been having a lot of fun. He wrote 2 nice blogs:
Both have "Java 7 Example" in their titles - So closures is going in Java 7: Got that!

The Java code is a little scary at first, but after a while it works, you can read it. IMHO: all the inlining of closure syntax can get very confusing. I know they supposed to be, but I played with the code and when you create variables for the closures it helps increased readability.

Anyway, I really like the fact that you can now really test, today, closures and their impact on your code.
All this, gave me the idea to do the same for abstract enum.

The reasons for abstract enum (and there are more I'm sure) are:
  1. I wanted abstract enum for solving the property binding issue in a type safe way.
  2. I found out that you cannot use String Enum.name() and int Enum.ordinal() methods as annotations parameters.
  3. I know there are way too many strings in annotations (methods, fields, scopes, states, package, groups, ...). I don't like strings describing my code.
  4. While I was at it, Steven Coco asked for generics in enums, so it's there also. That happens to be quite an interesting feature, that was already there from Neal (but buggy ;-).
For all those reasons I know abstract enum is a good thing. Changing the javac to implement this feature is at least 2 orders of magnitude easier than implementing closures, so it was within my reach ;-)

I have exposed some mercurial repositories for langtools and for the jdk. You need to have a mercurial working copy of openJDK, then inside langtools run:
hg pull http://www.jfrog.org/hg/openJDK/MASTER/langtools

You can "hg log" to see what's going on, then do:
hg up

The patch for the JDK is very small, and actually (in my view), it is just another valid way (in a Java 5 environment) to do Class.isEnum() and Annotations parsing. The new JDK is needed for the tests to pass because abstract enum are not considered enum by java.lang.Class otherwise :-(.

Type safe reflection


The solution for field binding is straight forward. The abstract enum looks like:
public abstract enum FieldDefinition {
private Field reflectField;

public Field getField() {
if (reflectField == null) {
Class modelClass = null;
try {
modelClass = this.getClass().getEnclosingClass();
reflectField = modelClass.getDeclaredField(name());
} catch (NoSuchFieldException e) {
throw new ObjectMappingException("Field " + this +
" does not exists in " + modelClass, e);
}
}
return reflectField;
}
}

And to use it in my Value Object it looks like:
public class ModelMock {
private String firstName;
private String lastName;
private int age;

public static enum fields extends FieldDefinition {
firstName, lastName, age;
}
}
Of course is not as good as properties as a language support, but it solves my problem. From the above, I extended to property using generics. So, the PropertyDefinition takes the property type as generic parameter, so the generic getter/setter methods are generic typed. I took (type 1 and 2) from Stephen Colebourne's Weblog Java 7 - Properties terminology, and converted it.

Before abstract enum there was:
public class MyBeanBefore {
private String firstName;
private String lastName;
private BigDecimal height;
private Date dob;

public PropertyDefinition<MyBeanBefore, String> firstNameProperty() {
return ReflectionAttachedProperty.create(this, "firstName");
}
public PropertyDefinition<MyBeanBefore, String> lastNameProperty() {
return ReflectionAttachedProperty.create(this, "lastName");
}
public PropertyDefinition<MyBeanBefore, BigDecimal> heightProperty() {
return ReflectionAttachedProperty.create(this, "height");
}
public PropertyDefinition<MyBeanBefore, Date> dobProperty() {
return ReflectionAttachedProperty.create(this, "dob");
}
}
always using strings :-( And now:
@FieldPrefix("_")
public class MyBean {
private String _firstName;
private String _lastName;
private BigDecimal _height;
private Date _dob;

public static enum properties<V> extends PropertyDefinition<MyBean,V> {
<String> firstName,
<String> lastName,
<BigDecimal> height,
<Date> dob
}
}
This code actually compile and run (with the strange _ prefix ;-). Cool, no. The full code is under subversion here.
Now, with this, I can find usages, refactor, and get compilation errors for every binding using a field that does not exist. Like in here:
MyBean bean = new MyBean();
PropertyAdaptor<MyBean, Date> dobProperty = MyBean.properties.dob;
PropertyInstance<Date> dobPropertyInstance = dobProperty.getPropertyInstance(bean);
The type safety is not entirely true, since in my own Model class I get Runtime error for mismatching fields in the "fields" enumeration. This can be solve in 2 ways: make fields a keyword in Java and generate it automatically ;-), and/or test your model class and fields enumeration correctly.

abstract enum in Annotations


Like with closures, today when I encounter a problem in some projects, I often end up with: That will have been a lot faster and nicer with closures, and that will be solved perfectly with abstract enum. Now, I know it's the standard disease of being too much into it ;) But still I think it's true.

And abstract enum in Annotations is, for me, a really powerful feature.

So, one problem I had was with JPA schema names, cache names, and fields associations. All those information need to be provided as strings inside annotations. And of course they are repeating themselves a lot, they get misspelled, and basically you loose the strong typing.
So, I wanted to use enum (Schemas, Caches, Fields) to control the strings. But I would hit a dead end: Enum.name() is not a string literal, so you cannot use it in Annotations.
Now, with abstract enum, I have way more flexibility and type safety.
For the caches for example I can have Hibernate declaring something like:
public abstract enum CacheDefinition {
private CacheConcurrencyStrategy defaultUsage;
private String defaultInclude;

CacheDefinition(CacheConcurrencyStrategy defaultUsage, String defaultInclude) {
this.defaultUsage = defaultUsage;
this.defaultInclude = defaultInclude;
}

public CacheConcurrencyStrategy getDefaultUsage() {
return defaultUsage;
}

public String getDefaultInclude() {
return defaultInclude;
}
}
and then in my application:
public enum MyCaches extends CacheDefinition {
references(CacheConcurrencyStrategy.READ_ONLY,"all"),
business(CacheConcurrencyStrategy.TRANSACTIONAL,"non-lazy");
}
to use in:
@Cache(MyCaches.business)
class Action {
...
}

I wrote a blog (JPA NamedQueries and JDBC 4.0) about how to solve the string association issue in JPA named queries. Frank Cornelis answered with an addition that enables the reuse of query methods. The main problem with this addition is it adds more strings in annotations.
This case can also be solved with abstract enum.

So, if you reached there and you like the language feature of abstract enum: Vote for the RFE: 6570766.

2 comments:

Yardena said...

Hi Fred, I posted some thoughts here on my blog. We can also discuss this over cup of coffee if you are interested enough, you know the number :-)

Frederic Simon said...

If you are interested or want to learn more please check the extended enums web site: http://www.extended-enums.org/