Getting Started with Inherited Annotations for Java

Java annotations have a restriction, which is inability to inherit annotations to subclasses. This peculiarity brings in some inconvenience and could be critical if some wrappers, generated with the reflection mechanism, are used over some Java classes. In this case there are no possibility to manage annotations in derived classes manually and no ability to use this convenient tool of program behavior declarative description. To solve this problem, Fusionsoft Company developed the library of inherited annotations which is considered below. The library is open-source and free, with no restriction for commercial application.

Problem to Solve

Let's consider the following example:

			@Target(ElementType.TYPE)
			@Retention(RetentionPolicy.RUNTIME)
			public @interface A {}
				
			@Target(ElementType.METHOD)
			@Retention(RetentionPolicy.RUNTIME)
			public @interface B {}
				
			@A
			public interface BaseInterface {
				@B
				public void method1();
			}

			public class BaseClass {
				@B
				public void method2(){}
			}

			public class Derived extends BaseClass implements BaseInterface{
				public void method1(){}
				public void method2(){}
			}
			

This example contains two kinds of annotations: the annotation @A for types, and the annotation @B for methods. The annotations were used for describing one superclass and one superinterface. There was created a derived class inherited from the superclass and superinterface.

According to Java annotation practice, neither derived class nor its methods inherit annotations defined in the superclass and superinterface. Therefore the following code returns null in both cases:

			Derived.class.getMethod(
					"method1", new Class[0]).getAnnotation(B.class)

			Derived.class.getMethod(
					"method2", new Class[0]).getAnnotation(B.class)
			

Accessing Superclass Annotations

The main idea that allows getting annotations for superclasses lies in extracting them from annotated base classes using reflection mechanism. One could cache annotations in a unified storage and get access to them using names of classes and signatures of methods as keys.

The product provides centralized cache for annotated classes as follows:

			public class AnnotationManager {
				private static Map<Class <?>, AnnotatedClass> classToAnnotatedMap =
					new HashMap<Class<?>, AnnotatedClass>();
				
				/**
				 * @param theClass to wrap.
				 * @return the annotated class wrapping the specified one.
				 */
				public static AnnotatedClass getAnnotatedClass(Class<?> theClass){
					AnnotatedClass annotatedClass = classToAnnotatedMap.get(theClass);
					if (annotatedClass == null){
						annotatedClass = new AnnotatedClassImpl(theClass);
						classToAnnotatedMap.put(theClass, annotatedClass);
					}
					return annotatedClass;
				}
			}
			

By calling the static method getAnnotatedClass, one can get the object of the class AnnotatedClass containing annotations inherited from ancestor classes and interfaces. AnnotattedClass uses the reflection methods: Class.getInterfaces, Class.getDeclaredAnnotations. It gets annotations recursively from all the ancestors, caches them and associates annotations with the annotated interfaces and classes. The procedure of filling up the cache HashMap looks like the following:

			private Map<Class<?>, Annotation> getAllAnnotationMapCalculated(){
					HashMap<Class<?>, Annotation> result = new 
			                    HashMap<Class<?>, Annotation>();
					
					final Class<?> superClass = getTheClass().getSuperclass();
					// Get the superclass's annotations
					if (superClass != null)
						fillAnnotationsForOneClass(result, superClass);

					// Get the superinterfaces' annotations
					for (Class<?> c : getTheClass().getInterfaces())
						fillAnnotationsForOneClass(result, c);
					
					// Get its own annotations. They have preference to inherited
			            //annotations.
					for (Annotation annotation : getTheClass().
			                                       getDeclaredAnnotations())
						result.put(annotation.getClass().
			                      getInterfaces()[0], annotation);
					return result;
			}
			

The lazy-initialization pattern is used here to cache annotations: annotations are cached only when have requested for the first time.

Using the Library of Inherited Java Annotations

So, how does the access to Java annotations using the library of inherited annotations looks like? The access to inherited annotations is identical to the standard one, but the special classes from library are used instead Java API classes.

For our example it looks like the following:

			AnnotatedClass annotatedClass = 
						AnnotationManager.getAnnotatedClass(Derived.class);
			annotatedClass.getAnnotation(A.class);

			AnnotatedMethod annotatedMethod = annotatedClass
					.getAnnotatedMethod("method1", new Class[0]);
			annotatedMethod.getAnnotation(B.class);

			annotatedMethod = annotatedClass
					.getAnnotatedMethod("method2", new Class[0]);
			annotatedMethod.getAnnotation(B.class);
			

As long as method1 and method2 don't have parameters we have used empty array new Class[0] as parameter of function getAnnotatedMethod.