All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.Context Maven / Gradle / Ivy

/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2014 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
 * Institute of Molecular Cell Biology and Genetics.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.scijava.event.ContextDisposingEvent;
import org.scijava.event.EventHandler;
import org.scijava.event.EventService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.PluginIndex;
import org.scijava.service.Service;
import org.scijava.service.ServiceHelper;
import org.scijava.service.ServiceIndex;
import org.scijava.util.ClassUtils;

/**
 * Top-level SciJava application context, which initializes and maintains a list
 * of services.
 * 
 * @author Curtis Rueden
 * @see Service
 */
public class Context implements Disposable {

	// -- Constants --

	/**
	 * System property indicating whether the context should fail fast when
	 * is attempts to instantiate a required service which is invalid or missing.
	 * If this property is set to "false" then the context creation will attempt
	 * to continue even when a required service cannot be instantiated. Otherwise,
	 * the constructor will throw an {@link IllegalArgumentException} in that situation.
	 */
	public static final String STRICT_PROPERTY = "scijava.context.strict";

	// -- Fields --

	/** Index of the application context's services. */
	private final ServiceIndex serviceIndex;

	/** Master index of all plugins known to the application context. */
	private final PluginIndex pluginIndex;

	/**
	 * Creates a new SciJava application context with all available services.
	 * 
	 * @see #Context(Collection, PluginIndex, boolean)
	 */
	public Context() {
		this(false);
	}

	/**
	 * Creates a new SciJava application context.
	 * 
	 * @param empty If true, the context will be empty; otherwise, it will be
	 *          initialized with all available services.
	 * @see #Context(Collection, PluginIndex, boolean)
	 */
	@SuppressWarnings("unchecked")
	public Context(final boolean empty) {
		this(empty ? Collections.> emptyList() : Arrays
			.> asList(Service.class));
	}

	/**
	 * Creates a new SciJava application context with the specified services (and
	 * any required service dependencies).
	 * 

* Developer's note: This constructor's argument is raw (i.e., * {@code Class...} instead of {@code Class...}) because * otherwise, downstream invocations (e.g., * {@code new Context(DisplayService.class)}) yield the potentially confusing * warning: *

*
Type safety: A generic array of Class is * created for a varargs parameter
*

* To avoid this, we have opted to use raw types and suppress the relevant * warnings here instead. *

* * @param serviceClasses A list of types that implement the {@link Service} * interface (e.g., {@code DisplayService.class}). Compatible * services will be loaded in the order given, * regardless of their relative priorities. * @see #Context(Collection, PluginIndex, boolean) * @throws ClassCastException If any of the given arguments do not implement * the {@link Service} interface. */ public Context(@SuppressWarnings("rawtypes") final Class... serviceClasses) { this(serviceClassList(serviceClasses)); } /** * Creates a new SciJava application context with the specified services (and * any required service dependencies). * * @param serviceClasses A collection of types that implement the * {@link Service} interface (e.g., {@code DisplayService.class}). * Compatible services will be loaded according to the order of the * collection, regardless of their relative priorities. * @see #Context(Collection, PluginIndex, boolean) */ public Context(final Collection> serviceClasses) { this(serviceClasses, null); } /** * Creates a new SciJava application context with the specified services (and * any required service dependencies). * * @param serviceClasses A collection of types that implement the * {@link Service} interface (e.g., {@code DisplayService.class}). * Compatible services will be loaded according to the order of the * collection, regardless of their relative priorities. * @param strict Whether context creation will fail fast when there is * an error instantiating a required service. * @see #Context(Collection, PluginIndex, boolean) */ public Context(final Collection> serviceClasses, final boolean strict) { this(serviceClasses, null, strict); } /** * Creates a new SciJava application context with all available services from * the specified PluginIndex. This allows a base set of available plugins to * be defined, and is useful when plugins that would not be returned by the * {@link PluginIndex}'s {@link org.scijava.plugin.PluginFinder} are desired. * * @param pluginIndex The plugin index to use when discovering and indexing * plugins. If you wish to completely control how services are * discovered (i.e., use your own * {@link org.scijava.plugin.PluginFinder} implementation), then you * can pass a custom {@link PluginIndex} here. Passing null will * result in a default plugin index being constructed and used. * @see #Context(Collection, PluginIndex, boolean) */ @SuppressWarnings("unchecked") public Context(final PluginIndex pluginIndex) { this(Arrays.> asList(Service.class), pluginIndex); } /** * Creates a new SciJava application context with the specified services (and * any required service dependencies). Service dependency candidates are * selected from those discovered by the given {@link PluginIndex}'s * associated {@link org.scijava.plugin.PluginFinder}. * * @param serviceClasses A collection of types that implement the * {@link Service} interface (e.g., {@code DisplayService.class}). * Compatible services will be loaded according to the order of the * collection, regardless of their relative priorities. * @param pluginIndex The plugin index to use when discovering and indexing * plugins. If you wish to completely control how services are * discovered (i.e., use your own * {@link org.scijava.plugin.PluginFinder} implementation), then you * can pass a custom {@link PluginIndex} here. Passing null will * result in a default plugin index being constructed and used. * @see #Context(Collection, PluginIndex, boolean) */ public Context(final Collection> serviceClasses, final PluginIndex pluginIndex) { this(serviceClasses, pluginIndex, strict()); } /** * Creates a new SciJava application context with the specified services (and * any required service dependencies). Service dependency candidates are * selected from those discovered by the given {@link PluginIndex}'s * associated {@link org.scijava.plugin.PluginFinder}. *

* NB: Context creation is an important step of a SciJava application's * lifecycle. Particularly in environments where more than one implementation * exists for various services, careful consideration should be exercised * regarding what classes and plugins are provided to the Context, and what * needs to occur during the initialization of these services (especially * those of lower priority). See {@link ServiceHelper#loadServices()} for more * information. *

* * @param serviceClasses A collection of types that implement the * {@link Service} interface (e.g., {@code DisplayService.class}). * Compatible services will be loaded according to the order of the * collection, regardless of their relative priorities. * @param pluginIndex The plugin index to use when discovering and indexing * plugins. If you wish to completely control how services are * discovered (i.e., use your own * {@link org.scijava.plugin.PluginFinder} implementation), then you * can pass a custom {@link PluginIndex} here. Passing null will * result in a default plugin index being constructed and used. * @param strict Whether context creation will fail fast when there is * an error instantiating a required service. */ public Context(final Collection> serviceClasses, final PluginIndex pluginIndex, final boolean strict) { serviceIndex = new ServiceIndex(); this.pluginIndex = pluginIndex == null ? new PluginIndex() : pluginIndex; this.pluginIndex.discover(); final ServiceHelper serviceHelper = new ServiceHelper(this, serviceClasses, strict); serviceHelper.loadServices(); } // -- Context methods -- public ServiceIndex getServiceIndex() { return serviceIndex; } public PluginIndex getPluginIndex() { return pluginIndex; } /** * Gets the service of the given class. * * @throws NoSuchServiceException if the context does not have the requested * service. */ public S service(final Class c) { final S service = getService(c); if (service == null) { throw new NoSuchServiceException("Service " + c.getName() + " not found."); } return service; } /** * Gets the service of the given class name (useful for scripts). * * @throws IllegalArgumentException if the class does not exist, or is not a * service class. * @throws NoSuchServiceException if the context does not have the requested * service. */ public Service service(final String className) { final Class c = ClassUtils.loadClass(className); if (c == null) { throw new IllegalArgumentException("No such class: " + className); } if (!Service.class.isAssignableFrom(c)) { throw new IllegalArgumentException("Not a service class: " + c.getName()); } @SuppressWarnings("unchecked") final Class serviceClass = (Class) c; return service(serviceClass); } /** * Gets the service of the given class, or null if there is no matching * service. */ public S getService(final Class c) { return serviceIndex.getService(c); } /** Gets the service of the given class name (useful for scripts). */ public Service getService(final String className) { final Class c = ClassUtils.loadClass(className); if (c == null) return null; if (!Service.class.isAssignableFrom(c)) return null; // not a service class @SuppressWarnings("unchecked") final Class serviceClass = (Class) c; return getService(serviceClass); } /** * Injects the application context into the given object. This does three * distinct things: *
    *
  • If the given object has any non-final {@link Context} fields annotated * with @{@link Parameter}, sets the value of those fields to this context.
  • *
  • If the given object has any non-final {@link Service} fields annotated * with @{@link Parameter}, sets the value of those fields to the * corresponding service available from this context.
  • *
  • Calls {@link EventService#subscribe(Object)} with the object to * register any @{@link EventHandler} annotated methods as event subscribers.
  • * . *
* * @param o The object to which the context should be assigned. * @throws IllegalStateException If the object already has a context. * @throws IllegalArgumentException If the object has a required * {@link Service} parameter (see {@link Parameter#required()}) * which is not available from this context. */ public void inject(final Object o) { // iterate over all @Parameter annotated fields final List fields = ClassUtils.getAnnotatedFields(o.getClass(), Parameter.class); for (final Field f : fields) { f.setAccessible(true); // expose private fields final Class type = f.getType(); if (Service.class.isAssignableFrom(type)) { final Service existingService = (Service) ClassUtils.getValue(f, o); if (existingService != null) { throw new IllegalStateException("Context already injected: " + f.getDeclaringClass().getName() + "#" + f.getName()); } // populate Service parameter @SuppressWarnings("unchecked") final Class serviceType = (Class) type; final Service service = getService(serviceType); if (service == null && f.getAnnotation(Parameter.class).required()) { throw new IllegalArgumentException( createMissingServiceMessage(serviceType)); } ClassUtils.setValue(f, o, service); } else if (Context.class.isAssignableFrom(type) && type.isInstance(this)) { final Context existingContext = (Context) ClassUtils.getValue(f, o); if (existingContext != null) { throw new IllegalStateException("Context already injected: " + f.getDeclaringClass().getName() + "#" + f.getName()); } // populate Context parameter ClassUtils.setValue(f, o, this); } } // NB: Subscribe to all events handled by this object. // This greatly simplifies event handling. final EventService eventService = getService(EventService.class); if (eventService != null) eventService.subscribe(o); } // -- Disposable methods -- @Override public void dispose() { final EventService eventService = getService(EventService.class); if (eventService != null) eventService.publish(new ContextDisposingEvent()); // NB: Dispose services in reverse order. // This may or may not actually be necessary, but seems safer, since // dependent services will be disposed *before* their dependencies. final List services = serviceIndex.getAll(); for (int s = services.size() - 1; s >= 0; s--) { services.get(s).dispose(); } } // -- Utility methods -- /** * Utility method for converting a varargs list of service classes to a * {@link List} of those classes. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static List> serviceClassList( final Class... serviceClasses) { return serviceClasses != null ? (List) Arrays.asList(serviceClasses) : Arrays.asList(Service.class); } // -- Helper methods -- private String createMissingServiceMessage( final Class serviceType) { final String nl = System.getProperty("line.separator"); final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); final StringBuilder msg = new StringBuilder("Required service is missing: " + serviceType.getName() + nl); msg.append("Context: " + this + nl); msg.append("ClassLoader: " + classLoader + nl); // Add list of services known to context msg.append(nl + "-- Services known to context --" + nl); for (final Service knownService : serviceIndex.getAll()) { msg.append(knownService + nl); } // Add list of classes known to classloader msg.append(nl + "-- Classpath of ClassLoader --" + nl); if (classLoader instanceof URLClassLoader) { for (final URL url : ((URLClassLoader) classLoader).getURLs()) { msg.append(url.getPath() + nl); } } else { msg .append("ClassLoader was not a URLClassLoader. Could not print classpath."); } return msg.toString(); } private static boolean strict() { return !"false".equals(System.getProperty(STRICT_PROPERTY)); } }