Skip to content

Commit a9a28c1

Browse files
committed
Merge branch 'vendor-eventbus'
This branch moves the org.bushe:eventbus codebase into SciJava Common directly, removing unneeded parts, and fixing an intermittent JVM shutdown hang caused by a ThreadSafeEventService's cleanup timer that wasn't marked as a daemon thread. It also adds a new subscribe(EventSubscriber) method to the SciJava EventService.
2 parents ffbf376 + f49e711 commit a9a28c1

34 files changed

+4370
-62
lines changed

pom.xml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</parent>
1111

1212
<artifactId>scijava-common</artifactId>
13-
<version>2.93.2-SNAPSHOT</version>
13+
<version>2.94.0-SNAPSHOT</version>
1414

1515
<name>SciJava Common</name>
1616
<description>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.</description>
@@ -167,6 +167,7 @@
167167
<license.licenseName>bsd_2</license.licenseName>
168168
<license.projectName>SciJava Common shared library for SciJava software.</license.projectName>
169169
<license.copyrightOwners>SciJava developers.</license.copyrightOwners>
170+
<license.excludes>**/bushe/**</license.excludes>
170171
</properties>
171172

172173
<dependencies>
@@ -176,13 +177,6 @@
176177
<artifactId>parsington</artifactId>
177178
</dependency>
178179

179-
<!-- Third-party dependencies -->
180-
<dependency>
181-
<groupId>org.bushe</groupId>
182-
<artifactId>eventbus</artifactId>
183-
<version>1.4</version>
184-
</dependency>
185-
186180
<!-- Test scope dependencies -->
187181
<dependency>
188182
<groupId>junit</groupId>
@@ -193,7 +187,7 @@
193187
<groupId>org.mockito</groupId>
194188
<artifactId>mockito-core</artifactId>
195189
<scope>test</scope>
196-
</dependency>
190+
</dependency>
197191
</dependencies>
198192

199193
<build>

src/main/java/org/scijava/event/DefaultEventBus.java

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,17 @@
3434
import java.util.Arrays;
3535
import java.util.List;
3636

37-
import org.bushe.swing.event.CleanupEvent;
38-
import org.bushe.swing.event.ThreadSafeEventService;
37+
import org.scijava.event.bushe.ThreadSafeEventService;
3938
import org.scijava.log.LogService;
4039
import org.scijava.service.Service;
4140
import org.scijava.thread.ThreadService;
4241

4342
/**
44-
* An {@link org.bushe.swing.event.EventService} implementation for SciJava.
43+
* An {@code org.scijava.event.bushe.EventService} implementation for SciJava.
4544
* <p>
4645
* It is called "DefaultEventBus" rather than "DefaultEventService" to avoid a
4746
* name clash with {@link DefaultEventService}, which is not an
48-
* {@link org.bushe.swing.event.EventService} but rather a SciJava
47+
* {@code org.scijava.event.bushe.EventService} but rather a SciJava
4948
* {@link Service} implementation.
5049
* </p>
5150
*
@@ -59,7 +58,7 @@ public class DefaultEventBus extends ThreadSafeEventService {
5958
public DefaultEventBus(final ThreadService threadService,
6059
final LogService log)
6160
{
62-
super(200L, false, null, null, null);
61+
super(200L, null, null, null);
6362
this.threadService = threadService;
6463
this.log = log;
6564
}
@@ -112,37 +111,10 @@ public void publishLater(final String topicName, final Object eventObj) {
112111
getVetoEventListeners(topicName), null);
113112
}
114113

115-
// -- org.bushe.swing.event.EventService methods --
114+
// -- org.scijava.event.bushe.EventService methods --
116115

117116
@Override
118117
public void publish(final Object event) {
119-
// HACK: Work around a deadlock problem caused by ThreadSafeEventService:
120-
121-
// 1) The ThreadSafeEventService superclass has a special cleanup thread
122-
// that takes care of cleaning up stale references. Every time it runs, it
123-
// publishes some CleanupEvents using publish(Object) to announce that this
124-
// is occurring. Normally, such publication delegates to
125-
// publishNow, which calls ThreadService#invoke, which calls
126-
// EventQueue.invokeAndWait, which queues the publication for execution on
127-
// the EDT and then blocks until publication is complete.
128-
129-
// 2) When the ThreadSafeEventService publishes the CleanupEvents, it does
130-
// so inside a synchronized block that locks on a "listenerLock" object.
131-
132-
// 3) Unfortunately, since the CleanupEvent publication is merely *queued*,
133-
// any other pending operations on the EDT happen first. If one such
134-
// operation meanwhile calls e.g.
135-
// ThreadSafeEventService#getSubscribers(Class<T>), it will deadlock because
136-
// those getter methods are also synchronized on the listenerLock object.
137-
138-
// Hence, our hack workaround is to instead use publishLater for the
139-
// CleanupEvents, since no one really cares about them anyway. ;-)
140-
141-
if (event instanceof CleanupEvent) {
142-
publishLater(event);
143-
return;
144-
}
145-
146118
publishNow(event);
147119
}
148120

src/main/java/org/scijava/event/DefaultEventService.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@
4040
import java.util.Map;
4141
import java.util.WeakHashMap;
4242

43-
import org.bushe.swing.event.annotation.AbstractProxySubscriber;
44-
import org.bushe.swing.event.annotation.BaseProxySubscriber;
45-
import org.bushe.swing.event.annotation.ReferenceStrength;
4643
import org.scijava.Priority;
44+
import org.scijava.event.bushe.AbstractProxySubscriber;
45+
import org.scijava.event.bushe.ReferenceStrength;
4746
import org.scijava.log.LogService;
4847
import org.scijava.plugin.Parameter;
4948
import org.scijava.plugin.Plugin;
@@ -139,6 +138,11 @@ public List<EventSubscriber<?>> subscribe(final Object o) {
139138
return subscribers;
140139
}
141140

141+
@Override
142+
public void subscribe(final EventSubscriber<?> subscriber) {
143+
eventBus.subscribe(subscriber.getClass(), subscriber);
144+
}
145+
142146
@Override
143147
public void unsubscribe(final Collection<EventSubscriber<?>> subscribers) {
144148
for (final EventSubscriber<?> subscriber : subscribers) {
@@ -262,9 +266,10 @@ private synchronized void keepIt(final Object o, final ProxySubscriber<?> subscr
262266
/**
263267
* Helper class used by {@link #subscribe(Object)}.
264268
* <p>
265-
* Recapitulates some logic from {@link BaseProxySubscriber}, because that
266-
* class implements {@link org.bushe.swing.event.EventSubscriber} as a raw
267-
* type, which is incompatible with this class implementing SciJava's
269+
* Recapitulates some logic from
270+
* {@code org.scijava.event.bushe.BaseProxySubscriber}, because that class
271+
* implements {@link org.scijava.event.bushe.EventSubscriber} as a raw type,
272+
* which is incompatible with this class implementing SciJava's
268273
* {@link EventSubscriber} as a typed interface; it becomes impossible to
269274
* implement both {@code onEvent(Object)} and {@code onEvent(E)}.
270275
* </p>

src/main/java/org/scijava/event/EventHandler.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@
4040
* handling methods and annotating each with @{@link EventHandler}.
4141
* <p>
4242
* Note to developers: This annotation serves exactly the same purpose as
43-
* EventBus's {@link org.bushe.swing.event.annotation.EventSubscriber}
44-
* annotation, recapitulating a subset of the same functionality. We do this to
45-
* avoid third party code depending directly on EventBus. That is, we do not
46-
* wish to require SciJava developers to {@code import org.bushe.swing.event.*}
47-
* or similar. In this way, EventBus is isolated as only a transitive dependency
48-
* of downstream code, rather than a direct dependency. Unfortunately, because
49-
* Java annotation interfaces cannot utilize inheritance, we have to
50-
* recapitulate the functionality rather than extend it (as we are able to do
51-
* with {@link EventSubscriber}).
43+
* EventBus's {@code org.scijava.event.bushe.EventSubscriber} annotation,
44+
* recapitulating a subset of the same functionality. We do this to avoid third
45+
* party code depending directly on EventBus. That is, we do not wish to require
46+
* SciJava developers to {@code import org.scijava.event.bushe.*} or similar. In
47+
* this way, EventBus is isolated as only a transitive dependency of downstream
48+
* code, rather than a direct dependency. Unfortunately, because Java annotation
49+
* interfaces cannot utilize inheritance, we have to recapitulate the
50+
* functionality rather than extend it (as we are able to do with
51+
* {@link EventSubscriber}).
5252
* </p>
5353
*
5454
* @author Curtis Rueden

src/main/java/org/scijava/event/EventService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,24 @@ public interface EventService extends SciJavaService {
125125
*/
126126
List<EventSubscriber<?>> subscribe(Object o);
127127

128+
/**
129+
* Subscribes the given {@link EventSubscriber} to its associated event class.
130+
* Its {@link EventSubscriber#onEvent} method will be called whenever an event
131+
* of the matching type is published.
132+
* <p>
133+
* <strong>Important note:</strong> The event service does <em>not</em> keep a
134+
* strong reference to the subscriber! If you use this method, you are also
135+
* responsible for keeping a reference to the subscriber, or else it is likely
136+
* to be garbage collected, and thus no longer respond to events as intended.
137+
* One simple way to force a strong reference to exist is to add it to
138+
* SciJava's {@link org.scijava.object.ObjectService} via
139+
* {@link org.scijava.object.ObjectService#addObject}.
140+
* </p>
141+
*
142+
* @param subscriber the event subscriber to register
143+
*/
144+
void subscribe(EventSubscriber<?> subscriber);
145+
128146
/**
129147
* Removes all the given subscribers; they will no longer be notified when
130148
* events are published.

src/main/java/org/scijava/event/EventSubscriber.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
* @param <E> Type of event for which to listen
4545
*/
4646
public interface EventSubscriber<E extends SciJavaEvent> extends
47-
org.bushe.swing.event.EventSubscriber<E>
47+
org.scijava.event.bushe.EventSubscriber<E>
4848
{
4949

5050
@Override
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package org.scijava.event.bushe;
2+
3+
import java.lang.ref.WeakReference;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.AccessibleObject;
6+
import java.lang.reflect.InvocationTargetException;
7+
8+
/**
9+
* Common base class for EventService Proxies.
10+
* <p>
11+
* Implementing Prioritized even when Priority is not used is always OK. The default
12+
* value of 0 retains the FIFO order.
13+
*/
14+
public abstract class AbstractProxySubscriber implements ProxySubscriber, Prioritized {
15+
private Object proxiedSubscriber;
16+
private Method subscriptionMethod;
17+
private ReferenceStrength referenceStrength;
18+
private EventService eventService;
19+
private int priority;
20+
protected boolean veto;
21+
22+
protected AbstractProxySubscriber(Object proxiedSubscriber, Method subscriptionMethod,
23+
ReferenceStrength referenceStrength, EventService es, boolean veto) {
24+
this(proxiedSubscriber, subscriptionMethod, referenceStrength, 0, es, veto);
25+
}
26+
27+
protected AbstractProxySubscriber(Object proxiedSubscriber, Method subscriptionMethod,
28+
ReferenceStrength referenceStrength, int priority, EventService es, boolean veto) {
29+
this.referenceStrength = referenceStrength;
30+
this.priority = priority;
31+
eventService = es;
32+
this.veto = veto;
33+
if (proxiedSubscriber == null) {
34+
throw new IllegalArgumentException("The realSubscriber cannot be null when constructing a proxy subscriber.");
35+
}
36+
if (subscriptionMethod == null) {
37+
throw new IllegalArgumentException("The subscriptionMethod cannot be null when constructing a proxy subscriber.");
38+
}
39+
Class<?> returnType = subscriptionMethod.getReturnType();
40+
if (veto && returnType != Boolean.TYPE) {
41+
throw new IllegalArgumentException("The subscriptionMethod must have the two parameters, the first one must be a String and the second a non-primitive (Object or derivative).");
42+
}
43+
if (ReferenceStrength.WEAK.equals(referenceStrength)) {
44+
this.proxiedSubscriber = new WeakReference(proxiedSubscriber);
45+
} else {
46+
this.proxiedSubscriber = proxiedSubscriber;
47+
}
48+
this.subscriptionMethod = subscriptionMethod;
49+
}
50+
51+
/** @return the object this proxy is subscribed on behalf of */
52+
public Object getProxiedSubscriber() {
53+
if (proxiedSubscriber instanceof WeakReference) {
54+
return ((WeakReference)proxiedSubscriber).get();
55+
}
56+
return proxiedSubscriber;
57+
}
58+
59+
/** @return the subscriptionMethod passed in the constructor */
60+
public Method getSubscriptionMethod() {
61+
return subscriptionMethod;
62+
}
63+
64+
/** @return the EventService passed in the constructor */
65+
public EventService getEventService() {
66+
return eventService;
67+
}
68+
69+
/** @return the ReferenceStrength passed in the constructor */
70+
public ReferenceStrength getReferenceStrength() {
71+
return referenceStrength;
72+
}
73+
74+
/**
75+
* @return the priority, no effect if priority is 0 (the default value)
76+
*/
77+
public int getPriority() {
78+
return priority;
79+
}
80+
81+
/**
82+
* Called by EventServices to inform the proxy that it is unsubscribed.
83+
* The ProxySubscriber should perform any necessary cleanup.
84+
* <p>
85+
* <b>Overriding classes must call super.proxyUnsubscribed() or risk
86+
* things not being cleanup up properly.</b>
87+
*/
88+
public void proxyUnsubscribed() {
89+
proxiedSubscriber = null;
90+
}
91+
92+
@Override
93+
public final int hashCode() {
94+
throw new RuntimeException("Proxy subscribers are not allowed in Hash " +
95+
"Maps, since the underlying values use Weak References that" +
96+
"may disappear, the calculations may not be the same in" +
97+
"successive calls as required by hashCode.");
98+
}
99+
100+
protected boolean retryReflectiveCallUsingAccessibleObject(Object[] args, Method subscriptionMethod, Object obj,
101+
IllegalAccessException e, String message) {
102+
boolean accessibleTriedAndFailed = false;
103+
if (subscriptionMethod != null) {
104+
AccessibleObject[] accessibleMethod = {subscriptionMethod};
105+
try {
106+
AccessibleObject.setAccessible(accessibleMethod, true);
107+
Object returnValue = subscriptionMethod.invoke(obj, args);
108+
return Boolean.valueOf(returnValue+"");
109+
} catch (SecurityException ex) {
110+
accessibleTriedAndFailed = true;
111+
} catch (InvocationTargetException e1) {
112+
throw new RuntimeException(message, e);
113+
} catch (IllegalAccessException e1) {
114+
throw new RuntimeException(message, e);
115+
}
116+
}
117+
if (accessibleTriedAndFailed) {
118+
message = message + ". An attempt was made to make the method accessible, but the SecurityManager denied the attempt.";
119+
}
120+
throw new RuntimeException(message, e);
121+
}
122+
123+
@Override
124+
public boolean equals(Object obj) {
125+
if (obj instanceof AbstractProxySubscriber) {
126+
AbstractProxySubscriber bps = (AbstractProxySubscriber) obj;
127+
if (referenceStrength != bps.referenceStrength) {
128+
return false;
129+
}
130+
if (subscriptionMethod != bps.subscriptionMethod) {
131+
return false;
132+
}
133+
if (ReferenceStrength.WEAK == referenceStrength) {
134+
if (((WeakReference)proxiedSubscriber).get() != ((WeakReference)bps.proxiedSubscriber).get()) {
135+
return false;
136+
}
137+
} else {
138+
if (proxiedSubscriber != bps.proxiedSubscriber) {
139+
return false;
140+
}
141+
}
142+
if (veto != bps.veto) {
143+
return false;
144+
}
145+
if (eventService != bps.eventService) {
146+
return false;
147+
}
148+
return true;
149+
} else {
150+
return false;
151+
}
152+
}
153+
154+
@Override
155+
public String toString() {
156+
return "AbstractProxySubscriber{" +
157+
"realSubscriber=" + (proxiedSubscriber instanceof WeakReference?
158+
((WeakReference)proxiedSubscriber).get():proxiedSubscriber) +
159+
", subscriptionMethod=" + subscriptionMethod +
160+
", veto=" + veto +
161+
", referenceStrength=" + referenceStrength +
162+
", eventService=" + eventService +
163+
'}';
164+
}
165+
}

0 commit comments

Comments
 (0)