Sonntag, 21. April 2013

Active Annotation: Builder Pattern

The @Data annotation shipped with Xtend is a nice demonstration of the concept of active annotations. It generates a constructor, getters for all fields as well as equals(), hashCode() and toString() methods. Although it says that it turns your class into an "immutable value object", this is not really true. For instance, the generated class is not final and thus can have mutable subclasses. Also, it renames fields so that reflection bases frameworks will not find the corresponding field for a given property. Using the big constructor with all fields can be hard to read, especially if multiple fields have the same type.

So I wanted something a little different. For bigger objects you just need a builder. This is what my @Immutable annotation does:
  • getters for all fields
  • a constructor with all fields
  • a builder class with setters for each field of the class
  • a static builder() method for fluent building in Java clients
  • a static build(Procedure) method for closure-style building in Xtend clients
  • equals(), hashCode() and toString() implementations
  • makes the class final
  • forbids inheriting from other classes
Here you can see the annotation and the build(Procedure)-method in action.

In the future I plan to analyze the classes' fields and generate defensive copying of known mutable classes. For instance, if the class contains a Date field, it will automatically be copied in the constructor and the getter. A warning will be issued for all cases where the annotation does not know whether a field is of a mutable type. This warning will be suppressed if the user specifies his own implementation of a public getter and a private setter (used by the constructor).

Also, there is a lot of fine tuning to do like not overwriting the toString() method if the user has already specified a custom one.

So for everyone who's interested, here is the full implementation of the annotation processor. It's just over a hundred lines of code that saves me thousands of lines of boilerplate in my data classes.

Active Annotation: Automated "extract interface"

This idea for an active annotation came from Oliver Libutzki. He told me that often times
  • he has only one sensible implementation for a piece of code, but wants to hide it behind an interface for easier testing 
  • the default implementation is so widely used that its API defines the interface while other implementations should follow that
I think these are use cases that we all have encountered. This can be done manually by using the "extract interface" refactoring in eclipse and then adding the @ImplementedBy annotation of Guice pointing to the default implementation. When you want a different implementation (like in tests),  you can overwrite that binding with a Guice module.

Since the interface is completely derived from the public methods of its default implementation, this is a prime candidate for automation. So I wrote the @ExtractInterface annotation. It creates an interface that contains all public methods of a class. The name of the interface is the same as the class, with leading "Default" or trailing "Impl" removed. You could just as well prepend an "I", but I'm no big fan of that convention. I'd rather have a clean interface name and a prefix/suffix on the implementing class.

The annotation processor for this one is so simple that you should have no problems understanding it after reading the first two paragraphs.

Well, there is a small problem that currently prevents this from working. The line set("value", cls.newTypeReference) does not compile, because Xtend 2.4.1 does not allow class parameters for annotations. This will most probably change in version 2.4.2, as it is a very common use case.