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 extends Service> 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 extends Service> baseClass,
+ List> serviceList)
+ {
+ for (Class extends Service> 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 extends Service> 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);
}
-
}