package nl.elastique.poetry.reflection; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.HashMap; import nl.elastique.poetry.annotations.Nullable; /** * AnnotationRetriever caches annotations to improve performance. * * In a test with hundreds of objects, Field.getAnnotation() took over 50% CPU time. * When looking at the Android source code it shows that this is a fairly heavy method. * Considering that Poetry uses only a certain amount of model classes and fields, it * makes sense to cache this in memory. * * Reference: http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/4.4_r1-robolectric-1/libcore/reflect/AnnotationAccess.java#AnnotationAccess.getDeclaredAnnotation%28java.lang.reflect.AnnotatedElement%2Cjava.lang.Class%29 */ public class AnnotationRetriever { private final HashMap<Field, HashMap<Class<? extends Annotation>, Annotation> > mFieldAnnotationCache = new HashMap<>(); private final HashMap<Class<?>, HashMap<Class<? extends Annotation>, Annotation> > mClassAnnotationCache = new HashMap<>(); /** * Retrieve a {@link Field} for a Field. * * @param field the Field to search for the Annotation * @param annotationClass the annotation class to search for * @param <AnnotationType> the annotation type to search for * @return the found Annotation or null */ public @Nullable <AnnotationType extends Annotation> AnnotationType getAnnotation(Field field, Class<AnnotationType> annotationClass) { // Try to retrieve it from cache Annotation annotation = getCachedAnnotation(field, annotationClass); // If not cached, try reflection if (annotation == null) { annotation = field.getAnnotation(annotationClass); // Null values are also cached because it will make the next failure quicker setCachedAnnotation(field, annotationClass, annotation); } return (AnnotationType)annotation; } private @Nullable Annotation getCachedAnnotation(Field field, Class<? extends Annotation> annotationClass) { HashMap<Class<? extends Annotation>, Annotation> annotation_map = mFieldAnnotationCache.get(field); return (annotation_map != null) ? annotation_map.get(annotationClass) : null; } private void setCachedAnnotation(Field field, Class<? extends Annotation> annotationClass, Annotation annotation) { HashMap<Class<? extends Annotation>, Annotation> annotation_map = mFieldAnnotationCache.get(field); if (annotation_map == null) { annotation_map = new HashMap<>(); mFieldAnnotationCache.put(field, annotation_map); } annotation_map.put(annotationClass, annotation); } /** * Retrieve a {@link Field} for a class. * * @param parentClass the class to retrieve the annotation from * @param annotationClass the annotation type to search for * @param <AnnotationType> the annotation type to search for * @return the Annotation or null */ public @Nullable <AnnotationType extends Annotation> AnnotationType getAnnotation(Class<?> parentClass, Class<AnnotationType> annotationClass) { // Try to retrieve it from cache Annotation annotation = getCachedAnnotation(parentClass, annotationClass); // If not cached, try reflection if (annotation == null) { annotation = parentClass.getAnnotation(annotationClass); // Null values are also cached because it will make the next failure quicker setCachedAnnotation(parentClass, annotationClass, annotation); } return (AnnotationType)annotation; } private @Nullable Annotation getCachedAnnotation(Class<?> parentClass, Class<? extends Annotation> annotationClass) { HashMap<Class<? extends Annotation>, Annotation> annotation_map = mClassAnnotationCache.get(parentClass); return (annotation_map != null) ? annotation_map.get(annotationClass) : null; } private void setCachedAnnotation(Class<?> parentClass, Class<? extends Annotation> annotationClass, Annotation annotation) { HashMap<Class<? extends Annotation>, Annotation> annotation_map = mClassAnnotationCache.get(parentClass); if (annotation_map == null) { annotation_map = new HashMap<>(); mClassAnnotationCache.put(parentClass, annotation_map); } annotation_map.put(annotationClass, annotation); } }