Reputation: 6808
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 @Override
n (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
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
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