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

org.scijava.Context Maven / Gradle / Ivy

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%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.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 {

	// -- 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. */
	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.
	 */
	@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}). * @throws ClassCastException If any of the given arguments do not implement * the {@link Service} interface. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Context(final Class... serviceClasses) { this(serviceClasses != null ? (Collection) Arrays.asList(serviceClasses) : Arrays.asList(Service.class)); } /** * 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}). */ public Context(final Collection> serviceClasses) { this(serviceClasses, new PluginIndex()); } /** * Creates a new SciJava application with 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 PluginIndex's PluginFinder are * desired. *

* NB: the {@link PluginIndex#discover()} method may still be called, adding * additional plugins to this index. The mechanism of discovery should be * configured exclusively through the attached PluginFinder. *

* * @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. */ @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}. *

* NB: Context creation is an important step of a SciJava applictation's * lifecycle. Particularly in environments where more than one implementation * exists for various services, careful consideration should be exercised * regaring 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}). * @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. */ public Context(final Collection> serviceClasses, final PluginIndex pluginIndex) { serviceIndex = new ServiceIndex(); this.pluginIndex = pluginIndex; pluginIndex.discover(); final ServiceHelper serviceHelper = new ServiceHelper(this, serviceClasses); 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 (type.isAssignableFrom(getClass())) { 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() { // 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(); } } // -- 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(); } }