Why can #Repeatable Annotations not be inherited from interfaces - java

In Java annotations marked as #Inherited will only work when annotating classes:
Note that this meta-annotation type has no effect if the annotated
type is used to annotate anything other than a class. Note also that
this meta-annotation only causes annotations to be inherited from
superclasses; annotations on implemented interfaces have no effect.
So interfaces or methods annotated with an #Inherited annotation will not result in implementing classes/methods to also be annotated with the annotation. The reason for this is most likely, that the compiler would'n know which of the annotations to choose, if there are multiple annotations in the class hierarchy as described here.
Now Java 8 introduced the new annotation #Repeatable. I think it would have been natural to remove the above restrictions for annotations that are both marked as #Inherited and #Repeatable, because the compiler should then be able to add the conflicting annotations to the #Repeatable annotation.
Given the following example:
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Inherited
#interface RepeatableAnnotations {
RepeatableAnnotation[] value();
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Inherited
#Repeatable(RepeatableAnnotations.class)
#interface RepeatableAnnotation {
String value();
}
#RepeatableAnnotation("A")
interface IntefaceA {}
#RepeatableAnnotation("B")
interface IntefaceB {}
#RepeatableAnnotation("C")
#RepeatableAnnotation("D")
public class TestClass implements IntefaceA, IntefaceB {
public static void main(String[] args) {
for (RepeatableAnnotation a : TestClass.class.getAnnotation(RepeatableAnnotations.class).value()) {
System.out.print(a.value());
}
}
}
I would have hoped the output to be ABCD but it is "just" CD (i.e. #Inherited is working exactly like pre Java 8).
Does anyone know if there was good reason for not removing the #Inherited restrictions regarding interfaces and methods in the case of #Repeatable annotations for Java 8?
Is there any workaround to achieve the ABCD output for the above type hierarchy? (other than using reflection to scan the super interfaces for annotations...)

Please recall the documentation of #Inherited:
If an Inherited meta-annotation is present on an annotation type declaration, and the user queries the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class's superclass will automatically be queried for the annotation type.
In other words, #Inherited never was intended to be a feature for collecting multiple annotations on a type hierarchy. Instead, you will get the annotation of the most specific type which has an explicit annotation.
In other words, if you change your declaration to
#RepeatableAnnotation("FOO") #RepeatableAnnotation("BAR") class Base {}
#RepeatableAnnotation("C") #RepeatableAnnotation("D")
public class TestClass extends Base implements IntefaceA, IntefaceB {
it won’t change the result; FOO and BAR of Base are not inherited by TestClass as it has the explicit annotation values C and D.
Expanding this to the interface hierarchy would be awkward due to the multiple inheritance and the fact that a super-interface may turn out to be a sub-interface of another super-interface so finding the most specific one is not trivial. This differs heavily from the linear search of the superclass hierarchy.
You may encounter the situation where multiple unrelated annotated interfaces exist but it’s not clear why this ambiguity should be resolved by joining them into one repeated annotation. This would not harmonize with the behavior in all other scenarios.
Note that the answer you have linked is a bit odd as it shows code using a method annotation but method annotations are never inherited, regardless of whether you specified #Inherited or not (an audit tool should generate a warning when you combine #Target(ElementType.METHOD) with #Inherited, imho). #Inherited is relevant for type annotations only.

but I found this thread searching for a similar solution. In the end I wrote this helper method:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Labels
{
Label[] value();
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Repeatable(Labels.class)
public #interface Label
{
String value();
}
#Label("A")
class A
{
}
#Label("B")
class B extends A
{
}
#Test
void LabelStackTest()
{
var labels = ClassUtils.getAnnotatedLabels(B.class);
assertThat(labels).contains("A");
assertThat(labels).contains("B");
}
public static List<String> getAnnotatedLabels(Class<?> labeledClass)
{
var labels = new ArrayList<String>();
do
{
labels.addAll(Arrays.asList(labeledClass.getAnnotationsByType(Label.class))
.stream()
.map(labelAnnotations -> labelAnnotations.value())
.toList());
labeledClass = labeledClass.getSuperclass();
} while (labeledClass != Object.class);
return labels;
}

Related

Repeatable Annotation Interfaces

The JLS states:
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Repeatable(FooContainer.class)
#interface Foo {}
#Target(ElementType.ANNOTATION_TYPE)
#interface FooContainer {
Foo[] value();
}
#Foo can appear on any class or interface declaration while #FooContainer can appear on only annotation interface declarations. Therefore, the following annotation interface declaration is legal:
#Foo #Foo
#interface Anno {}
while the following interface declaration is illegal:
#Foo #Foo
interface Intf {}
I don't understand why the latter one is illegal.
Because #Foo #Foo is actually #FooContainer({#Foo, #Foo}) and #FooContainer is only allowed on annotation types, not on interfaces (as declared by #Target(ElementType.ANNOTATION_TYPE)).
#Foo itself is only valid on types (#Target(ElementType.TYPE)), which includes annotation types. Annotations can always be used without their container annotation, but different targets might apply.

Unable to create annotation with multiple bound generic class arguments

I'm trying to create an annotation which can accept multiple classes as input. Typical usage would be
#Prerequisites{FirstPrerequisite.class, SecondPrerequisite.class}
For this I can create an annotation as shown below
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Inherited
public #interface Prerequisites {
public Class<?>[] value();
}
I want to restrict the types that are allowed in the prerequisites annotation. If I give something like public Class<? extends Dependency>[] value(); It is working however, the problem is My FirstPrerequisite and SecondsPrerequisite may bot be extended from same type. I tried the following but none seemed to work, they are giving compilation errors.
public Class<? extends Dependency & TestCaseStep>[] value();
public Class<? extends Dependency , TestCaseStep>[] value();
public Class<T extends Dependency & TestCaseStep>[] value();
How to bound Generics to take inputs of two different types?
One option is to create a marker interface and have your classes implement the interface. You can then provide a bound with that interface type.
Another alternative is to simply move the constraint checking at runtime instead of at compile time. Have your annotation processor validate the Class arguments that were provided.

Can't implement generics with java.lang.annotation.Annotation: incompatible types

I am wanting to generically retrieve annotations based on where a provided is retrieving values. Either from a field or getters/setters. So, a provider needs to return information in a Field annotation. There are other annotations planned, so the implementations needs to be generic.
The method is exactly what is in the java.lang.Class class.
import java.lang.annotation.Annotation;
public interface Setter<Instance, Type>
{
<A extends Annotation> A getAnnotation(Class<A> annotationClass);
}
But, when I use the method..
final Field annotation = setter.getAnnotation(Field.class);
I get a complication error..
incompatible types
found : java.lang.annotation.Annotation
required: Field
This makes nearly no sense because Field is an Annotation!
Are there special considerations to give to annotations while implementing generics?
Why isn't this working?? It is exactly like the Java API.
Just for more information, here is the Field annotation.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.METHOD})
public #interface Field
{
/**
* The effective path of the local or result set value to load into the field.
*/
String value() default "";
}

Java annotations fundamentals

I am a newbie to annotations. I have gone through a lot of tutorials explaining the concept of annotations. But nowhere do i find information about defining multiple annotations within a class. So pls give me some insight on defining and accessing multiple annotations.Below is the code where I define two annotations in a class and eclipse IDE presents me an error "The public type SampleAnn must be defined in its own file".. Is the reason for this error because of the java convention that "there should only one public annotation per class in the name of the class-name"?
#Documented
#Target(ElementType.LOCAL_VARIABLE)
#Inherited
#Retention(RetentionPolicy.RUNTIME)
public #interface MethodInfo{
int number1;
}
#Documented
#Target(ElementType.LOCAL_VARIABLE)
#Retention(RetentionPolicy.RUNTIME)
public #interface SampleAnn{
int number2;
}
You are right, you can have only a single top-level class in one file.
But what you can do:
public class MyAnnotations {
public #interface SampleAnn { ... }
public #interface MethodInfo { ... }
}
There should more generally be ONE public CLASS per class file and annotations are no exception. It is also important that any publicly defined entity has the same name as its java file's name, so I don't see how you could have two in the same file.
The annotations need to be in separate compilation units (files).
The regarding top-level classes the specification states:
This restriction implies that there must be at most one such type per
compilation unit. This restriction makes it easy for a Java compiler
to find a named class within a package. In practice, many programmers
choose to put each class or interface type in its own compilation
unit, whether or not it is public or is referred to by code in other
compilation units.
Specification

reflect.Field.annotations always null

I am trying to play with reflection and annotations.
For some reason whenever I add an annotation to a field (or a class or a method), and use reflection to look at the field, I see that its annotations field is null.
For example, this code:
public class Test {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
System.out.println(Test.class.getField("bl").getAnnotations().length);
}
#anno
public int bl;
public #interface anno {}
}
prints 0.
BTW, Java does not ignore annotations in general, and when (for example) I use the #Deprecated annotation - Eclipse recognizes it and tells me the class is deprecated.
I am using Eclipse Indigo and Java SE Development Kit 7 update 2.
Thanks!
By default, annotations are not available to reflection. In order to do so, you need to change the retention policy, i.e.
#Retention(RetentionPolicy.RUNTIME)
public #interface Anno {}
The various retention policies can be found in the Javadoc. The default (for mysterious reasons that nobody seems to know) is CLASS.
I would check the #Retention e.g.
#Documented
#Retention(RetentionPolicy.RUNTIME)
public #interface Deprecated {
}
If you don't set it, it won't be visible at runtime.

Categories