Practical tips on JDK 1.5 annotations

Posted by    |      

Maybe you haven't noticed, but every single piece of information on JDK 1.5 Metadata Annotations is nothing but a simple /Hello World/ tutorial (of course, I exclude the specification). There is no real world implementation. This is especially true for codes processing the annotations. I'll call such an application an annotation reader.

I was publicly cursing about that and Cedric Beust answered in his blog and gave me some hints.

I want to add some more tips and comments about Cedric's list, based on my experience with the development of Hibernate annotations:

  • default values, what about null?
  • faking a non-existing annotation programmatically
  • no extends, what about code duplication?

default values, what about null?

Nothing to declare, except that Cedric is right. The whole point is that there should be something that let's the annotation reader know whether the user set the value or used the default one. However, no such mechanism has been provided by the JSR.

As a workaround I've also build my own BooleanImplied enum type that takes care of values not explicitly set by the user. With Strings, I don't really like using some restricted key-string, such as #implied. For now, my special key-string works like a charm, but one day it will face the what-if-I-want-to-use-that-keyword-as-a-value syndrome. I've no better answer :-(

faking a non existing annotation programmatically

In annotation reader code, there is no way to programmatically build a new annotation /instance/, whether with or without default values. Of course, an annotation is an interface and there is no implementation provided! However, building your own annotation could be useful to keep the code as generic as possible and to fake a user provided annotation. (Especially if most of your annotations have default values).

Let's have a look at an example:

public @interface Column {
    /** 
     * column name
     * default value means the name is guessed.
     */
    String name() default "#implied";

    /**
     * column length
     * Default value 255  
     */
    int length() default 255;

    [...]
}

public @interface Property {

    /**
     * column(s) used
     * Default is guessed programmatically based on the default @Column()
     */
    Column[] columns() default {};

    [...]
}

@Property()
public String getMyProperty() {
    return myProperty;
}

I want to use the default values of @Column() when the user does not set any @Column() array in @property(). Why? Because I want the simplest annotations, I want implicit and default values to be the rule, and I don't want to duplicated my annotation processing code.

Two solutions:

  • duplicate the default values somewhere in a value holder class (don't like that one)
  • keep a fake method somewhere with the default @Column() inside:
/**
 * Get default value for annotation here
 * (I couldn't find a way to build an annotation programatically)
 */
@Column()
private static void defaultColumnAnnHolder() {}

You'll then be able to read and reuse this method in your annotation reader code. I think some bytecode manipulation framework can easily and elegantly provide such feature without the ugly extra method in your code. (Javassist 3.0 should be able to do that if I understand Bill Burke, but I've not tested it yet)

no extends, what about code duplication?

That one that really sucks. The extends keyword is not allowed for annotation. Without it, we can't build a generic method, handling an annotation hierarchy. Bye-bye to the beauty of OO.

Suppose I have @CoreProperty() and then @ComplexProperty():

public void handleProperties(CoreProperty ann) {
    [...]
}

This can't process @ComplexProperty(), as there is no way to let @ComplexProperty inherit @CoreProperty.

Some solutions:

  • force your user to set @ComplexProperty() and @CoreProperty() on its annoted method (I don't consider this as an option)
  • use composition
  • copy annotation data into a new class hierarchy (or flatten it)
  • use reflection on annotation

The second one is pretty simple but it may not be appropriate for every model:

@ComplexProperty(
    @CoreProperty(...)
)

If the CoreProperty is an implementation concept, it's pretty ugly to show that in the annotation.

The third solution kind of sucks, but that is the one I'm using now. The annotation reader has to be implemented with two layers:

  • a layer reading and flatteninng the annotation (or putting it in an extra class hierarchy)
  • a process method, handling properties in flat (or hierarchical) representation:
public void processPropertyAnn(Ann property) {
    [...]
    String name = property.name();
    processFlatProperty(name, null);
}

/**
 * Process a property
 * @param name property name
 * @param extraValue this extraValue can be set using ComplexProperty
 */
private void processFlatProperty(String name, String extraValue) {
    [...]
}

This leads to some extra metadata structure: the good old DTO are evil issue appears in annotation!

The fourth solution will probably be complex and unreadable code, it seems to me that reflection is overkill in this case (I haven't implemented it, however).

Time to conclude

Well, I stayed focused on JSR 175 practical flaws and gave some tips to make life easier. I still strongly believe annotations are good and will give us a new way of thinking metadata in our apps. I don't care which one is best: XDoclet or JSR 175, I care about a standard metadata facility (this topic is mature enough for standardization). I care about a standard metatada desription to easily plug tools on top of frameworks like Hibernate.

This spec will evolve. But for now, don't curse about it, use it for real!


Back to top