Skip to content

Commit 314b3b2

Browse files
committed
Add caching method to consolidate scans
The getAnnotatedMethods and getAnnotatedFields methods are highly repetitive, and the addition of caching exacerbates this code repetition. Also, maintaining methods for each object type, and requiring one call per annotation type is not extensible and results in multiple superclass/interface hierarchy traversals. Thus a ClassUtils.cacheAnnotatedObjects method is provided to ensure a list of Annotation:Object pairs is cached and future requests for these pairings will result in cache hits. This consolidates all annotation scraping logic in one method (although there is still repetition for Fields and Methods right now, it could be further simplified). It also allows a single traversal of the Class/Interface hierarchy to discover an arbitrary number of annotations.
1 parent b831e16 commit 314b3b2

File tree

2 files changed

+229
-69
lines changed

2 files changed

+229
-69
lines changed

src/main/java/org/scijava/Context.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@
3131

3232
package org.scijava;
3333

34+
import java.lang.annotation.Annotation;
35+
import java.lang.reflect.AccessibleObject;
3436
import java.lang.reflect.Field;
37+
import java.lang.reflect.Method;
3538
import java.net.URL;
3639
import java.net.URLClassLoader;
3740
import java.util.Arrays;
3841
import java.util.Collection;
3942
import java.util.Collections;
43+
import java.util.HashMap;
4044
import java.util.List;
45+
import java.util.Map;
4146

4247
import org.scijava.event.ContextDisposingEvent;
4348
import org.scijava.event.EventHandler;
@@ -357,6 +362,14 @@ public Service getService(final String className) {
357362
* which is not available from this context.
358363
*/
359364
public void inject(final Object o) {
365+
// Ensure parameter fields and event handler methods are cached for this
366+
// object.
367+
Map<Class<? extends Annotation>, Class<? extends AccessibleObject>> query =
368+
new HashMap<Class<? extends Annotation>, Class<? extends AccessibleObject>>();
369+
query.put(Parameter.class, Field.class);
370+
query.put(EventHandler.class, Method.class);
371+
ClassUtils.cacheAnnotatedObjects(o.getClass(), query);
372+
360373
// iterate over all @Parameter annotated fields
361374
final List<Field> fields = getParameterFields(o);
362375
for (final Field f : fields) {

src/main/java/org/scijava/util/ClassUtils.java

Lines changed: 216 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@
3333

3434
import java.io.File;
3535
import java.lang.annotation.Annotation;
36+
import java.lang.reflect.AccessibleObject;
3637
import java.lang.reflect.Array;
3738
import java.lang.reflect.Field;
3839
import java.lang.reflect.Method;
3940
import java.lang.reflect.Type;
4041
import java.net.MalformedURLException;
4142
import java.net.URL;
4243
import java.util.ArrayList;
44+
import java.util.Collections;
4345
import java.util.HashMap;
46+
import java.util.HashSet;
4447
import java.util.List;
4548
import java.util.Map;
49+
import java.util.Set;
4650

4751
/**
4852
* Useful methods for working with {@link Class} objects and primitive types.
@@ -338,46 +342,17 @@ public static <A extends Annotation> List<Method> getAnnotatedMethods(
338342
getAnnotatedMethods(final Class<?> c, final Class<A> annotationClass,
339343
final List<Method> methods)
340344
{
341-
// NB: The java.lang.Object class does not have any annotated methods.
342-
// And even if it did, it definitely does not have any methods annotated
343-
// with SciJava annotations such as org.scijava.event.EventHandler, which
344-
// are the main sorts of methods we are interested in.
345-
if (c == null || c == Object.class) return;
346-
347-
final Class<?> sc = c.getSuperclass();
348-
if (sc != null) {
349-
List<Method> superMethods = lookupMethods(sc, annotationClass);
350-
if (superMethods == null) {
351-
superMethods = new ArrayList<Method>();
352-
// check supertypes for annotated methods first
353-
getAnnotatedMethods(sc, annotationClass, superMethods);
354-
}
355-
methods.addAll(superMethods);
356-
}
357-
358-
// NB: In some cases, we may not need to recursively scan interfaces.
359-
// In particular, for the @EventHandler annotation, we only care about
360-
// concrete methods, not interface method declarations. So we could have
361-
// additional method signatures with a boolean toggle indicating whether
362-
// to include interfaces in the recursive scan. But initial benchmarks
363-
// suggest that the performance difference, even when creating a
364-
// full-blown Context with a large classpath, is negligible.
365-
for (final Class<?> iface : c.getInterfaces()) {
366-
List<Method> ifaceMethods = lookupMethods(iface, annotationClass);
367-
368-
if (ifaceMethods == null) {
369-
ifaceMethods = new ArrayList<Method>();
370-
getAnnotatedMethods(iface, annotationClass, ifaceMethods);
371-
}
372-
methods.addAll(ifaceMethods);
373-
}
374-
375-
for (final Method m : c.getDeclaredMethods()) {
376-
final A ann = m.getAnnotation(annotationClass);
377-
if (ann != null) methods.add(m);
345+
List<Method> cachedMethods = lookupMethods(c, annotationClass);
346+
347+
if (cachedMethods == null) {
348+
Map<Class<? extends Annotation>, Class<? extends AccessibleObject>> query =
349+
new HashMap<Class<? extends Annotation>, Class<? extends AccessibleObject>>();
350+
query.put(annotationClass, Method.class);
351+
cacheAnnotatedObjects(c, query);
352+
cachedMethods = lookupMethods(c, annotationClass);
378353
}
379354

380-
mapMethods(c, annotationClass, methods);
355+
methods.addAll(cachedMethods);
381356
}
382357

383358
/**
@@ -397,17 +372,18 @@ public static <A extends Annotation> List<Field> getAnnotatedFields(
397372
{
398373
List<Field> fields = lookupFields(c, annotationClass);
399374

400-
if (fields != null) return fields;
401-
fields = new ArrayList<Field>();
402-
getAnnotatedFields(c, annotationClass, fields);
375+
if (fields == null) {
376+
fields = new ArrayList<Field>();
377+
getAnnotatedFields(c, annotationClass, fields);
378+
}
403379

404380
return fields;
405381
}
406382

407383
/**
408-
* Gets the given class's {@link Field}s marked with the annotation of the
409-
* specified class.
410-
* <p>
384+
* Gets the given class's {@link Field}s marked with the annotation of the
385+
* specified class.
386+
* <p>
411387
* Unlike {@link Class#getFields()}, the result will include any non-public
412388
* fields, including fields defined in supertypes of the given class.
413389
* </p>
@@ -419,38 +395,169 @@ public static <A extends Annotation> List<Field> getAnnotatedFields(
419395
public static <A extends Annotation> void getAnnotatedFields(
420396
final Class<?> c, final Class<A> annotationClass, final List<Field> fields)
421397
{
422-
// NB: The java.lang.Object class does not have any annotated fields.
423-
// And even if it did, it definitely does not have any fields annotated
424-
// with SciJava annotations such as org.scijava.plugin.Parameter, which
425-
// are the main sorts of fields we are interested in.
426-
if (c == null || c == Object.class) return;
427-
428-
final Class<?> sc = c.getSuperclass();
429-
if (sc != null) {
430-
List<Field> superFields = lookupFields(sc, annotationClass);
431-
if (superFields == null) {
432-
superFields = new ArrayList<Field>();
433-
// check supertypes for annotated fields first
434-
getAnnotatedFields(sc, annotationClass, superFields);
435-
}
436-
fields.addAll(superFields);
398+
List<Field> cachedFields = lookupFields(c, annotationClass);
399+
400+
if (cachedFields == null) {
401+
Map<Class<? extends Annotation>, Class<? extends AccessibleObject>> query =
402+
new HashMap<Class<? extends Annotation>, Class<? extends AccessibleObject>>();
403+
query.put(annotationClass, Field.class);
404+
cacheAnnotatedObjects(c, query);
405+
cachedFields = lookupFields(c, annotationClass);
437406
}
438407

439-
for (final Class<?> iface : c.getInterfaces()) {
440-
List<Field> ifaceFields = lookupFields(iface, annotationClass);
441-
if (ifaceFields == null) {
442-
ifaceFields = new ArrayList<Field>();
443-
getAnnotatedFields(iface, annotationClass, ifaceFields);
408+
fields.addAll(cachedFields);
409+
}
410+
411+
/**
412+
* This method scans the provided class, its superclasses and interfaces for
413+
* all supported {@code {@link Annotation} : {@link AccessibleObject} pairs.
414+
* These are then cached to remove the need for future queries.
415+
* <p>
416+
* By combining multiple {@code Annotation : AccessibleObject} pairs in one
417+
* query, we can limit the number of times a class's superclass and interface
418+
* hierarchy are traversed.
419+
* </p>
420+
*
421+
* @param scannedClass Class to scan
422+
* @param query Pairs of {@link Annotation} and {@link AccessibleObject}s to
423+
* discover.
424+
*/
425+
public static void cacheAnnotatedObjects(
426+
final Class<?> scannedClass,
427+
final Map<Class<? extends Annotation>, Class<? extends AccessibleObject>> query)
428+
{
429+
// NB: The java.lang.Object class does not have any annotated methods.
430+
// And even if it did, it definitely does not have any methods annotated
431+
// with SciJava annotations such as org.scijava.event.EventHandler, which
432+
// are the main sorts of methods we are interested in.
433+
if (scannedClass == null || scannedClass == Object.class) return;
434+
435+
// Initialize step - determine which queries are solved
436+
final Set<Class<? extends Annotation>> keysToDrop =
437+
new HashSet<Class<? extends Annotation>>();
438+
for (final Class<? extends Annotation> annotationClass : query.keySet()) {
439+
final Class<? extends AccessibleObject> objectClass =
440+
query.get(annotationClass);
441+
442+
// Fields
443+
if (Field.class.isAssignableFrom(objectClass)) {
444+
if (lookupFields(scannedClass, annotationClass) != null) keysToDrop
445+
.add(annotationClass);
446+
}
447+
// Methods
448+
else if (Method.class.isAssignableFrom(objectClass)) {
449+
if (lookupMethods(scannedClass, annotationClass) != null) keysToDrop
450+
.add(annotationClass);
444451
}
445-
fields.addAll(ifaceFields);
446452
}
447453

448-
for (final Field f : c.getDeclaredFields()) {
449-
final A ann = f.getAnnotation(annotationClass);
450-
if (ann != null) fields.add(f);
454+
// Clean up resolved keys
455+
for (final Class<? extends Annotation> key : keysToDrop) {
456+
query.remove(key);
457+
}
458+
459+
// Stop now if we know all requested information is cached
460+
if (query.isEmpty()) return;
461+
462+
final List<Class<?>> inherited = new ArrayList<Class<?>>();
463+
464+
// cache all parents recursively
465+
final Class<?> superClass = scannedClass.getSuperclass();
466+
if (superClass != null) {
467+
// Recursive step
468+
cacheAnnotatedObjects(
469+
superClass,
470+
new HashMap<Class<? extends Annotation>, Class<? extends AccessibleObject>>(
471+
query));
472+
inherited.add(superClass);
473+
}
474+
475+
// cache all interfaces recursively
476+
for (final Class<?> ifaceClass : scannedClass.getInterfaces()) {
477+
// Recursive step
478+
cacheAnnotatedObjects(
479+
ifaceClass,
480+
new HashMap<Class<? extends Annotation>, Class<? extends AccessibleObject>>(
481+
query));
482+
inherited.add(ifaceClass);
451483
}
452484

453-
mapFields(c, annotationClass, fields);
485+
// Populate supported objects for scanned class
486+
for (final Class<? extends Annotation> annotationClass : query.keySet()) {
487+
final Class<? extends AccessibleObject> objectClass =
488+
query.get(annotationClass);
489+
490+
// Methods
491+
if (Method.class.isAssignableFrom(objectClass)) {
492+
for (final Class<?> inheritedClass : inherited) {
493+
final List<Method> annotatedMethods =
494+
lookupMethods(inheritedClass, annotationClass);
495+
496+
if (annotatedMethods != null && !annotatedMethods.isEmpty()) {
497+
final List<Method> scannedMethods =
498+
makeMethodsArray(scannedClass, annotationClass);
499+
500+
scannedMethods.addAll(annotatedMethods);
501+
}
502+
}
503+
504+
// Add declared methods
505+
final Method[] declaredMethods = scannedClass.getDeclaredMethods();
506+
if (declaredMethods != null && declaredMethods.length > 0) {
507+
List<Method> scannedMethods = null;
508+
509+
for (final Method m : declaredMethods) {
510+
if (m.getAnnotation(annotationClass) != null) {
511+
if (scannedMethods == null) {
512+
scannedMethods = makeMethodsArray(scannedClass, annotationClass);
513+
}
514+
scannedMethods.add(m);
515+
}
516+
}
517+
}
518+
519+
// If there were no methods for this query, map an empty
520+
// list to mark the query complete
521+
if (lookupMethods(scannedClass, annotationClass) == null) {
522+
mapMethods(scannedClass, annotationClass, Collections.<Method>emptyList());
523+
}
524+
}
525+
// Fields
526+
else if (Field.class.isAssignableFrom(objectClass)) {
527+
for (final Class<?> inheritedClass : inherited) {
528+
final List<Field> annotatedFields =
529+
lookupFields(inheritedClass, annotationClass);
530+
531+
if (annotatedFields != null && !annotatedFields.isEmpty()) {
532+
final List<Field> scannedFields =
533+
makeFieldsArray(scannedClass, annotationClass);
534+
535+
scannedFields.addAll(annotatedFields);
536+
}
537+
}
538+
539+
// Add declared fields
540+
final Field[] declaredFields = scannedClass.getDeclaredFields();
541+
if (declaredFields != null && declaredFields.length > 0) {
542+
List<Field> scannedFields = null;
543+
544+
for (final Field f : declaredFields) {
545+
if (f.getAnnotation(annotationClass) != null) {
546+
if (scannedFields == null) {
547+
scannedFields = makeFieldsArray(scannedClass, annotationClass);
548+
}
549+
scannedFields.add(f);
550+
}
551+
}
552+
}
553+
554+
// If there were no fields for this query, map an empty
555+
// list to mark the query complete
556+
if (lookupFields(scannedClass, annotationClass) == null) {
557+
mapFields(scannedClass, annotationClass, Collections.<Field>emptyList());
558+
}
559+
}
560+
}
454561
}
455562

456563
/**
@@ -628,6 +735,46 @@ private static <A extends Annotation> void mapMethods(final Class<?> c,
628735
map.put(annotationClass, annotatedMethods);
629736
}
630737

738+
/**
739+
* As {@link #lookupFields(Class, Class)} but ensures an array is
740+
* created and mapped, if it doesn't exist already.
741+
*
742+
* @param c Base class
743+
* @param annotationClass Annotation type
744+
* @return Cached list of Fields in the base class with the specified
745+
* annotation.
746+
*/
747+
private static <A extends Annotation> List<Field> makeFieldsArray(
748+
final Class<?> c, final Class<A> annotationClass)
749+
{
750+
List<Field> fields = lookupFields(c, annotationClass);
751+
if (fields == null) {
752+
fields = new ArrayList<Field>();
753+
mapFields(c, annotationClass, fields);
754+
}
755+
return fields;
756+
}
757+
758+
/**
759+
* As {@link #lookupMethods(Class, Class)} but ensures an array is
760+
* created and mapped, if it doesn't already exist.
761+
*
762+
* @param c Base class
763+
* @param annotationClass Annotation type
764+
* @return Cached list of Fields in the base class with the specified
765+
* annotation.
766+
*/
767+
private static <A extends Annotation> List<Method> makeMethodsArray(
768+
final Class<?> c, final Class<A> annotationClass)
769+
{
770+
List<Method> methods = lookupMethods(c, annotationClass);
771+
if (methods == null) {
772+
methods = new ArrayList<Method>();
773+
mapMethods(c, annotationClass, methods);
774+
}
775+
return methods;
776+
}
777+
631778
/**
632779
* @param c Base class
633780
* @param annotationClass Annotation type

0 commit comments

Comments
 (0)