Annotation Support
By Mathias Panzenböck < panzi@complang.tuwien.ac.at >
Contents
A short Introduction to Annotations in Java
Since JDK 5.0 there is the possibility to annotate certain elements. This can be used to supply some kind of documentation to the annotated elements (=metadata), to influence compiler behaviour (e.g. the all known @SuppressWarnings("unchecked")), or to do nifty tricks like some frameworks do (e.g. like Hibernate uses annotations for object relational mapping; or for really easy command line option parsing).
Annotations can be defined for classes and all that can be represented by an instance of java.lang.Class (=class, interface, @interface (annotation type), enum, package), for fields, for methods, for constructors and for parameters. Local variables can also be annotated, but the annotations cannot be accessed at runtime and are simply discarded by the compiler.
Annotations are stored as attributes in the classfile. There are several annotation attributes defined:
RuntimeVisibleAnnotations for classes, methods and fields
RuntimeInvisibleAnnotations for classes, methods and fields
RuntimeVisibleParameterAnnotations for parameters
RuntimeInvisibleParameterAnnotations for parameters
AnnotationDefault for methods
These attributes are loaded when the classfile is read, but will only be parsed when the corresponding annotation is accessed.
Depending on its usage an annotation might not be needed at runtime. E.g. the SuppressWarnings annotation is only needed by the compiler. To tell the compiler that an annotation should be visible at runtime, the annotation itself has to have the annotation @Retention(RetentionPolicy.RUNTIME) declared.
Runtime invisible annotations usually aren't even included in the classfile, but that depends on the compiler (or on compiler flags). If they are included in the classfile, the virtual machine should not load them, unless an implementation specific flag is set, which tells the JVM to do so. CACAO doesn't have such a flag right now and therefore runtime invisible annotations are simply discarded.
You declare annotations like this:
1 public @interface MyAnnotation {
2 int intValue();
3 String stringValue() default "foo bar";
4 Class<?>[] classArray() default {Foo.class, Class.class};
5 SuppressWarnings annotationValue() default @SuppressWarnings("unused");
6 }
All methods defined in annotations have zero parameters and their return type must be one of the following:
a primitive type: boolean, byte, short, char, int, long, float or double
java.lang.String
java.lang.Class<?>
an enum type
an annotation type (e.g. SuppressWarnings)
- or an one-dimensional array thereof
You use annotations like this:
1 import java.util.Map;
2 @MyAnnotation(intValue=42)
3 class MyClass {
4 @MyAnnotation(intValue=23, stringValue="hello world")
5 public int aField;
6 @Deprecated
7 public void aDeprecatedMethod(
8 @MyAnnotation(intValue=5, classArray=void.class)
9 int a) {
10 }
11 @SuppressWarnings({"unchecked", "unused"})
12 public MyClass() {
13 Map<String,String>[] map = new Map[16];
14 }
15 }
The annotations @SuppressWarnings and @Deprecated are defined by the Java library (amongst many others).
You can then access the annotations of an annotated element by the reflection API. There are several methods to do so. Each annotated element can have an arbitrary number of annotations, but only one per annotation type.
The annotated element interface looks like this:
1 package java.lang.reflect;
2 import java.lang.annotation.Annotation;
3 public interface AnnotatedElement
4 {
5 <T extends Annotation> T getAnnotation(Class<T> annotationClass);
6 Annotation[] getAnnotations();
7 Annotation[] getDeclaredAnnotations();
8 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
9 }
Some classes offer more than this basic set of methods.
java.lang.Class has the method public boolean isAnnotation() which tells you if the referred class is an annotation.
java.lang.reflect.Constructor and java.lang.reflect.Method have the method public Annotation[][] getParameterAnnotations() which returns the annotations for each parameter as a two-dimensional array.
java.lang.reflect.Method has the method public Object getDefaultValue() which returns the default value of a method of an annotation.
Annotations in Java are interfaces. Therefore there has to be an implementing class for each annotation type. But the Java compiler does not generate such an implementation, this actually happens at runtime. The Java runtime has to create an implementation for an annotation type the first time an annotation of a specific annotation type is requested. It then remembers this on the fly generated class and uses it for each further instance of the same annotation type.
Overview of Annotation Support in CACAO
Even though annotations in Java exist since JDK 5.0 I used the classfile specification of JDK 6.0 (JSR202) to implement the annotations support. However, there where no changes between this versions.
Because OpenJDK does a lot concerning annotation support in it's J2SE library I started with annotations support for CACAO + OpenJDK. I thought this would be easier and I would be able to gather some experience for working on the annotation support for CACAO + GNU Classpath. OpenJDK does the annotation parsing in Java code.
What had to be done:
Loading of annotations from the classfile to the classinfo struct
Implementing the JVM_* functions for getting the unparsed annotations for each annotated element as a byte array.
Implementing the sun.reflect.ConstantPool class which is used by OpenJDKs annotation parser to get access to constants that have to be loaded.
The code for the annotation loading is placed in the files src/vmcore/annotation.h and src/vmcore/annotation.c.
This code is called by the loader functions:
bool class_load_attributes(classbuffer *cb)
bool method_load(classbuffer *cb, methodinfo *m, descriptor_pool *descpool)
bool field_load(classbuffer *cb, fieldinfo *f, descriptor_pool *descpool)
See: src/vmcore/class.c, src/vmcore/method.c, src/vmcore/field.c
All annotations and also the annotation default values are stored in the classinfo struct as unparsed byte arrays:
147 #if defined(ENABLE_ANNOTATIONS)
148 java_object_t *annotations;
149
150 java_object_t *method_annotations;
151 java_object_t *method_parameterannotations;
152 java_object_t *method_annotationdefaults;
153 java_object_t *field_annotations;
154 #endif
See: src/vmcore/class.h
I decided to use Java objects (java_bytearray_t and java_objectarray_t) rather than implementing my own array structs, because the annotation parser, that is written in Java, needs Java bytearrays anyway. I would have had to copy the whole unparsed annotation when they are parsed, which would had reduced speed and increased memory usage.
For this to be possible twisti had to adapt CACAOs bootstrap process, so that the primitive table already exists when classes will be loaded (because I use builtin_newarray_byte, bultin_anewarray and primitive_arrayclass_get_by_type during loading of the annotation attributes).
Futher I added functions to access the unparsed annotations as Java byte arrays:
class_get_annotations
method_get_annotations
method_get_parameterannotations
method_get_annotationdefault
field_get_annotations
See: src/vmcore/class.h, src/vmcore/field.h, src/vmcore/method.h
And used them in src/native/vm/openjdk/jvm.c to implement these functions:
JVM_GetClassAnnotations
JVM_GetFieldAnnotations
JVM_GetMethodAnnotations
JVM_GetMethodDefaultAnnotationValue
JVM_GetMethodParameterAnnotations
Then I had to add an implementation for OpenJDKs ConstantPool class by filling in these functions (not all of them are used in the annotations support):
JVM_GetClassConstantPool
JVM_ConstantPoolGetSize
JVM_ConstantPoolGetClassAt
JVM_ConstantPoolGetClassAtIfLoaded
JVM_ConstantPoolGetMethodAt
JVM_ConstantPoolGetMethodAtIfLoaded
JVM_ConstantPoolGetFieldAt
JVM_ConstantPoolGetFieldAtIfLoaded
JVM_ConstantPoolGetMemberRefInfoAt
JVM_ConstantPoolGetIntAt
JVM_ConstantPoolGetLongAt
JVM_ConstantPoolGetFloatAt
JVM_ConstantPoolGetDoubleAt
JVM_ConstantPoolGetStringAt
JVM_ConstantPoolGetUTF8At
In the annotation attributes in the classfile are no values stored, but indices of members of the constant pool of the annotated class. The values at these indices represent the values of the annotations' field. Therefore the ConstantPool class is needed by the annotation parser to access those constants.
This was roughly all that what was needed for annotation support for CACAO + OpenJDK. For GNU Classpath much more had to be done. First there is no annotations parser in GNU Classpath, but because of the GPL I just imported the one from OpenJDK. So far so good, but GNU Classpath did not do anything about annotations except in java.lang.Class. I had to implement the "high level" annotations interface:
Annotation[] java.lang.VMClass.getDeclaredAnnotations()
Annotation java.lang.reflect.Constructor.getAnnotation(Class annotationClass)
Annotation[] java.lang.reflect.Constructor.getDeclaredAnnotations()
Annotation java.lang.reflect.Field.getAnnotation(Class annotationClass)
Annotation[] java.lang.reflect.Field.getDeclaredAnnotations()
Annotation java.lang.reflect.Method.getAnnotation(Class annotationClass)
Annotation[] java.lang.reflect.Method.getDeclaredAnnotations()
See: src/native/vm/gnuclasspath/java_lang_VMClass.c, src/classes/gnuclasspath/java/lang/reflect/Constructor.java, src/native/vm/gnuclasspath/java_lang_reflect_Constructor.c, src/classes/gnuclasspath/java/lang/reflect/Field.java, src/native/vm/gnuclasspath/java_lang_reflect_Field.c, src/classes/gnuclasspath/java/lang/reflect/Method.java, src/native/vm/gnuclasspath/java_lang_reflect_Method.c
I implementined caching of the parsed annotations almost identical to OpenJDK, except for java.lang.Class, because that class already had the annotation interface implemented (without caching). So I would have had to import this class from GNU Classpath, change it and keep it up to date. Twisti said that's not worth the effort and that OpenJDK is the future.
Because I used the annotation parser from OpenJDK I had to implement the sun.reflect.ConstantPool class for GNU Classpath, too.
See: src/classes/gnuclasspath/sun/reflect/ConstantPool.java, src/native/vm/gnuclasspath/sun_reflect_ConstantPool.cpp
The ugly bit there is the redundant implementation of this class. It is still under discussion what we do in such a case. We agreed that until a better solution is found, we will just keep the redundant implementations.
Imported Files
Following files where imported from GNU Classpath or OpenJDK and have to be kept up to date. Files marked with * where changed or extended a bit to be usable with CACAO. All this imported files are used with GNU Classpath only.
Importet from GNU Classpath:
Imported from OpenJDK:
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationParser.java*
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationType.java*
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/ExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/TypeNotPresentExceptionProxy.java
TODO: Keep these imported files up to date with OpenJDK/GNU Classpath.
Files I touched and what I did to them
List of touched files:
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationParser.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationType.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/ExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/TypeNotPresentExceptionProxy.java
THIRDPARTY
Added copyright notice for this imported OpenJDK files:
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationParser.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationType.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/ExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/TypeNotPresentExceptionProxy.java
configure.ac
Annotations Support will be built if the configure option --enable-annotations is supplied. As of the next release after the summer of 2007 this option will be enabled by default.
Twisti later moved this option to the file m4/annotations.m4.
If possible, all annotations support code is #ifdef-ed with the ENABLE_ANNOTATIONS macro.
src/cacaoh/dummy.c
Simple/dummy implementations of following functions where added:
builtin_newarray_byte
array_objectarray_element_get
array_objectarray_element_set
array_length_get
builtin_anewarray
primitive_arrayclass_get_by_type
src/classes/Makefile.am
I added src/classes/gnuclasspath/java/lang/reflect/Constructor.java to VM_JAVA_FILES in this file.
Following files are only added to VM_JAVA_FILES if ENABLE_ANNOTATIONS is defined:
src/classes/gnuclasspath/sun/reflect/ConstantPool.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationParser.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationType.java
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/ExceptionProxy.java
src/classes/gnuclasspath/sun/reflect/annotation/TypeNotPresentExceptionProxy.java
src/classes/gnuclasspath/java/lang/reflect/Constructor.java
This file was imported from GNU Classpath because I needed to add some methods.
The additions to this class are inspired by OpenJDK (the private interface looks and works the same but is implemented differently).
Following fields where added:
92 private byte[] annotations = null;
This field holds the unparsed annotations.
97 private byte[] parameterAnnotations = null;
This field holds the unparsed parameter annotations.
103 private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations = null;
This field holds the parsed annotations.
111 private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY =
112 new Annotation[0];
Used in getDeclaredAnnotations(). Look there for a description.
Following methods where added/implemented:
443 private synchronized native Map<Class<? extends Annotation>, Annotation> declaredAnnotations();
When called the first time, this method will parse the declared annotations, which are stored unparsed in the field annotations and then stores the result in the field declaredAnnotations as a HashMap. Each successive call will just return this field.
425 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
426 if (annotationClass == null)
427 throw new NullPointerException();
428 return (T)declaredAnnotations().get(annotationClass);
429 }
This method gets the annotation of the constructor of the type specified by annotationClass, or null, if there is no such annotation present.
435 public Annotation[] getDeclaredAnnotations() {
436 return declaredAnnotations().values().toArray(EMPTY_ANNOTATIONS_ARRAY);
437 }
This method gets all the declared annotations in an array. Because the generic type java.util.Collection<Annotation> cannot create an array of type Annotation[] (because of type erasure) the toArray() method must get a zero length template array passed, with which an annotation array can be created.
461 public native Annotation[][] getParameterAnnotations();
This method parses and returns all the parameter annotations in a two-dimensional Annotation array.
TODO: Maybe also cache parsed parameter annotations? However, the API specification does not specify an annotation lookup method like getAnnotation(Class<T> annotationClass) for parameter annotations, and so no Map has to be stored for this. The question is: Is caching the parsed parameter annotation worth it? (OpenJDk does not do it.)
src/classes/gnuclasspath/java/lang/reflect/Field.java
Following fields where added:
1 private byte[] annotations = null;
2 private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations = null;
3 private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY =
4 new Annotation[0];
See: Constructor.java
Following methods where added/implemented:
1 public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
2 public Annotation[] getDeclaredAnnotations()
3 private synchronized native Map<Class<? extends Annotation>, Annotation> declaredAnnotations();
See: Constructor.java
src/classes/gnuclasspath/java/lang/reflect/Method.java
Following fields where added:
1 private byte[] annotations = null;
2 private byte[] parameterAnnotations = null;
3 private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations = null;
4 private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY =
5 new Annotation[0];
See: Constructor.java
1 private byte[] annotationDefault = null;
This field stores the unparsed annotation default value, if this method belongs to an annotation interface. Otherwise, or if there is no default value, it points to null.
Following methods where added/implemented:
1 public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
2 public Annotation[] getDeclaredAnnotations()
3 private synchronized native Map<Class<? extends Annotation>, Annotation> declaredAnnotations();
4 public native Annotation[][] getParameterAnnotations();
See: Constructor.java
1 public native Object getDefaultValue();
This method returns the parsed annotation default value of this method, if this method belongs to an annotation interface. Otherwise, or if there is no default value, it returns null.
src/classes/gnuclasspath/sun/reflect/ConstantPool.java
I imported this class from OpenJDk, where it is used to access the constant pool entries of a class. This is needed when parsing annotations (and at the moment only then).
I guess because it could be a security risk to grant normal Java code access to this class, certain measures where taken to prevent that. First of all, there is no obvious way to get a ConstantPool object in normal Java code. Further the field constantPoolOop of the class ConstantPool is hidden from the reflection API by this code:
61 static {
62 Reflection.registerFieldsToFilter(ConstantPool.class, new String[] { "constantPoolOop" });
63 }
However, this is OpenJDK specific and I imported this class for usage with GNU Classpath. I just out commented this static block, because I could not find a similar mechanism in GNU Classpath. I'm not sure if this is even necessary because there is no way to get a ConstantPool object in normal java code.
However, the field constantPoolOop is just the class object for which the constant pool was requested. I figured this is the easiest way, because in CACAO the access to the constant pool entries takes place by the function voidptr class_getconstant(classinfo *class, u4 pos, u4 ctype).
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationParser.java
Like all classes in src/classes/gnuclasspath/sun/reflect/annotation this class was imported from OpenJDK.
59 public static Annotation[] parseAnnotationsIntoArray(
60 byte[] rawAnnotations,
61 ConstantPool constPool,
62 Class container)
This method is only used by java.lang.Class.
79 public static Annotation[][] parseParameterAnnotations(
80 byte[] parameterAnnotations,
81 ConstantPool constPool,
82 Class container,
83 int numParameters)
This method is a wrapper around public static Annotation[][] parseParameterAnnotations(byte[] rawAnnotations, ConstantPool constPool, Class container) which basically adds a check if parameterAnnotations == null (= no parameter annotations at all) and a check for the parameter count and throws appropriate exceptions.
111 public static Object parseAnnotationDefault(Method method,
112 byte[] annotationDefault,
113 ConstantPool constPool)
This is basically a copy from OpenJDKs java.lang.reflect.Method.getAnnotationDefault() method because I wanted to change as less as possible in the Classpath code and there this method is declared as native. However, I would have to write a method to get a ConstantPool object in java code from which I rather stay away. My policy is: You can't get a ConstantPool, but the VM can give one to "you" (to the right method).
465 private static Class<?> parseSig(String sig, Class container) {
466 if (sig.equals("V")) {
467 return void.class;
468 }
469 else {
470 return toClass(new FieldSignatureParser(container, sig).getFieldType());
471 }
472 }
I had to rewrite this method. It was much more complex and used a lot of classes from OpenJDK I would have had to import. But GNU Classpath has it's own signature parser (gnu.java.lang.reflect.FieldSignatureParser) which I rather used instead. The sig.equals("V") comparison was already in OpenJDKs version. It seems that neither GNU Classpath nor OpenJDK has a return type signature parser, but looking at the specs one can see that a field type signature parser plus this check for void does the same.
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationType.java
Imported from OpenJDK. Needed by AnnotationParser.java.
This class used OpenJDks sun.misc.SharedSecrets feature to keep an annotation Class to AnnotationType mapping at a shared but secrete place. In particular the methods void sun.misc.SharedSecrets.getJavaLangAccess().setAnnotationType(Class annotationClass, AnnotationType annotationType) and AnnotationType sun.misc.SharedSecrets.getJavaLangAccess().getAnnotationType(Class annotationClass) where used.
GNU Classpath does not have such a feature and therefore I simply added a Map to maintain the mapping:
50 private static Map<Class, AnnotationType> annotationTypes =
51 new HashMap<Class, AnnotationType>();
This map is accessed in the methods public static synchronized AnnotationType getInstance(Class annotationClass) and private AnnotationType(final Class<?> annotationClass).
src/classes/gnuclasspath/sun/reflect/annotation/AnnotationTypeMismatchExceptionProxy.java
Imported from OpenJDK. Needed by AnnotationParser.java.
src/classes/gnuclasspath/sun/reflect/annotation/EnumConstantNotPresentExceptionProxy.java
Imported from OpenJDK. Needed by AnnotationParser.java.
src/classes/gnuclasspath/sun/reflect/annotation/ExceptionProxy.java
Imported from OpenJDK. Needed by AnnotationParser.java.
src/classes/gnuclasspath/sun/reflect/annotation/TypeNotPresentExceptionProxy.java
Imported from OpenJDK. Needed by AnnotationParser.java.
src/native/include/Makefile.am
Added sun_reflect_ConstantPool.h to JAVASE_HEADER_FILES.
src/native/llni.h
Added two macros for accessing fields of the classinfo struct which are Java objects:
88 /* LLNI_classinfo_field_get ***************************************************
89
90 Get a field from classinfo that is a java object.
91 ******************************************************************************/
92 #define LLNI_classinfo_field_get(cls, field, variable) \
93 LLNI_CRITICAL_START; \
94 (variable) = LLNI_WRAP((cls)->field); \
95 LLNI_CRITICAL_END
96 /* LLNI_classinfo_field_set ***************************************************
97
98 Set a field from classinfo that is a java object.
99 ******************************************************************************/
100 #define LLNI_classinfo_field_set(cls, field, variable) \
101 LLNI_CRITICAL_START; \
102 (cls)->field = LLNI_UNWRAP(variable); \
103 LLNI_CRITICAL_END
For implementing class unloading the classinfo struct has to be placed onto the Java heap. When this will happen and handles are enabled, accessing members of classinfo will become more difficult (basically like accessing members of other Java objects). To wrap this access I added these macros. It is still not clear how exactly the access will work, but when using these macros one has only to change them instead all code that access Java object fields of classinfo.
src/native/vm/gnuclasspath/Makefile.am
Added sun_reflect_ConstantPool.cpp to SUN_REFLECT_SOURCES. Added java_lang_reflect_Constructor.c to libnativevmcore_la_SOURCES.
src/native/vm/gnuclasspath/java_lang_VMClass.c
If ENABLE_ANNOTATIONS is defined, the method getDeclaredAnnotations will be defined.
src/native/vm/gnuclasspath/java_lang_reflect_Constructor.c
If ENABLE_ANNOTATIONS is defined, the methods declaredAnnotations and getParameterAnnotations will be defined.
Following functions where added:
Java_java_lang_reflect_Constructor_declaredAnnotations
This function implements java.lang.reflect.Constructor.declaredAnnotations. It uses the function struct java_util_Map* reflect_get_declaredannotatios(java_handle_bytearray_t *annotations, java_lang_Class *declaringClass, classinfo *referrer) to do so.
Java_java_lang_reflect_Constructor_getParameterAnnotations
This function implements java.lang.reflect.Constructor.getParameterAnnotations. It uses the function java_handle_objectarray_t* reflect_get_parameterannotations(java_handle_t *parameterAnnotations, int32_t slot, java_lang_Class *declaringClass, classinfo *referrer) to do so.
src/native/vm/gnuclasspath/java_lang_reflect_Field.c
If ENABLE_ANNOTATIONS is defined, the method declaredAnnotations will be defined.
Following function was added:
Java_java_lang_reflect_Field_declaredAnnotations
This function implements java.lang.reflect.Field.declaredAnnotations. It uses the function struct java_util_Map* reflect_get_declaredannotatios(java_handle_bytearray_t *annotations, java_lang_Class *declaringClass, classinfo *referrer) to do so.
src/native/vm/gnuclasspath/java_lang_reflect_Method.c
If ENABLE_ANNOTATIONS is defined, the methods getDefaultValue, declaredAnnotations and getParameterAnnotations will be defined.
Following functions where added:
Java_java_lang_reflect_Method_getDefaultValue
This function implements java.lang.reflect.Method.getDefaultValue. It uses the static method sun.reflect.annotation.AnnotationParser.parseAnnotationDefault(Method m, ConstantPoop cpool) to do so. Because this static method is only used here, I thought it makes sense to cache it's methodinfo here, so I don't have to resolve this method every time getDefaultValue is called. For this purpose I made the m_parseAnnotationDefault pointer static.
Java_java_lang_reflect_Constructor_declaredAnnotations
This function implements java.lang.reflect.Method.declaredAnnotations. It uses the function struct java_util_Map* reflect_get_declaredannotatios(java_handle_bytearray_t *annotations, java_lang_Class *declaringClass, classinfo *referrer) to do so.
Java_java_lang_reflect_Constructor_getParameterAnnotations
This function implements java.lang.reflect.Method.getParameterAnnotations. It uses the function java_handle_objectarray_t* reflect_get_parameterannotations(java_handle_t *parameterAnnotations, int32_t slot, java_lang_Class *declaringClass, classinfo *referrer) to do so.
src/native/vm/gnuclasspath/sun_reflect_ConstantPool.cpp
This file is one of two almost 100% identical implementations of the class sun.reflect.ConstantPool. The other one can be found in src/native/vm/openjdk/jvm.c. The thing is, this class is needed for OpenJDK and GNU Classpath (because I also use OpenJDKs AnnotationParser with GNU Classpath) and even though it does the same thing in both cases, it has to be implemented in different files with different method names. This is a common problem which is discussed on the mailing list. Until we find a proper solution, twisti said I just should implement it twice, no matter the redundancy.
Actually not all methods of this class are used in the annotations support (and therefore in whole OpenJDK, because annotations support still is the only thing which uses this class).
The used methods are:
getIntAt0
getLongAt0
getFloatAt0
getDoubleAt0
getUTF8At0
Not used methods, which I implemented anyway because of their triviality are:
getSize0
getClassAt0
getClassAtIfLoaded0
getMethodAt0
getMethodAtIfLoaded0
getFieldAt0
getFieldAtIfLoaded0
getStringAt0
Methods, which I didn't implement because what they do wasn't clear to me:
getMemberRefInfoAt0
Almost all the implemented functions basically are implemented by calling voidptr class_getconstant(classinfo *class, u4 pos, u4 ctype).
TODO: In the implementations for getStringAt0 and getUTF8At0 I'm not sure if I used the right string_new-function. I used java_object_t *literalstring_new(utf *u) where maybe java_handle_t *javastring_new(utf *text) would have been the correct function. Maybe this has to be changed.
I'm not sure if the implementation of getMethodAt0 is 100% right. (See comment in the source.) But this method is not used anyway.
TODO: Join the two redundant sun.reflect.ConstantPool implementations. It has to be discussed how that has to be done and where this implementation has to be placed.
src/native/vm/java_lang_Class.c
Here I had to implement the getDeclaredAnnotations method, but only for GNU Classpath. OpenJDK does that in the J2SE implementation. The method is implemented in the function java_handle_objectarray_t *_Jv_java_lang_Class_getDeclaredAnnotations(java_lang_Class* klass). This function has to call the static method Annotation[] sun.reflect.annotation.AnnotationParser.parseAnnotationsIntoArray(ConstantPool cpool, Class<?> cls). The methodinfo for this method is cached like it's done in Java_java_lang_reflect_Method_getDefaultValue.
src/native/vm/java_lang_Class.h
Added declaration: java_handle_objectarray_t *_Jv_java_lang_Class_getDeclaredAnnotations(java_lang_Class* klass);
src/native/vm/nativevm.c
Added to GNU Classpath section:
90 #if defined(ENABLE_ANNOTATIONS)
91 _Jv_sun_reflect_ConstantPool_init();
92 #endif
src/native/vm/nativevm.h
Added to GNU Classpath section:
69 #if defined(ENABLE_ANNOTATIONS)
70 void _Jv_sun_reflect_ConstantPool_init();
71 #endif
src/native/vm/reflect.c
In the reflect_*_new-functions for the reflective types, I added the calls to the *_get_annotations functions.
Following functions were added:
reflect_get_declaredannotatios
This function creates a ConstantPool instance for the given declaringClass and calls the static method Map<Class, Annotation> sun.reflect.annotation.AnnotationParser.parseAnnotation(ConstantPool cpool, Class cls) in order to parse the annotations. The methodinfo for this static method is cached as a static variable.
reflect_get_parameterannotations
This function creates a ConstantPool instance for the given declaringClass and calls the static method Annotation[][] sun.reflect.annotation.AnnotationParser.parseAnnotation(ConstantPool cpool, Class cls, int paramcount) in order to parse the parameter annotations. The methodinfo for this static method is cached as a static variable.
src/native/vm/reflect.h
Following declarations were added:
65 #if defined(WITH_CLASSPATH_GNU) && defined(ENABLE_ANNOTATIONS)
66 struct java_util_Map* reflect_get_declaredannotatios(
67 java_handle_bytearray_t *annotations,
68 java_lang_Class *declaringClass,
69 classinfo *referrer);
70 java_handle_objectarray_t* reflect_get_parameterannotations(
71 java_handle_t *parameterAnnotations,
72 int32_t slot,
73 java_lang_Class *declaringClass,
74 classinfo *referrer);
75 #endif
src/native/vm/openjdk/jvm.c
Following functions where implemented:
JVM_GetClassAnnotations
JVM_GetFieldAnnotations
JVM_GetMethodAnnotations
JVM_GetMethodDefaultAnnotationValue
JVM_GetMethodParameterAnnotations
JVM_GetClassConstantPool
JVM_ConstantPoolGetSize
JVM_ConstantPoolGetClassAt
JVM_ConstantPoolGetClassAtIfLoaded
JVM_ConstantPoolGetMethodAt
JVM_ConstantPoolGetMethodAtIfLoaded
JVM_ConstantPoolGetFieldAt
JVM_ConstantPoolGetFieldAtIfLoaded
JVM_ConstantPoolGetMemberRefInfoAt
JVM_ConstantPoolGetIntAt
JVM_ConstantPoolGetLongAt
JVM_ConstantPoolGetFloatAt
JVM_ConstantPoolGetDoubleAt
JVM_ConstantPoolGetStringAt
JVM_ConstantPoolGetUTF8At
The JVM_Get*Annotations functions just call the corresponding *_get_annotations functions.
The JVM_ConstantPool* functions are implementing the methods of the sun.reflect.ConstantPool class.
See: src/native/vm/gnuclasspath/sun_reflect_ConstantPool.cpp
The function JVM_GetClassConstantPool returns a sun.reflect.ConstantPool instance for the given java.lang.Class object.
src/vmcore/Makefile.am
ANNOTATION_SOURCES is only defined if ENABLE_ANNOTATIONS is.
src/vmcore/annotation.c
This file implements the annotation attribute loading.
The annotation_load_* functions are used to load the corresponding annotations and store them into the corresponding classinfo struct.
The unparsed annotations, parameter annotations and annotation default values for methods and fields are also stored in the classinfo, in order to reduce the overhead to the methodinfo and fieldinfo structs. This data is stored in arrays, where you can access the data at the members slot. These arrays are only as big as they need to be, meaning if there are e.g. 10 methods, but only the first one is annotated, the method_annotations array is only one element in size. If there aren't any method annotations at all, method_annotations is NULL.
During the classloading process I can't know which one's the highest slot with annotations, so I first allocate an array that is just big enough for the first annotated slot I parse. If during further loading higher annotated slots occur, a bigger array is allocated and the old elements are copied to it.
Maybe this could be made more efficient by first allocating an array that is as big as the method-/field-count and after loading all methods/fields, the array will be packed (the elements get copied into a smaller but big enough array).
I used java_bytearray_t to store the unparsed annotations and java_objectarray_t when I needed an array of byte-arrays.
See: src/vmcore/class.h
src/vmcore/annotation.h
Following declarations where added:
43 /* function prototypes ********************************************************/
44 bool annotation_load_class_attribute_runtimevisibleannotations(
45 classbuffer *cb);
46 bool annotation_load_class_attribute_runtimeinvisibleannotations(
47 classbuffer *cb);
48 bool annotation_load_method_attribute_runtimevisibleannotations(
49 classbuffer *cb, methodinfo *m);
50 bool annotation_load_method_attribute_runtimeinvisibleannotations(
51 classbuffer *cb, methodinfo *m);
52 bool annotation_load_field_attribute_runtimevisibleannotations(
53 classbuffer *cb, fieldinfo *f);
54 bool annotation_load_field_attribute_runtimeinvisibleannotations(
55 classbuffer *cb, fieldinfo *f);
56 bool annotation_load_method_attribute_annotationdefault(
57 classbuffer *cb, methodinfo *m);
58 bool annotation_load_method_attribute_runtimevisibleparameterannotations(
59 classbuffer *cb, methodinfo *m);
60 bool annotation_load_method_attribute_runtimeinvisibleparameterannotations(
61 classbuffer *cb, methodinfo *m);
These functions load the respective attributes from a classbuffer. They return true on success or false if an error has occured.
src/vmcore/class.c
classinfo *class_sun_reflect_ConstantPool;
classinfo *class_sun_reflect_annotation_AnnotationParser;
Declared classinfos for preloading for these two classes if ENABLE_ANNOTATIONS is defined.
class_sun_reflect_annotation_AnnotationParser is only defined if WITH_CLASSPATH_GNU is defined.
class_load_attributes
Added loading of annotations if ENABLE_ANNOTATIONS is defined.
class_get_annotations
This function returns the unparsed annotations as a Java byte array.
src/vmcore/class.h
Following fields where added to the classinfo struct:
147 #if defined(ENABLE_ANNOTATIONS)
148 /* All the annotation attributes are NULL (and not a zero length array) */
149 /* if there is nothing. */
150 java_object_t *annotations; /* annotations of this class */
151
152 java_object_t *method_annotations; /* array of annotations of the methods */
153 java_object_t *method_parameterannotations; /* array of parameter */
154 /* annotations of the methods */
155 java_object_t *method_annotationdefaults; /* array of annotation default */
156 /* values of the methods */
157 java_object_t *field_annotations; /* array of annotations of the fields */
158 #endif
Following declarations where added:
246 #if defined(ENABLE_ANNOTATIONS)
247 extern classinfo *class_sun_reflect_ConstantPool;
248 #if defined(WITH_CLASSPATH_GNU)
249 extern classinfo *class_sun_reflect_annotation_AnnotationParser;
250 #endif
251 #endif
385 java_handle_bytearray_t *class_get_annotations(classinfo *c);
src/vmcore/field.c
Added implementation of newly defined functions in field.h.
src/vmcore/field.h
field_get_annotations
Returns the unparsed annotations as a Java byte array.
src/vmcore/linker.h
Fixed formatting of a comment.
src/vmcore/loader.c
loader_init
Added loading of the class_sun_reflect_ConstantPool and the class_sun_reflect_annotation_AnnotationParser classinfo structs.
TODO: This is maybe a tiny bit of a memory waste. Not much but still, if no annotation support is used, this classinfos are needlessly loaded.
src/vmcore/method.c
Added implementation of newly defined functions in method.h.
src/vmcore/method.h
method_get_annotations
Returns the unparsed annotations as a Java byte array.
method_get_parameterannotations
Returns the unparsed parameter annotations as a Java byte array.
method_get_annotationdefault
Returns the unparsed annotation default value as a Java byte array.
method_get_parametercount
Returns the number of parameters of the referred method. The this pointer of non-static methods is not counted.
src/vmcore/utf8.c
Implemented loading of annotation attribute names into the according utf constants:
utf_RuntimeVisibleAnnotations
utf_RuntimeInvisibleAnnotations
utf_RuntimeVisibleParameterAnnotations
utf_RuntimeInvisibleParameterAnnotations
utf_AnnotationDefault
src/vmcore/utf8.h
Following declarations where added:
151 #if defined(ENABLE_ANNOTATIONS)
152 extern utf *utf_RuntimeVisibleAnnotations;
153 extern utf *utf_RuntimeInvisibleAnnotations;
154 extern utf *utf_RuntimeVisibleParameterAnnotations;
155 extern utf *utf_RuntimeInvisibleParameterAnnotations;
156 extern utf *utf_AnnotationDefault;
157 #endif
tests/regression/Makefile.am
Added $(srcdir)/MinimalClassReflection.java and $(srcdir)/TestAnnotations.java to SOURCE_FILES.
Added MinimalClassReflection.output and TestAnnotations.output to EXTRA_DIST.
Added MinimalClassReflection and TestAnnotations to OUTPUT_JAVA_TESTS.
tests/regression/MinimalClassReflection.java
Testcases for a few methods of java.lang.Class. This is already obsolete because I ported this test to the Mauve test framework and continued working on that test.
I wrote this testcase because I mentioned some odd/wrong behaviours of this methods while testing the annotations support.
Download Mauve testcase: MinimalClassReflection.zip (ZIP, 2 KB)
tests/regression/MinimalClassReflection.output
Correct output for the minimal class reflection test.
tests/regression/TestAnnotations.java
Testcases for the annotations support. This is already obsolete because I ported this test to the Mauve test framework and continued working on that test.
Download Mauve testcase: TestAnnotations.zip (ZIP, 9 KB)
tests/regression/TestAnnotations.output
Correct output for the annotations test.
TODOs
Check methods getStringAt0 and getUTF8At0 of sun.reflect.ConstantPool.
Do something with RuntimeInvisible*Annotations?