George Z.
George Z.

Reputation: 6808

Is it possible to know whether an Annotation Method is overriden (boolean values)?

I have tried a lot of things online but nothing seem to work for me. I want to know whether an annotation method has been @Overriden (either with the same value as its default).

Take a look at this example:

public class AnnoTest {

    @Anno
    private String something;

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        Field field = AnnoTest.class.getDeclaredField("something");
        field.setAccessible(true);
        boolean isDefault= field.getAnnotation(Anno.class).annotationType().getDeclaredMethod("include").isDefault();
        System.out.println(isDefault); //returns false

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;
    }
}

For some reason it returns false. When i change it to:

@Anno(include = false)
private String something;

It returns false again. Is there a way to know whether the value has been declared in the annotation?

I know i could just compare the default value and its current value, but it will not work for me. I want to know if it has been declared.


With other words I need some kind of magic boolean that does the following:

@Anno
private String something;

return false.

@Anno(include = true)
private String something;

return true.

@Anno(include = false)
private String something;

return true.


The reason of this is that i am wishing to add a method (to my annotation) named "parent". When a parent (a String) has been declared the annotation, this field will inherit the annotation of the field named parent. Take a look at this example:

public class AnnoTest {

    @Anno(include = false)
    private Something something = new Something();

    @Anno(parent = "something")
    private Something somethingElse  = new Something();

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        AnnoTest test = new AnnoTest();

        Field somethingField = AnnoTest.class.getDeclaredField("something");
        somethingField.setAccessible(true);

        Field somethingElseField = AnnoTest.class.getDeclaredField("somethingElse");
        somethingField.setAccessible(true);

        Anno anno = somethingElseField.getAnnotation(Anno.class);

        if (anno.parent().equals("something")) {
            boolean include = somethingField.getAnnotation(Anno.class).include();
            test.somethingElse.isIncluded = include;
        }

        //If not declared it will return true, which it should be false, because "something" field has it false.
        boolean include = somethingElseField.getAnnotation(Anno.class).include();
        //if somethingElse has declared "include", dominate the value, else keep it from the parent
        test.somethingElse.isIncluded = include;

    }

    public class Something {
        boolean isIncluded;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;

        String parent() default "";
    }
}

Upvotes: 4

Views: 2721

Answers (2)

Azad
Azad

Reputation: 81

I know a few years has passed but for reference, there is a less dark magic way to achieve this. If you have access to the .java file, you can use the JavaCompiler api to process the annotations and know whether an Annotation Method is overriden. Small example:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.*;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.Trees;

import java.util.List;
import java.util.Set;

@Retention(RetentionPolicy.RUNTIME)
@interface Anno {
    boolean include() default false;
}

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
class AnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(Anno.class)) {
            final Trees trees = Trees.instance(processingEnv);
            List<? extends AnnotationTree> annotationTrees = ((MethodTree) trees.getTree(e)).getModifiers().getAnnotations();
            System.out.printf("%s is annotated with %s%n", e, annotationTrees);
            if (annotationTrees.size() > 0 && annotationTrees.get(0).getArguments().size() > 0) {
                System.out.println("Using overridden value");
            } else {
                System.out.println("Using default value");
            }
        }
        return true;
    }
}

class Main {
    @Anno(include = false)
    public void includeFalse() {
    }

    @Anno(include = true)
    public void includeTrue() {
    }

    @Anno()
    public void includeDefault() {
    }

    public static void main(String[] args) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        File file = new File(System.getProperty("user.dir") + "/src/Annot.java"); // Location of the .java file
        Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(file));
        JavaCompiler.CompilationTask task = compiler.getTask(null,
                fileManager,
                null,
                null,
                null,
                fileObjects);
        task.setProcessors(Collections.singletonList(new AnnotationProcessor()));
        task.call();
    }

}

I still wouldn't recommend doing this because as mentioned earlier it can cause a lot of confusion for the users. In fact the only reason I know this trick is because it caused a very hard to find bug in our code :)

Upvotes: 2

meriton
meriton

Reputation: 70584

The reflection api does not allow to query whether an annotation value has been specified explicitly or merely defaulted.

The usual workaround is to specify a default value that nobody in their right mind would specify explicitly, and check for that value instead. For instance, JPA uses "" for that purpose.

One might try

Boolean value() default null;

but as you rightly point out in the comments, java does not support Boolean annotation values, only boolean ones. You could use an enum with 3 values instead, but that's probably burdensome for your users.

That leaves dark magic: You could parse the classfile yourself. This would work, because the classfile only lists specified annotation attributes, as the following javap output shows:

Given

@Anno(false)
public void foo() 

we get

Constant pool:
    ...
    #16 = Utf8               Lstackoverflow/Anno;
    #17 = Utf8               value
    #18 = Integer            0

  public void foo();
    descriptor: ()V
    flags: ACC_PUBLIC
    RuntimeVisibleAnnotations:
      0: #16(#17=Z#18)

but given

@Anno()
public void foo() {

we get

Constant pool:
  ...
  #16 = Utf8               Lstackoverflow/Anno;

public void foo();
  descriptor: ()V
  flags: ACC_PUBLIC
  RuntimeVisibleAnnotations:
    0: #16()

That said, even if you manage to do this, you might surprise your users and confuse their tooling. For instance, it's quite possible that an IDE will flag explicit assignments of a default value as redundant.

If at all possible, I'd therefore change your annotation so you don't have to distinguish whether a boolean has been specified explicitly.

Upvotes: 4

Related Questions