Skip to content

Commit 7b8be13

Browse files
authored
Merge pull request #332 from scijava/fix-singleton-service
SingletonService: update instances when available plugins change
2 parents 821c829 + 46bd654 commit 7b8be13

File tree

3 files changed

+173
-17
lines changed

3 files changed

+173
-17
lines changed

src/main/java/org/scijava/plugin/AbstractSingletonService.java

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,17 @@
3232

3333
package org.scijava.plugin;
3434

35+
import java.util.ArrayList;
3536
import java.util.Collections;
3637
import java.util.HashMap;
3738
import java.util.List;
3839
import java.util.Map;
3940

41+
import org.scijava.event.EventHandler;
4042
import org.scijava.log.LogService;
4143
import org.scijava.object.ObjectService;
44+
import org.scijava.plugin.event.PluginsAddedEvent;
45+
import org.scijava.plugin.event.PluginsRemovedEvent;
4246

4347
/**
4448
* Abstract base class for {@link SingletonService}s.
@@ -56,9 +60,6 @@ public abstract class AbstractSingletonService<PT extends SingletonPlugin>
5660
@Parameter
5761
private ObjectService objectService;
5862

59-
// TODO: Listen for PluginsAddedEvent and PluginsRemovedEvent
60-
// and update the list of singletons accordingly.
61-
6263
/** List of singleton plugin instances. */
6364
private List<PT> instances;
6465

@@ -74,7 +75,7 @@ public ObjectService objectService() {
7475
@Override
7576
public List<PT> getInstances() {
7677
if (instances == null) initInstances();
77-
return instances;
78+
return Collections.unmodifiableList(instances);
7879
}
7980

8081
@SuppressWarnings("unchecked")
@@ -84,20 +85,63 @@ public <P extends PT> P getInstance(final Class<P> pluginClass) {
8485
return (P) instanceMap.get(pluginClass);
8586
}
8687

88+
//-- Event handlers --
89+
90+
@EventHandler
91+
protected void onEvent(final PluginsRemovedEvent event) {
92+
if (instanceMap == null) return;
93+
for (final PluginInfo<?> info : event.getItems()) {
94+
final PT obj = instanceMap.remove(info.getPluginClass());
95+
if (obj != null) { // we actually removed a plugin
96+
instances.remove(obj);
97+
objectService.removeObject(obj);
98+
}
99+
}
100+
}
101+
102+
@EventHandler
103+
protected void onEvent(final PluginsAddedEvent event) {
104+
if (instanceMap == null) return;
105+
// collect singleton plugins
106+
final List<PluginInfo<PT>> singletons = new ArrayList<>();
107+
for (final PluginInfo<?> pluginInfo : event.getItems()) {
108+
if (getPluginType().isAssignableFrom(pluginInfo.getPluginType())) {
109+
@SuppressWarnings("unchecked")
110+
final PT plugin = pluginService().createInstance(
111+
(PluginInfo<PT>) pluginInfo);
112+
@SuppressWarnings("unchecked")
113+
final Class<? extends PT> pluginClass = (Class<? extends PT>) plugin
114+
.getClass();
115+
instanceMap.put(pluginClass, plugin);
116+
instances.add(plugin);
117+
}
118+
}
119+
120+
for (final PluginInfo<PT> pluginInfo : singletons) {
121+
final PT plugin = pluginService().createInstance(pluginInfo);
122+
@SuppressWarnings("unchecked")
123+
final Class<? extends PT> pluginClass = (Class<? extends PT>) plugin
124+
.getClass();
125+
instanceMap.put(pluginClass, plugin);
126+
instances.add(plugin);
127+
}
128+
129+
}
130+
87131
// -- Helper methods --
88132

89133
private synchronized void initInstances() {
90134
if (instances != null) return;
91135

92-
final List<PT> list = Collections.unmodifiableList(filterInstances(
93-
pluginService().createInstancesOfType(getPluginType())));
136+
@SuppressWarnings("unchecked")
137+
final List<PT> list = (List<PT>) filterInstances(pluginService()
138+
.createInstancesOfType(getPluginType()));
94139

95-
final HashMap<Class<? extends PT>, PT> map =
96-
new HashMap<>();
140+
final Map<Class<? extends PT>, PT> map = new HashMap<>();
97141

98142
for (final PT plugin : list) {
99143
@SuppressWarnings("unchecked")
100-
final Class<? extends PT> ptClass =
144+
final Class<? extends PT> ptClass = //
101145
(Class<? extends PT>) plugin.getClass();
102146
map.put(ptClass, plugin);
103147
}

src/main/java/org/scijava/plugin/SingletonService.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import java.util.ArrayList;
3636
import java.util.List;
3737

38-
import org.scijava.object.LazyObjects;
3938
import org.scijava.object.ObjectService;
4039

4140
/**
@@ -95,13 +94,7 @@ default <P extends PT> P create(final Class<P> pluginClass) {
9594
@Override
9695
default void initialize() {
9796
// add singleton instances to the object index... IN THE FUTURE!
98-
objectService().getIndex().addLater(new LazyObjects<Object>() {
99-
100-
@Override
101-
public ArrayList<Object> get() {
102-
return new ArrayList<>(getInstances());
103-
}
104-
});
97+
objectService().getIndex().addLater(() -> new ArrayList<>(getInstances()));
10598
}
10699

107100
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2018 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
7+
* Institute of Molecular Cell Biology and Genetics, University of
8+
* Konstanz, and KNIME GmbH.
9+
* %%
10+
* Redistribution and use in source and binary forms, with or without
11+
* modification, are permitted provided that the following conditions are met:
12+
*
13+
* 1. Redistributions of source code must retain the above copyright notice,
14+
* this list of conditions and the following disclaimer.
15+
* 2. Redistributions in binary form must reproduce the above copyright notice,
16+
* this list of conditions and the following disclaimer in the documentation
17+
* and/or other materials provided with the distribution.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
23+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
* #L%
31+
*/
32+
33+
package org.scijava.plugin;
34+
35+
import static org.junit.Assert.assertEquals;
36+
import static org.junit.Assert.assertFalse;
37+
import static org.junit.Assert.assertTrue;
38+
39+
import java.util.List;
40+
41+
import org.junit.Test;
42+
import org.scijava.Context;
43+
44+
/**
45+
* Tests for the {@link SingletonService}
46+
*
47+
* @author Gabriel Einsdorf KNIME GmbH
48+
*/
49+
public class SingletonServiceTest {
50+
51+
@Test
52+
public void testListenToRemove() {
53+
54+
final Context ctx = new Context(PluginService.class, DummySingletonService.class,
55+
DummySingletonService2.class);
56+
57+
final DummySingletonService dss = ctx.getService(
58+
DummySingletonService.class);
59+
60+
final DummySingletonService2 dss2 = ctx.getService(
61+
DummySingletonService2.class);
62+
63+
final List<DummyPlugin> instances = dss.getInstances();
64+
final DummyPlugin dummy = instances.get(0);
65+
66+
assertFalse("Service not correctly initialized", dss2.getInstances()
67+
.isEmpty());
68+
69+
// test successful removal
70+
final PluginService ps = ctx.getService(PluginService.class);
71+
ps.removePlugin(dummy.getInfo());
72+
73+
assertFalse("Plugin was removed from wrong service!", dss2.getInstances()
74+
.isEmpty());
75+
assertTrue("Plugin was not removed!", dss.getInstances().isEmpty());
76+
77+
// test successful add
78+
ps.addPlugin(dummy.getInfo());
79+
assertEquals("Wrong number of plugins in service:", 1, dss.getInstances()
80+
.size());
81+
assertEquals("Wrong number of plugins in independent service:", 1, dss2
82+
.getInstances().size());
83+
}
84+
85+
@Plugin(type = DummyPlugin.class)
86+
public static class DummyPlugin extends AbstractRichPlugin implements
87+
SingletonPlugin
88+
{
89+
// NB: No implementation needed.
90+
}
91+
92+
public static class DummySingletonService extends
93+
AbstractSingletonService<DummyPlugin>
94+
{
95+
96+
@Override
97+
public Class<DummyPlugin> getPluginType() {
98+
return DummyPlugin.class;
99+
}
100+
}
101+
102+
@Plugin(type = DummyPlugin2.class)
103+
public static class DummyPlugin2 extends AbstractRichPlugin implements
104+
SingletonPlugin
105+
{
106+
// NB: No implementation needed.
107+
}
108+
109+
public static class DummySingletonService2 extends
110+
AbstractSingletonService<DummyPlugin2>
111+
{
112+
113+
@Override
114+
public Class<DummyPlugin2> getPluginType() {
115+
return DummyPlugin2.class;
116+
}
117+
}
118+
119+
}

0 commit comments

Comments
 (0)