5

I tried to create a "map" using nested JAVA annotations.

public @interface EnvInstance {
    Env env();
    Instance instance();
}

public @interface Configuration {
    String description();
    EnvInstance[] envInstances() default {};
}

@Configuration(description = "Test", envInstances = {
    @EnvInstance(env = Env.CERT, instance = Instance.FIRST),
    @EnvInstance(env = Env.INTEGR, instance = Instance.SECOND),
    @EnvInstance(env = Env.PROD, instance = Instance.FIRST),
    ...
}
)
public class TestObject {

}

It seems to work but there is one thing I don't know how to achieve. I want to create two default sets of envInstances configuration so I can type:

@Configuration(description = "Test", envInstances = SET_ONE)
public class TestObject {
}

or

@Configuration(description = "Test", envInstances = SET_TWO)
public class TestObject {
}

Is there a possibility to create a static array of inner annotations or something like this and pass it to the outer annotation?

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
aerion
  • 702
  • 1
  • 11
  • 28

2 Answers2

2

I am afraid that there is no way to extract this duplication.

You cannot supply an array value to annotation from constant (read more). It is also not possible to create annotation that extends another annotation (read more).

I don't know the context, but have you considered passing this information into the object itself, and storing it as a field, rather than via annotations?

Another potential solution that might work is to make these classes implement a marker interface and annotate an interface instead. Annotations however are not inherited. If you can modify your parser (or whatever is reading the annotations), you could do something like this:

@Retention(RetentionPolicy.RUNTIME)
public @interface X {
    String[] value();
}

@X({"a", "b", "c"})
interface AnInterface {}

public static class TestClass implements AnInterface {}

public static void main(String[] args) {
    // annotations are not inherited, empty array
    System.out.println(Arrays.toString(TestClass.class.getAnnotations()));

    // check if TestClass is annotated with X and get X.value()
    Arrays.stream(TestClass.class.getAnnotatedInterfaces())
            .filter(type -> type.getType().equals(AnInterface.class))
            .map(type -> (Class<AnInterface>) type.getType())
            .findFirst()
            .ifPresent(anInterface -> {
                String[] value = anInterface.getAnnotation(X.class).value();
                System.out.println(Arrays.toString(value));
            });
}
Community
  • 1
  • 1
Jaroslaw Pawlak
  • 5,538
  • 7
  • 30
  • 57
  • 1
    Not an option. :( I guess I will have to be forced to copy-paste all the configuration for every single environment for every single object. Anyway I see a nice functionality for incoming Java releases. ;) – aerion Nov 10 '15 at 13:29
  • I will wait couple hours for other answers. Maybe someone will figure it out. If not, I'll mark your answer as correct. Thanks! – aerion Nov 10 '15 at 13:30
  • @aerion there is one more possibility but requires to modify the parser (or whatever reads the annotations). See my edit. – Jaroslaw Pawlak Nov 10 '15 at 13:35
  • Clever but not very elegant and makes hard to override other configuration data. Anyway thanks! – aerion Nov 10 '15 at 14:49
2

Is there a possibility to create a static array of inner annotations or something like this and pass it to the outer annotation?

No. For primitive or string-valued annotation elements you can declare a static final constant somewhere else and refer to that in the annotation, but this does not work for array-valued elements.

The Java language specification section 9.7.1 mandates that any value specified for an array-valued annotation element that does not syntactically start with an opening brace must be treated as shorthand for a single-element array, i.e. the parser treats

@Configuration(description = "Dupa", envInstances = SET_ONE)

as if it said

@Configuration(description = "Dupa", envInstances = {SET_ONE})

and fails because you're trying to set envInstances to an EnvInstance[][] instead of an EnvInstance[].


The precise wording (my bold on the sections that apply to this case):

It is a compile-time error if the element type is not commensurate with the element value. An element type T is commensurate with an element value V if and only if one of the following is true:

  • T is an array type E[], and either:
    • If V is a ConditionalExpression or an Annotation, then V is commensurate with E; or
    • If V is an ElementValueArrayInitializer, then each element value that V contains is commensurate with E.

[snip]

  • T is not an array type, and the type of V is assignment compatible (§5.2) with T, and:
    • If T is a primitive type or String, then V is a constant expression (§15.28).

[snip]

Community
  • 1
  • 1
Ian Roberts
  • 120,891
  • 16
  • 170
  • 183