Search code examples
javareflectionannotationsreflections

How to set default value to annotation variable as the class type of the variable annotated?


I have a custom annotation with a single variable.

I use it to annotate attributes in a class and what i need is that the annotation default value for the variable, be the type of the attribute declared. Here the example:

Annotation:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Annotation{
    Class<?> className() default ???????; // <- here i need to set something that tells my annotation to take the class of the attribute annotated
}

Class using Annotation:

public class Main {

    @Annotation
    private AnotherClass annotatedAttribute;

    //other code
}

And so what i need is that when i get the annotatedAttribute field and i get its annotation and its value of the className() variable, the default value should be the equivalent to AnotherClass.class unless i state otherwise in the declaration of the @Annotation

E.g:

@Annotation(classname= YetAnotherClass.class)

Is there a way to do this?

I saw some posts talking about an annotation processor, but in my case i don't want to generate new classes files since my class already exist and i'm fetching the field and the annotation through reflection (so i'm at runtime level)


Solution

  • There is no way to specify a custom logic in an annotation, so you have to leave it to the code processing the annotation at runtime, however, you can’t use null as a marker value either.

    The only way to tell your annotation processing tool that custom processing is required, is by choosing a dedicated marker type as default value. This might be a type that would otherwise never occur as a regular annotation value, e.g. void.class, or you create a class solely for serving as the marker.

    To show a similar real life example, JUnit’s @Test annotation has an expected element denoting an expected type to be thrown. The default, supposed to express that no exception is expected, can’t be null nor a type outside the Throwable hierarchy as the value must conform to the declared type Class<? extends Throwable>. Therefore, the default value is a dedicated type Test.None that is never thrown and treated specially by the framework when processing the annotation.

    For your case, you have to decide for a suitable marker type or create a dedicated type and adapt the processing code to check for the type. E.g.

    public final class UseFieldType {
        private UseFieldType() {}
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface YourAnnotation {
        Class<?> className() default UseFieldType.class;
    }
    class YourCodeUsedAtRuntime {
        public static Optional<Class<?>> getAnnotationValue(Field f) {
           YourAnnotation a = f.getAnnotation(YourAnnotation.class);
           if(a == null) return Optional.empty();
    
           Class<?> type = a.className();
           if(type == UseFieldType.class) type = f.getType();
           return Optional.of(type);
        }
    }
    class Example {
        @YourAnnotation String string;
        @YourAnnotation(className = Pattern.class) String regEx;
        String none;
    
        public static void main(String[] args) {
            for(Field f: Example.class.getDeclaredFields()) {
                System.out.println(f.getName() + ": "
                    + YourCodeUsedAtRuntime.getAnnotationValue(f)
                          .map(Class::getName).orElse("No annotation"));
            }
        }
    }
    
    string: java.lang.String
    regEx: java.util.regex.Pattern
    none: No annotation