Using Class as an annotation attribute type

4

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:

public interface ValueStringAdapter<V> {
V toValue(String string) throws InternalFailureException;
String[] allowedValues(); // can return null if infinite
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Adapter {
String value();
}

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:

// Create adapter type
public class LongValueAdapter implements ValueStringAdapter<Long> {
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;
}
}

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:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Adapter {
Class value();
}

and change the use to specify a class, not a string:

@Adapter(foo.LongValueAdapter.class)
public void setLong(Long val) {
this.val = val;
}

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.

Comments

4 Responses to “Using Class as an annotation attribute type”
  1. I remember to have used classes in annotations at work. The main problem with it, as you said, is the tight binding you get to the class. I usually prefer to use classes over textual references though, whenever possible. IIRC, eclipse offers checking for such textual references on refactoring, so it’s not too bad. But usually, one will see the errors at runtime and no earlier.
    One remark, Eugene made a valid point on your approach on his blog*, and I wonder why he didn’t comment on it here, too. It’s in general about generic usage of Class, which allows for type-safe restriction of the class to be passed. It should at least be a Class but of course the more specific the better.
    *)http://www.jroller.com/page/eu?entry=class_parameters_in_annotations,

  2. Alex says:

    Thanks Stefan. I did actually use a parameterized Class in my code, but I thought it muddied the example a bit. I guess I should have mentioned it….

  3. Bruce Chapman says:

    For those that can’t follow the above comments…

    The annotation currently would allow a value of Long.class which would then blow up at run time.

    You can used a bounded wildcard in your annotation type declaration to enforce that the class is some type of ValueStringAdapter thus

    Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Adapter {
    Class> value();
    }

  4. Bruce Chapman says:

    bastard feedback system that doesn’t tell you whether its going to eat your less thans or not.

    here’s another attempt at the munged line

    Class<? extends ValueStringAdapter<?>> value();

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!