3333
3434import java .io .File ;
3535import java .lang .annotation .Annotation ;
36+ import java .lang .reflect .AccessibleObject ;
3637import java .lang .reflect .Array ;
3738import java .lang .reflect .Field ;
3839import java .lang .reflect .Method ;
3940import java .lang .reflect .Type ;
4041import java .net .MalformedURLException ;
4142import java .net .URL ;
4243import java .util .ArrayList ;
44+ import java .util .Collections ;
4345import java .util .HashMap ;
46+ import java .util .HashSet ;
4447import java .util .List ;
4548import 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