Pure Danger Tech


navigation
home

Using Class as an annotation attribute type

25 May 2007

I mentioned in my previous post on static typing that there was a tangent to this related to annotations. I was originally going to mention in that post that another place I see class names invisible to the compiler is in an annotation I have that had a class name as an attribute.

I have an annotation that lets a bean specify on a setter method an adapter class that can translate to/from string values. This lets a generic string-based user interface work with an extensible set of bean classes with rich(er) types. More explicitly, I have something like: [source:java]

public interface ValueStringAdapter {

V toValue(String string) throws InternalFailureException;

String[] allowedValues(); // can return null if infinite

}</p>

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Adapter {

String value();

}

[/source]

where the first interface defines the interface internal code can use to convert from a passed string value to the desired value, and also an interface to call to get allowed values if any.

Note: the “value” attribute is well-known on an annotation. If you use it, you do not need to specify the attribute name (“value”) at the point of use. In other words, if you create a value attribute, you can define an annotation as @annotation(“value”) but any other attribute name must be specified as @annotation(attr=”value”).

So, using this to convert Long values would be something like:

[source:java]

// Create adapter type

public class LongValueAdapter implements ValueStringAdapter {

public Long toValue(String string) {

return Long.valueOf(string);

}

public String[] allowedValues() {

return null; // infinite -> return null

}

}

// specify the adapter into string values on a setter

public class ExampleBean {

private Long val;

@Adapter(“foo.LongValueAdapter”)

public void setLong(Long val) {

this.val = val;

}

public Long getLong() {

return this.val;

}

}

[/source]

So, the problem here as described in my previous post is that we are embedding a class name in the annotation attribute. I took another look at the annotation documentation and noticed that the annotation attributes can be primitives, String, or Class. Also, the Annotation.getValue() method is defined to return an Object (which can now return a more specific type on subclasses due to the addition of co-variant return types in JDK 5). Bingo!

I was then able to change the return type to Class for the value attribute:

[source:java]

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Adapter {

Class value();

}

[/source]

and change the use to specify a class, not a string: [source:java]

@Adapter(foo.LongValueAdapter.class)

public void setLong(Long val) {

this.val = val;

}

[/source]

And the nice thing about this is that the compiler (and my tooling) know that this is a class and will type-check and I can refactor it (rename for example) and pick up all changes in the code base. Good stuff.