Skip to content

Commit 2340fb5

Browse files
committed
Merge branch 'smart-run-with-map'
This makes ModuleService#run smarter when given a map as the sole argument to the "Object... input" varargs parameter. See scijava/scripting-jython#8.
2 parents e13d21f + 163b3b7 commit 2340fb5

File tree

2 files changed

+166
-7
lines changed

2 files changed

+166
-7
lines changed

src/main/java/org/scijava/module/DefaultModuleService.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import java.util.concurrent.ExecutionException;
4343
import java.util.concurrent.Future;
4444

45-
import org.scijava.Identifiable;
4645
import org.scijava.MenuPath;
4746
import org.scijava.Priority;
4847
import org.scijava.convert.ConvertService;
@@ -145,8 +144,7 @@ public List<ModuleInfo> getModules() {
145144
public ModuleInfo getModuleById(final String id) {
146145
// TODO: Cache identifiers in a hash?
147146
for (final ModuleInfo info : getModules()) {
148-
if (!(info instanceof Identifiable)) continue;
149-
final String infoID = ((Identifiable) info).getIdentifier();
147+
final String infoID = info.getIdentifier();
150148
if (id.equals(infoID)) return info;
151149
}
152150
return null;
@@ -412,6 +410,26 @@ private Map<String, Object> createMap(final Object[] values) {
412410

413411
final HashMap<String, Object> inputMap = new HashMap<>();
414412

413+
if (values.length == 1 && values[0] instanceof Map) {
414+
// NB: This hack works around an issue where some script languages,
415+
// notably Jython but potentially others too, invoke the wrong run
416+
// method when called with a map. The Object... varargs method is
417+
// chosen instead of the Map method, with the map being passed as
418+
// the sole element of the object array. The code below detects
419+
// this situation, propagating the map entries into the new map.
420+
final Map<?, ?> valueMap = (Map<?, ?>) values[0];
421+
for (final Object key : valueMap.keySet()) {
422+
if (!(key instanceof String)) {
423+
log.error("Invalid input name: " + key);
424+
continue;
425+
}
426+
final String name = (String) key;
427+
final Object value = valueMap.get(key);
428+
inputMap.put(name, value);
429+
}
430+
return inputMap;
431+
}
432+
415433
if (values.length % 2 != 0) {
416434
log.error("Ignoring extraneous argument: " + values[values.length - 1]);
417435
}

src/test/java/org/scijava/module/ModuleServiceTest.java

Lines changed: 145 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,17 @@
3131

3232
package org.scijava.module;
3333

34+
import static org.junit.Assert.assertEquals;
3435
import static org.junit.Assert.assertNull;
3536
import static org.junit.Assert.assertSame;
3637

38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import java.util.Map.Entry;
41+
import java.util.concurrent.ExecutionException;
42+
43+
import org.junit.After;
44+
import org.junit.Before;
3745
import org.junit.Test;
3846
import org.scijava.Context;
3947

@@ -44,11 +52,89 @@
4452
*/
4553
public class ModuleServiceTest {
4654

47-
@Test
48-
public void testGetSingleInput() throws ModuleException {
55+
private ModuleService moduleService;
56+
57+
@Before
58+
public void setUp() {
4959
final Context context = new Context(ModuleService.class);
50-
final ModuleService moduleService = context.getService(ModuleService.class);
60+
moduleService = context.service(ModuleService.class);
61+
}
62+
63+
@After
64+
public void tearDown() {
65+
moduleService.context().dispose();
66+
}
67+
68+
/** Tests {@link ModuleService#run(ModuleInfo, boolean, Object...)}. */
69+
@Test
70+
public void testRunModuleInfoArray() throws InterruptedException,
71+
ExecutionException
72+
{
73+
final ModuleInfo info = new FooModuleInfo();
74+
final Module m = moduleService.run(info, false, createInputArray()).get();
75+
assertEquals(expectedResult(), m.getOutput("result"));
76+
}
77+
78+
/** Tests {@link ModuleService#run(ModuleInfo, boolean, Map)}. */
79+
@Test
80+
public void testRunModuleInfoMap() throws InterruptedException,
81+
ExecutionException
82+
{
83+
final ModuleInfo info = new FooModuleInfo();
84+
final Module m = moduleService.run(info, false, createInputMap()).get();
85+
assertEquals(expectedResult(), m.getOutput("result"));
86+
}
87+
88+
/** Tests {@link ModuleService#run(Module, boolean, Object...)}. */
89+
@Test
90+
public void testRunModuleArray() throws ModuleException, InterruptedException,
91+
ExecutionException
92+
{
93+
final ModuleInfo info = new FooModuleInfo();
94+
final Module module = info.createModule();
95+
final Module m = moduleService.run(module, false, createInputArray()).get();
96+
assertSame(module, m);
97+
assertEquals(expectedResult(), m.getOutput("result"));
98+
}
99+
100+
/** Tests {@link ModuleService#run(Module, boolean, Map)}. */
101+
@Test
102+
public void testRunModuleMap() throws ModuleException, InterruptedException,
103+
ExecutionException
104+
{
105+
final ModuleInfo info = new FooModuleInfo();
106+
final Module module = info.createModule();
107+
final Module m = moduleService.run(module, false, createInputMap()).get();
108+
assertSame(module, m);
109+
assertEquals(expectedResult(), m.getOutput("result"));
110+
}
111+
112+
/**
113+
* Tests that {@link ModuleService#run(ModuleInfo, boolean, Object...)} and
114+
* {@link ModuleService#run(Module, boolean, Object...)} intelligently handle
115+
* a single-element {@link Object} array consisting of a {@code Map<String,
116+
* Object>}.
117+
* <p>
118+
* This situation can happen e.g. due to Jython choosing the wrong overloaded
119+
* {@code run} method. We correct for the issue on our side, for convenience.
120+
* </p>
121+
*/
122+
@Test
123+
public void testRunMapHack() throws ModuleException, InterruptedException,
124+
ExecutionException
125+
{
126+
final ModuleInfo info = new FooModuleInfo();
127+
final Object[] inputs = new Object[] { createInputMap() };
128+
final Module m = moduleService.run(info, false, inputs).get();
129+
assertEquals(expectedResult(), m.getOutput("result"));
130+
131+
final Module module = info.createModule();
132+
final Module m2 = moduleService.run(module, false, inputs).get();
133+
assertEquals(expectedResult(), m2.getOutput("result"));
134+
}
51135

136+
@Test
137+
public void testGetSingleInput() throws ModuleException {
52138
final ModuleInfo info = new FooModuleInfo();
53139
final Module module = info.createModule();
54140

@@ -74,6 +160,44 @@ public void testGetSingleInput() throws ModuleException {
74160
assertSame(info.getInput("double2"), singleDouble);
75161
}
76162

163+
// -- Helper methods --
164+
165+
private Object[] createInputArray() {
166+
return new Object[] { //
167+
"string", "hello", //
168+
"float", 1.234f, //
169+
"integer1", -2, //
170+
"integer2", 7, //
171+
"double1", Math.E, //
172+
"double2", Math.PI //
173+
};
174+
}
175+
176+
private Map<String, Object> createInputMap() {
177+
final Map<String, Object> inputMap = new HashMap<>();
178+
inputMap.put("string", "hello");
179+
inputMap.put("float", 1.234f);
180+
inputMap.put("integer1", -2);
181+
inputMap.put("integer2", 7);
182+
inputMap.put("double1", Math.E);
183+
inputMap.put("double2", Math.PI);
184+
return inputMap;
185+
}
186+
187+
private String expectedResult() {
188+
return mapToString(createInputMap());
189+
}
190+
191+
private static String mapToString(final Map<String, Object> map) {
192+
final StringBuilder sb = new StringBuilder();
193+
for (final Entry<String, Object> entry : map.entrySet()) {
194+
sb.append(entry.getKey() + " = " + entry.getValue() + "\n");
195+
}
196+
return sb.toString();
197+
}
198+
199+
// -- Helper classes --
200+
77201
/** A sample module for testing the module service. */
78202
public static class FooModule extends AbstractModule {
79203

@@ -90,7 +214,7 @@ public ModuleInfo getInfo() {
90214

91215
@Override
92216
public void run() {
93-
// TODO Auto-generated method stub
217+
setOutput("result", mapToString(getInputs()));
94218
}
95219

96220
}
@@ -121,6 +245,7 @@ protected void parseParameters() {
121245
addInput("integer2", Integer.class, true);
122246
addInput("double1", Double.class, false);
123247
addInput("double2", Double.class, true);
248+
addOutput("result", String.class);
124249
}
125250

126251
private <T> void addInput(final String name, final Class<T> type,
@@ -146,6 +271,22 @@ public boolean isAutoFill() {
146271
});
147272
}
148273

274+
private <T> void addOutput(final String name, final Class<T> type) {
275+
registerOutput(new AbstractModuleItem<T>(this) {
276+
277+
@Override
278+
public String getName() {
279+
return name;
280+
}
281+
282+
@Override
283+
public Class<T> getType() {
284+
return type;
285+
}
286+
287+
});
288+
}
289+
149290
}
150291

151292
}

0 commit comments

Comments
 (0)