I try to implement several static analyses for Java bytecode. They try to compute if a certain method has a specific property, e.g. is a factory method. Because these analyses are difficult to test, I decided to write some Java code and annotate the methods directly with the correct property. After running the analysis, it is quite easy to check automatically whether the computed and the annotated property is the same.
MyAnnotation:
@Retention(RUNTIME)
@Target(METHOD)
public @interface FactoryMethodProperty {
FactoryMethodKeys value() default FactoryMethodKeys.NonFactoryMethod;
}
Example test code:
public class PublicFactoryMethod {
private PublicFactoryMethod(){
// I'm private
}
@FactoryMethodProperty
public static void newInstanceAfterOtherConstructorCall(){
new TransFacoryMethod();
new PublicFactoryMethod();
}
@FactoryMethodProperty(FactoryMethodKeys.IsFactoryMethod)
public static PublicFactoryMethod newInstance(){
return new PublicFactoryMethod();
}
}
Because most of the methods in my test code are no factory methods, I set the default to the enum value "FactoryMethodKeys.NonFactoryMethod". But when I don't explicitly pass the enum value to the annotation, it is not compiled to the bytecode.
Bytecode:
#23 = Utf8 value
#24 = Utf8 Lorg/opalj/fpa/test/annotations/FactoryMethodKeys;
#25 = Utf8 IsFactoryMethod
{
public static void newInstanceAfterOtherConstructorCall();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
RuntimeVisibleAnnotations:
0: #16()
Code:
stack=1, locals=0, args_size=0
0: new #17 // class factoryMethodTest/TransFacoryMethod
3: invokespecial #19 // Method factoryMethodTest/TransFacoryMethod."<init>":()V
6: new #1 // class factoryMethodTest/PublicFactoryMethod
9: invokespecial #20 // Method "<init>":()V
12: return
LineNumberTable:
line 49: 0
line 50: 6
line 51: 12
LocalVariableTable:
Start Length Slot Name Signature
public static factoryMethodTest.PublicFactoryMethod newInstance();
descriptor: ()LfactoryMethodTest/PublicFactoryMethod;
flags: ACC_PUBLIC, ACC_STATIC
RuntimeVisibleAnnotations:
0: #16(#23=e#24.#25)
Code:
stack=2, locals=0, args_size=0
0: new #1 // class factoryMethodTest/PublicFactoryMethod
3: dup
4: invokespecial #20 // Method "<init>":()V
7: areturn
LineNumberTable:
line 55: 0
LocalVariableTable:
Start Length Slot Name Signature
}
What did I wrong? Why is the default value completely ignored?
If you take a look for the bytecode for the annotation, you'll see the default there. Using javap -c -v
and trimming the irrelevant things:
...
ConstantPool:
#7 = Utf8 LFactoryMethodKeys
#8 = Utf8 NonFactoryMethod
...
{
public abstract FactoryMethodKeys value();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: e#7.#8}
It doesn't need to be there. At runtime, the JVM constructs an annotation instance that you can retrieve. This instance will be initialized with the default
value which is in the .class
file for the annotation itself. That's represented as an AnnotationDefault
attribute
The
AnnotationDefault
attribute is a variable-length attribute in the attributes table of certainmethod_info
structures (§4.6), namely those representing elements of annotation types. TheAnnotationDefault
attribute records the default value for the element represented by themethod_info
structure.Each
method_info
structure representing an element of an annotation type may contain at most oneAnnotationDefault
attribute. The Java Virtual Machine must make this default value available so it can be applied by appropriate reflective APIs.
You'll eventually invoke the annotation instance's value()
method (or whatever other method you defined) and it will return that value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With