Creating Nested (Complex) Java Annotations

When creating new annotations, it doesn't take long to get to the point of wanting to use what are referred to in the JSR-175 specification as complex annotations. Before we talk about complex annotations, as a reminder, non-complex, simple annotations look like this when used:

@SimpleAnnotation(a="...", b=3)
public Object foo() {...}
In this simple case, annotation members are scalar values, in this case "..." and 3.

By contrast, complex annotations may contain other annotations in a nested fashion. They look like this when used:

@ComplexAnnotation(
    @SimpleAnnotation(a="...", b=3)
)
public Object foo() {...}
Complex annotations can also define arrays of members too:
@ReallyComplexAnnotation(
    { @SimpleAnnotation(a="...", b=3), @SimpleAnnotation(a="...", b=4) }
)
public Object foo() {...}
Let's look at the declarations for the annotations above. The simple annotation is pretty straightforward:
@Target(ElementType.METHOD)
public @interface SimpleAnnotation {
    public String a();
    public int b();
)
The declaration of the first complex annotation example above is the following:
@Target(ElementType.METHOD)
public @interface ComplexAnnotation {
    public SimpleAnnotation value();
)
This is not too much different at first glance, but note that the type of the member value is now an annotation itself. This is how complex, nested annotations work. In fact, it's illegal for an annotation to declare a member that is not either a simple type (int, float, String, etc.) or an annotation. For example, you can't define an annotation that returns java.lang.Object. The reason for this is that annotation values must be constant expressions to the compiler, and there is no way to express an instance of java.lang.Object in source code such that it can be statically evaluated by the Java compiler. (You might at this point argue that string literals in source code can be concatenated with plus signs--doesn't that result in a non-constant expression? No, it doesn't. This is actually a trick of the compiler; it statically concatenates string literals at compile-time, so in the compiled code there is no difference between "ABC" and "A"+"BC".)

Let's move on the the next example where we declare an array member:

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation[] value();
)
Very straightforward; just add the square brackets onto the type, just like you're used to doing elsewhere in Java code.

Now here comes something a bit trickier. Annotation members can have default values so that users of the annotation don't need to specify a value for a member in every annotation. In this sense, the member value is optional in that it can be omitted by the user of the annotation, though the value will be the default specified by the annotation type. The way to declare this is quite easy:

@Target(ElementType.METHOD)
public @interface SimpleAnnotation {
    public String a();
    public int b() default -1;
)
Now, the user of the annotation can leave out the value of b, in which case its value will be -1:
@SimpleAnnotation(a="...")
public Object foo() {...}
So far, so good. Now, what happens when we want to specify a default for an array member? The logical default for an array would be an empty array, but how do we express this? Based on your previous Java experience, you may be tempted to use one of the following:
@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation[] value() default new SimpleAnnotation[0]; // wrong!
)

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation[] value() default SimpleAnnotation[0]; // wrong!
)

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation[] value() default null; // wrong!
)
Each of these is incorrect, however. The first is not a constant expression (the keyword new necessarily indicates an expression that can only be evaluated at runtime). The second is simply not valid Java. The third, specifying null, though seemingly a constant expression, is always an invalid expression according to the JSR-175 specification.

Here's the proper way to declare the empty array:

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation[] value() default {}; // right!
)
You should recognize this expression, though I admit it wasn't immediately obvious to me.

OK, now it's time for Final Jeopardy: how do you declare a default for a non-array annotation member? If you're like me, you're immediately tempted to specify null, because that's what we normally do in Java code. But, remember, null is always an invalid default expression according to the spec. Here are some variations you might try:

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation value() default null; // wrong!
)

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation value() default SimpleAnnotation; // wrong!
)

@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation value() default new SimpleAnnotation(); // wrong!
)

// arghhh!!
The compiler error for each of these attempts is "annotation value must be an annotation". This is utterly confusing at first, but does give you a hint. We need to return an annotation as the default, so how do we get one? How do we normally get other annotations? We simply type them:
@Target(ElementType.METHOD)
public @interface ReallyComplexAnnotation {
    public SimpleAnnotation value() default @SimpleAnnotation(a="..."); // right!
)
This seems completely counter-intuitive at first because we are using the "annotate X" syntax with the @ sign, but there is no apparent "X" to be annotated here. Furthermore, the target for the annotation seems to be ignored. Indeed, this seems like an inconsistency, but think of this syntax instead as specifying the annotation that will be used by default if no annotation is specified. If you think of it this way, it is pretty obvious how this works--it's as if the author had typed the default annotation in their code. Because everything is a constant at compile-time, there is no inconsistency in terms of specifying the annotation here instead of in the user's code.
Comments:

I am trying to construct a really complex annotation in code very similar to this: @ReallyComplexAnnotation( name = "name...", array = { @SimpleAnnotation(a="...", b=3), @SimpleAnnotation(a="...", b=4) } ) So my example is just a bit more complex than yours in that my ReallyComplexAnnotation defines two fields where one is an array of SimpleAnnotations. However, I get the following compiler error: Error:Error:line (33)annotation ReallyComplexAnnotation is missing <clinit> Do you know what's up with that? Is my syntax wrong? Is the above syntax even allowed? Do you need an element named "value" to make it work?

Posted by Eric on October 05, 2006 at 01:56 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

toddfast

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today