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

org.scijava.event.DefaultEventService 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 - 2017 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.event;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.bushe.swing.event.annotation.AbstractProxySubscriber;
import org.bushe.swing.event.annotation.BaseProxySubscriber;
import org.bushe.swing.event.annotation.ReferenceStrength;
import org.scijava.Priority;
import org.scijava.log.LogService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
import org.scijava.service.AbstractService;
import org.scijava.service.Service;
import org.scijava.thread.ThreadService;
import org.scijava.util.ClassUtils;

/**
 * Default service for publishing and subscribing to SciJava events.
 * 
 * @author Curtis Rueden
 * @author Grant Harris
 */
@Plugin(type = Service.class, priority = DefaultEventService.PRIORITY)
public class DefaultEventService extends AbstractService implements
	EventService
{

	/**
	 * The default event service's priority.
	 * 

* Alternative event service implementations that wish to prioritize * themselves above this one can still ensure preferential usage via * {@code priority = DefaultEventService.PRIORITY + 1} or similar. *

*/ public static final double PRIORITY = 10 * Priority.VERY_HIGH_PRIORITY; @Parameter private LogService log; @Parameter private ThreadService threadService; private DefaultEventBus eventBus; /** * A cache for mapping {@link Method}s to the {@link SciJavaEvent} class taken * as parameters. Only methods with event parameters will cached here. */ private final Map> eventClasses = new HashMap<>(); /** * Set of claimed {@link EventHandler#key()}s. Additional event handlers * specifying the same key will be ignored rather than subscribed. */ private final HashSet keys = new HashSet<>(); // -- EventService methods -- @Override public void publish(final E e) { e.setContext(getContext()); e.setCallingThread(Thread.currentThread()); eventBus.publishNow(e); } @Override public void publishLater(final E e) { e.setContext(getContext()); e.setCallingThread(Thread.currentThread()); eventBus.publishLater(e); } @Override public List> subscribe(final Object o) { final List eventHandlers = ClassUtils.getAnnotatedMethods(o.getClass(), EventHandler.class); if (eventHandlers.isEmpty()) return Collections.emptyList(); final ArrayList> subscribers = new ArrayList<>(); for (final Method m : eventHandlers) { // verify that the event handler method is valid final Class eventClass = getEventClass(m); if (eventClass == null) { log.warn("Invalid EventHandler method: " + m); continue; } // verify that the event handler key isn't already claimed final String key = m.getAnnotation(EventHandler.class).key(); if (!key.isEmpty()) { synchronized (keys) { if (keys.contains(key)) continue; keys.add(key); } } // subscribe the event handler subscribers.add(subscribe(eventClass, o, m)); } return subscribers; } @Override public void unsubscribe(final Collection> subscribers) { for (final EventSubscriber subscriber : subscribers) { unsubscribe(subscriber); } } @Override public List> getSubscribers( final Class c) { // HACK - It appears that EventBus API is incorrect, in that // EventBus#getSubscribers(Class) returns a List when it should // actually be a List>. This method works around the // problem with casts. @SuppressWarnings("rawtypes") final List list = eventBus.getSubscribers(c); @SuppressWarnings("unchecked") final List> typedList = list; return typedList; } // -- Service methods -- @Override public void initialize() { eventBus = new DefaultEventBus(threadService, log); super.initialize(); } // -- Disposable methods -- @Override public void dispose() { eventBus.clearAllSubscribers(); } // -- Helper methods -- private void subscribe(final Class c, final EventSubscriber subscriber) { eventBus.subscribe(c, subscriber); } private void unsubscribe( final EventSubscriber subscriber) { unsubscribe(subscriber.getEventClass(), subscriber); } private void unsubscribe(final Class c, final EventSubscriber subscriber) { eventBus.unsubscribe(c, subscriber); } private EventSubscriber subscribe( final Class c, final Object o, final Method m) { final ProxySubscriber subscriber = new ProxySubscriber<>(c, o, m); subscribe(c, subscriber); return subscriber; } /** Gets the event class parameter of the given method. */ private Class getEventClass(final Method m) { // Check for a cached entry for the given method Class eventClass = eventClasses.get(m); if (eventClass == null) { final Class[] c = m.getParameterTypes(); if (c == null || c.length != 1) return null; // wrong number of args if (!SciJavaEvent.class.isAssignableFrom(c[0])) return null; // wrong class // Cache the eventClass eventClass = c[0]; eventClasses.put(m, eventClass); } @SuppressWarnings("unchecked") final Class typedClass = (Class) eventClass; return typedClass; } // -- Event handlers garbage collection preventer -- private WeakHashMap>> keepEm = new WeakHashMap<>(); /** * Prevents {@link ProxySubscriber} instances from being garbage collected * prematurely. *

* We instantiate a {@link ProxySubscriber} for each method with an * {@link EventHandler} annotation. These instances are then passed to the * EventBus. The way the instances are created ensures that the event handlers * will be held only as weak references. But they are weak references to the * {@link ProxySubscriber} instances rather than the object containing the * {@link EventHandler}-annotated methods. Therefore, we have to make sure * that there is a non-GC'able reference to each {@link ProxySubscriber} as * long as there is a reference to the containing event handler object. *

* * @param o the object containing {@link EventHandler}-annotated methods * @param subscriber a {@link ProxySubscriber} for a particular {@link EventHandler} */ private synchronized void keepIt(final Object o, final ProxySubscriber subscriber) { List> list = keepEm.get(o); if (list == null) { list = new ArrayList<>(); keepEm.put(o, list); } list.add(subscriber); } // -- Helper classes -- /** * Helper class used by {@link #subscribe(Object)}. *

* Recapitulates some logic from {@link BaseProxySubscriber}, because that * class implements {@link org.bushe.swing.event.EventSubscriber} as a raw * type, which is incompatible with this class implementing SciJava's * {@link EventSubscriber} as a typed interface; it becomes impossible to * implement both {@code onEvent(Object)} and {@code onEvent(E)}. *

*/ private class ProxySubscriber extends AbstractProxySubscriber implements EventSubscriber { private final Class c; public ProxySubscriber(final Class c, final Object o, final Method m) { super(o, m, ReferenceStrength.WEAK, eventBus, false); keepIt(o, this); this.c = c; // allow calling of non-public methods m.setAccessible(true); } /** * Handles the event publication by pushing it to the real subscriber's * subscription method. * * @param event The event to publish. */ @Override public void onEvent(final E event) { try { final Object obj = getProxiedSubscriber(); if (obj == null) return; // has been garbage collected getSubscriptionMethod().invoke(obj, event); } catch (final IllegalAccessException exc) { log.error("Exception during event handling:\n\t[Event] " + event.getClass().getName() + ":" + event + "\n\t[Subscriber] " + getProxiedSubscriber() + "\n\t[Method] " + getSubscriptionMethod(), exc); } catch (final InvocationTargetException exc) { log.error("Exception during event handling:\n\t[Event] " + event.getClass().getName() + event + "\n\t[Subscriber] " + getProxiedSubscriber() + "\n\t[Method] " + getSubscriptionMethod(), exc.getCause()); } } @Override public Class getEventClass() { return c; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy