@@ -426,64 +426,87 @@ public static <A extends Annotation> void getAnnotatedFields(
426426 public static void cacheAnnotatedObjects (final Class <?> scannedClass ,
427427 final Query query )
428428 {
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- // Fields
440- if (fieldCache .getList (scannedClass , annotationClass ) != null ) {
441- keysToDrop .add (annotationClass );
442- }
443- else if (methodCache .getList (scannedClass , annotationClass ) != null ) {
444- keysToDrop .add (annotationClass );
429+ // Only allow one thread at a time to populate cached objects for a given
430+ // class. This lock is technically overkill - the minimal lock would be
431+ // on the provided Query contents + class. Devising a way to obtain this
432+ // lock could be useful - as if Query A and Query B were executed on the
433+ // same class by different threads, there are three scenarios:
434+ // 1) intersection of A + B is empty - then they can run on separate threads
435+ // 2) A == B - whichever was received second must wait for the first to
436+ // finish.
437+ // 3) A != B and intersection of A + B is not empty - the intersection subset
438+ // can be safely performed on a separate thread, but the later query must
439+ // still wait for the earlier query to complete.
440+ //
441+ // NB: an alternative would be to update the getAnnotatedxxx methods to
442+ // return Sets instead of Lists. Then threads can pretty much go nuts
443+ // as long as you double lock the Set creation in a synchronized block.
444+ //
445+ // NB: another possibility would be to keep this synchronized entry point
446+ // but divide the work for each Query into asynchronous blocks. However, it
447+ // has not been investigated how much of a performance boost that would
448+ // provide as it would then cause multiple traversals of the class hierarchy
449+ // - which is exactly what the Query notation was created to avoid.
450+ synchronized (scannedClass ) {
451+ // NB: The java.lang.Object class does not have any annotated methods.
452+ // And even if it did, it definitely does not have any methods annotated
453+ // with SciJava annotations such as org.scijava.event.EventHandler, which
454+ // are the main sorts of methods we are interested in.
455+ if (scannedClass == null || scannedClass == Object .class ) return ;
456+
457+ // Initialize step - determine which queries are solved
458+ final Set <Class <? extends Annotation >> keysToDrop =
459+ new HashSet <Class <? extends Annotation >>();
460+ for (final Class <? extends Annotation > annotationClass : query .keySet ()) {
461+ // Fields
462+ if (fieldCache .getList (scannedClass , annotationClass ) != null ) {
463+ keysToDrop .add (annotationClass );
464+ }
465+ else if (methodCache .getList (scannedClass , annotationClass ) != null ) {
466+ keysToDrop .add (annotationClass );
467+ }
445468 }
446- }
447469
448- // Clean up resolved keys
449- for (final Class <? extends Annotation > key : keysToDrop ) {
450- query .remove (key );
451- }
470+ // Clean up resolved keys
471+ for (final Class <? extends Annotation > key : keysToDrop ) {
472+ query .remove (key );
473+ }
452474
453- // Stop now if we know all requested information is cached
454- if (query .isEmpty ()) return ;
475+ // Stop now if we know all requested information is cached
476+ if (query .isEmpty ()) return ;
455477
456- final List <Class <?>> inherited = new ArrayList <Class <?>>();
478+ final List <Class <?>> inherited = new ArrayList <Class <?>>();
457479
458- // cache all parents recursively
459- final Class <?> superClass = scannedClass .getSuperclass ();
460- if (superClass != null ) {
461- // Recursive step
462- cacheAnnotatedObjects (superClass , new Query (query ));
463- inherited .add (superClass );
464- }
480+ // cache all parents recursively
481+ final Class <?> superClass = scannedClass .getSuperclass ();
482+ if (superClass != null ) {
483+ // Recursive step
484+ cacheAnnotatedObjects (superClass , new Query (query ));
485+ inherited .add (superClass );
486+ }
465487
466- // cache all interfaces recursively
467- for (final Class <?> ifaceClass : scannedClass .getInterfaces ()) {
468- // Recursive step
469- cacheAnnotatedObjects (ifaceClass , new Query (query ));
470- inherited .add (ifaceClass );
471- }
488+ // cache all interfaces recursively
489+ for (final Class <?> ifaceClass : scannedClass .getInterfaces ()) {
490+ // Recursive step
491+ cacheAnnotatedObjects (ifaceClass , new Query (query ));
492+ inherited .add (ifaceClass );
493+ }
472494
473- // Populate supported objects for scanned class
474- for (final Class <? extends Annotation > annotationClass : query .keySet ()) {
475- final Class <? extends AnnotatedElement > objectClass =
476- query .get (annotationClass );
495+ // Populate supported objects for scanned class
496+ for (final Class <? extends Annotation > annotationClass : query .keySet ()) {
497+ final Class <? extends AnnotatedElement > objectClass =
498+ query .get (annotationClass );
477499
478- // Methods
479- if (Method .class .isAssignableFrom (objectClass )) {
480- populateCache (scannedClass , inherited , annotationClass , methodCache ,
481- scannedClass .getDeclaredMethods ());
482- }
483- // Fields
484- else if (Field .class .isAssignableFrom (objectClass )) {
485- populateCache (scannedClass , inherited , annotationClass , fieldCache ,
486- scannedClass .getDeclaredFields ());
500+ // Methods
501+ if (Method .class .isAssignableFrom (objectClass )) {
502+ populateCache (scannedClass , inherited , annotationClass , methodCache ,
503+ scannedClass .getDeclaredMethods ());
504+ }
505+ // Fields
506+ else if (Field .class .isAssignableFrom (objectClass )) {
507+ populateCache (scannedClass , inherited , annotationClass , fieldCache ,
508+ scannedClass .getDeclaredFields ());
509+ }
487510 }
488511 }
489512 }
0 commit comments