From 581f23a295152f5dd3c6a1e31d33087d38c9ffaf Mon Sep 17 00:00:00 2001 From: Mark Hiner Date: Mon, 18 Mar 2013 15:33:43 -0500 Subject: [PATCH] Added lazy service loading mechanism The Plugin annotation now has a "lazy" attribute. If true, the ServiceHelper will not load these services during its first loadServices pass. After loading the non-lazy services, standard calls to the Context's getService(Class) method will check the lazy service list for a matching service, if an implementation was not already loaded. NB: these changes require the Context to maintain a reference to a ServiceHelper. ServiceHelper has a canLoadLazy() flag to determine if it is capable of loading lazy services or not (that is, if loadServices() has already been called or not). --- src/main/java/org/scijava/Context.java | 16 +++- src/main/java/org/scijava/plugin/Plugin.java | 10 ++ .../org/scijava/service/ServiceHelper.java | 91 +++++++++++++++---- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/scijava/Context.java b/src/main/java/org/scijava/Context.java index 551967736..0d8f81027 100644 --- a/src/main/java/org/scijava/Context.java +++ b/src/main/java/org/scijava/Context.java @@ -79,6 +79,9 @@ private static String getStaticVersion() { /** Index of the application context's services. */ private final ServiceIndex serviceIndex; + + /** Helper class for loading services. */ + private final ServiceHelper serviceHelper; /** Master index of all plugins known to the application context. */ private final PluginIndex pluginIndex; @@ -166,8 +169,7 @@ public Context(final Collection> serviceClasses) { pom = POM.getPOM(Context.class, "org.scijava", "scijava-common"); manifest = Manifest.getManifest(Context.class); - final ServiceHelper serviceHelper = - new ServiceHelper(this, serviceClasses); + serviceHelper = new ServiceHelper(this, serviceClasses); serviceHelper.loadServices(); } @@ -244,7 +246,15 @@ public PluginIndex getPluginIndex() { /** Gets the service of the given class. */ public S getService(final Class c) { - return serviceIndex.getService(c); + S service = serviceIndex.getService(c); + + if (service == null && serviceHelper != null && + serviceHelper.canLoadLazy()) + { + service = serviceHelper.loadService(c); + } + + return service; } /** Gets the service of the given class name (useful for scripts). */ diff --git a/src/main/java/org/scijava/plugin/Plugin.java b/src/main/java/org/scijava/plugin/Plugin.java index 4456e761b..842bf56b2 100644 --- a/src/main/java/org/scijava/plugin/Plugin.java +++ b/src/main/java/org/scijava/plugin/Plugin.java @@ -163,6 +163,16 @@ *

*/ boolean headless() default false; + + /** + * When true, if this plugin is an {@link org.scijava.service.Service}, + * the context will not try to load this service unless explicitly requested. + *

+ * NB: Annotating a service field the {@link org.scijava.plugin.Parameter} + * annotation will cause that service to be loaded. + *

+ */ + boolean lazy() default false; /** Defines a function that is called to initialize the plugin in some way. */ String initializer() default ""; diff --git a/src/main/java/org/scijava/service/ServiceHelper.java b/src/main/java/org/scijava/service/ServiceHelper.java index 6f5519328..072cc3eb1 100644 --- a/src/main/java/org/scijava/service/ServiceHelper.java +++ b/src/main/java/org/scijava/service/ServiceHelper.java @@ -70,6 +70,12 @@ public class ServiceHelper extends AbstractContextual { /** Classes to instantiate as services. */ private final List> serviceClasses; + /** Class list of lazy services. */ + private final List> lazyPoolList; + + /** Whether this ServiceHelper will load lazy services. */ + private boolean loadLazy; + /** * Creates a new service helper for discovering and instantiating services. * @@ -87,12 +93,13 @@ public ServiceHelper(final Context context) { * @param serviceClasses The service classes to instantiate. */ public ServiceHelper(final Context context, - final Collection> serviceClasses) + final Collection> serviceClasses) { setContext(context); classPoolMap = new HashMap, Double>(); classPoolList = new ArrayList>(); - findServiceClasses(classPoolMap, classPoolList); + lazyPoolList = new ArrayList>(); + findServiceClasses(classPoolMap, classPoolList, lazyPoolList); this.serviceClasses = new ArrayList>(); if (serviceClasses == null) { // load all discovered services @@ -102,6 +109,8 @@ public ServiceHelper(final Context context, // load only the services that were explicitly specified this.serviceClasses.addAll(serviceClasses); } + + loadLazy = false; } // -- ServiceHelper methods -- @@ -115,8 +124,12 @@ public void loadServices() { loadService(serviceClass); } final EventService eventService = - getContext().getService(EventService.class); + getContext().getService(EventService.class); if (eventService != null) eventService.publish(new ServicesLoadedEvent()); + + // All non-lazy services should be loaded at this point, + // so lazy services can now be loaded + loadLazy = true; } /** @@ -129,18 +142,18 @@ public void loadServices() { */ public S loadService(final Class c) { // if a compatible service already exists, return it - final S service = getContext().getServiceIndex().getService(c); + S service = getContext().getServiceIndex().getService(c); if (service != null) return service; // scan the class pool for a suitable match - for (final Class serviceClass : classPoolList) { - if (c.isAssignableFrom(serviceClass)) { - // found a match; now instantiate it - @SuppressWarnings("unchecked") - final S result = (S) createExactService(serviceClass); - return result; - } - } + service = this.searchListForService(c, classPoolList); + + // scan the lazy class pool for a suitable match if necessary + if (service == null && canLoadLazy()) + service = this.searchListForService(c, lazyPoolList); + + // found a match, return it. + if (service != null) return service; return createExactService(c); } @@ -164,12 +177,23 @@ public S createExactService(final Class c) { } return null; } + + /** + * Returns whether or not lazy services will be loaded by this ServiceHelper. + * {@link #loadServices()} should be run once before any lazy services + * can be loaded. + * + * @return true if this ServiceHelper will load lazy services + */ + public boolean canLoadLazy() { + return loadLazy ; + } // -- Helper methods -- /** Instantiates a service using the given constructor. */ private S createService(final Class c) - throws InstantiationException, IllegalAccessException + throws InstantiationException, IllegalAccessException { final S service = c.newInstance(); service.setContext(getContext()); @@ -180,7 +204,7 @@ private S createService(final Class c) // populate service parameters final List fields = - ClassUtils.getAnnotatedFields(c, Parameter.class); + ClassUtils.getAnnotatedFields(c, Parameter.class); for (final Field f : fields) { f.setAccessible(true); // expose private fields @@ -202,21 +226,51 @@ private S createService(final Class c) return service; } + /** + * Iterates over the provided list, looking for classes + * that can be cast to the specified baseClass, and attempting + * to instantiate these classes until successful or the list is exhausted. + */ + @SuppressWarnings("unchecked") + private S searchListForService( + Class baseClass, + List> serviceList) + { + for (Class testClass : serviceList) { + if (baseClass.isAssignableFrom(testClass)) { + // found a match; now instantiate it + return (S) createExactService(testClass); + } + } + + return null; + } + /** Asks the plugin index for all available service implementations. */ private void findServiceClasses( - final Map, Double> serviceMap, - final List> serviceList) + final Map, Double> serviceMap, + final List> serviceList, + List> lazyServiceList) { // ask the plugin index for the (sorted) list of available services final List> services = - getContext().getPluginIndex().getPlugins(Service.class); + getContext().getPluginIndex().getPlugins(Service.class); for (final PluginInfo info : services) { try { final Class c = info.loadClass(); final double priority = info.getPriority(); serviceMap.put(c, priority); - serviceList.add(c); + + // If the service is annotated as lazy, add it to the lazy list + // for later loading. Otherwise, it can be added to the list of + // available services immediately. + if (info.getAnnotation().lazy()) { + lazyServiceList.add(c); + } + else { + serviceList.add(c); + } } catch (final Throwable e) { error("Invalid service: " + info, e); @@ -241,5 +295,4 @@ private void debug(final String msg) { final LogService log = getContext().getService(LogService.class); if (log != null) log.debug(msg); } - }