ivan_ivanovich_ivanoff
ivan_ivanovich_ivanoff

Reputation: 19463

Annotation member which holds other annotations?

I want to create a custom annotation (using Java) which would accept other annotations as parameter, something like:

public @interface ExclusiveOr {
    Annotation[] value();
}

But this causes compiler error "invalid type for annotation member".

Object[] also doesn't work.

Is there a way to do what I want?

Upvotes: 10

Views: 15455

Answers (5)

Karl the Pagan
Karl the Pagan

Reputation: 1965

I just ran into this exact problem, but (inspired by @ivan_ivanovich_ivanoff) I have discovered a way to specify a bundle of any combination of Annotations as an annotation member: use a prototype / template class.

In this example I define a WhereOr (i.e. a "where clause" for my model annotation) which I need to contain arbitrary Spring meta-annotations (like @Qualifier meta-annotations).

The minor (?) defect in this is the forced dereferencing that separates the implementation of the where clause with the concrete type that it describes.

@Target({})
@Retention(RetentionPolicy.RUNTIME)
public @interface WhereOr {
    Class<?>[] value() default {};
}

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonModel {
    Class<?> value();
    WhereOr where() default @WhereOr;
}

public class Prototypes {
    @Qualifier("myContext")
    @PreAuthorize("hasRole('ROLE_ADMINISTRATOR')")
    public static class ExampleAnd {
    }
}

@JsonModel(
        value = MusicLibrary.class,
        where = @WhereOr(Prototypes.ExampleAnd.class)
)
public interface JsonMusicLibrary {
    @JsonIgnore
    int getMajorVersion();
    // ...
}

I will programmatically extract the possible valid configurations from the "where clause" annotation. In this case I also use the prototypes class as a logical AND grouping and the array of classes as the logical OR.

Upvotes: 0

Feuermurmel
Feuermurmel

Reputation: 9952

Depending on the reason why you would want to specify other annotations there are multiple solutions:

An array of instances of a single annotation type

Probably not what you meant in your question, but if you want to specify multiple instances of a single annotation type it's certainly possible:

public @interface Test {
    SomeAnnotation[] value();
}

An array of annotation types instead of instances

If you do not need to specify any parameters on the individual annotations you can just user their class objects instead of instances.

public @interface Test {
    Class<? extends Annotation>[] value();
}

But an enum would of course also do the trick in most situations.

Use multiple arrays

If the set of possible annotation types you want to use is limited, you can create a separate parameter for each one.

public @interface Test {
    SomeAnnotation[] somes() default { };
    ThisAnnotation[] thiss() default { };
    ThatAnnotation[] thats() default { };
}

Giving a default value to each member makes it possible to only specify arrays for the types you need.

Upvotes: 4

ivan_ivanovich_ivanoff
ivan_ivanovich_ivanoff

Reputation: 19463

I myself hereby propose a workaround for the given problem:

Well, what I wanted to make possible was something like that:

@Contract({
    @ExclusiveOr({
        @IsType(IAtomicType.class),
        @Or({
            @IsType(IListType.class),
            @IsType(ISetType.class)
        })
    })
})

Proposed workaround:

Define a class with parameter-less constructor (which will be called by your own annotation processor later) in following way:

final class MyContract extends Contract{
    // parameter-less ctor will be handeled by annotation processor
    public MyContract(){
        super(
            new ExclusiveOr(
                new IsType(IAtomicType.class),
                new Or(
                    new IsType(IListType.class),
                    new IsType(ISetType.class)
                )
            )
        );
    }
}

usage:

@Contract(MyContract.class)
class MyClass{
    // ...
}

Upvotes: 2

Michael Myers
Michael Myers

Reputation: 192035

The error is produced because you can't use interfaces as annotation values (change it to Comparable and you'll get the same error). From the JLS:

It is a compile-time error if the return type of a method declared in an annotation type is any type other than one of the following: one of the primitive types, String, Class and any invocation of Class, an enum type, an annotation type, or an array of one of the preceding types. It is also a compile-time error if any method declared in an annotation type has a signature that is override-equivalent to that of any public or protected method declared in class Object or in the interface annotation.Annotation.

I'm afraid I don't know of a good workaround, but now at least you know why you get the error.

Upvotes: 6

Todd R
Todd R

Reputation: 18526

You can do:

Class<? extends Annotation>[] value();

Not sure if that helps, but . . .

Upvotes: 2

Related Questions