Skip to content

Commit 44eb258

Browse files
authored
Merge pull request #387 from scijava/generic-dialog
Add Inputs class
2 parents 460927a + 9d3a6e6 commit 44eb258

File tree

5 files changed

+283
-6
lines changed

5 files changed

+283
-6
lines changed

src/main/java/org/scijava/command/DynamicCommand.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public abstract class DynamicCommand extends DefaultMutableModule implements
6060
private CommandService commandService;
6161

6262
@Parameter
63-
private PluginService pluginService;
63+
protected PluginService pluginService;
6464

6565
@Parameter
6666
protected ModuleService moduleService;
@@ -76,7 +76,8 @@ public abstract class DynamicCommand extends DefaultMutableModule implements
7676
public DynamicCommandInfo getInfo() {
7777
if (info == null) {
7878
// NB: Create dynamic metadata lazily.
79-
final CommandInfo commandInfo = commandService.getCommand(getClass());
79+
CommandInfo commandInfo = commandService.getCommand(getClass());
80+
if (commandInfo == null) commandInfo = new CommandInfo(getClass());
8081
info = new DynamicCommandInfo(commandInfo, getClass());
8182
}
8283
return info;

src/main/java/org/scijava/command/DynamicCommandInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848
* Helper class for maintaining a {@link DynamicCommand}'s associated
4949
* {@link ModuleInfo}.
5050
* <p>
51-
* The {@link CommandService} has a plain {@link CommandInfo} object in its
52-
* index, populated from the {@link DynamicCommand}'s @{@link Plugin}
53-
* annotation. So this class adapts that object, delegating to it for the
51+
* This class wraps a plain {@link CommandInfo} object (e.g. from the
52+
* {@link CommandService}'s index, present due to an @{@link Plugin} annotation
53+
* on the {@link DynamicCommand} class), delegating to it for the
5454
* {@link UIDetails} methods. The plain {@link CommandInfo} cannot be used
5555
* as-is, however, because we need to override the {@link ModuleInfo} methods as
5656
* well as provide metadata manipulation functionality such as
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 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.command;
34+
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.concurrent.ExecutionException;
38+
39+
import org.scijava.Context;
40+
import org.scijava.module.process.PreprocessorPlugin;
41+
42+
/**
43+
* A way to build a dynamic set of inputs, whose values are then harvested by
44+
* the preprocessing framework.
45+
* <p>
46+
* The {@link #run()} method of this command does nothing. If you want something
47+
* custom to happen during execution, use a normal {@link Command} instead:
48+
* either implement {@link Command directly}, or extend {@link ContextCommand}
49+
* or {@link DynamicCommand}.
50+
* </p>
51+
* <p>
52+
* Here is are some examples of usage:
53+
* </p>
54+
*
55+
* <pre>
56+
* {@code
57+
* // Single input, no configuration.
58+
* Inputs inputs = new Inputs(context);
59+
* inputs.addInput("sigma", Double.class);
60+
* Double sigma = (Double) inputs.harvest().get("sigma");
61+
*
62+
* // Two inputs, no configuration.
63+
* Inputs inputs = new Inputs(context);
64+
* inputs.addInput("name", String.class);
65+
* inputs.addInput("age", Integer.class);
66+
* Map<String, Object> values = inputs.harvest();
67+
* String name = (String) values.get("name");
68+
* Integer age = (Integer) values.get("age");
69+
*
70+
* // Inputs with configuration.
71+
* Inputs inputs = new Inputs(context);
72+
* MutableModuleItem<String> wordInput = inputs.addInput("word", String.class);
73+
* wordInput.setLabel("Favorite word");
74+
* wordInput.setChoices(Arrays.asList("quick", "brown", "fox"));
75+
* wordInput.setDefaultValue("fox");
76+
* MutableModuleItem<Double> opacityInput = inputs.addInput("opacity", Double.class);
77+
* opacityInput.setMinimumValue(0.0);
78+
* opacityInput.setMaximumValue(1.0);
79+
* opacityInput.setDefaultValue(0.5);
80+
* opacityInput.setWidgetStyle(NumberWidget.SCROLL_BAR_STYLE);
81+
* inputs.harvest();
82+
* String word = wordInput.getValue(inputs);
83+
* Double opacity = opacityInput.getValue(inputs);
84+
* }
85+
* </pre>
86+
*
87+
* @author Curtis Rueden
88+
*/
89+
public final class Inputs extends DynamicCommand {
90+
91+
public Inputs(final Context context) {
92+
context.inject(this);
93+
}
94+
95+
public Map<String, Object> harvest() {
96+
try {
97+
final List<PreprocessorPlugin> pre = //
98+
pluginService.createInstancesOfType(PreprocessorPlugin.class);
99+
return moduleService.run(this, pre, null).get().getInputs();
100+
}
101+
catch (final InterruptedException | ExecutionException exc) {
102+
throw new RuntimeException(exc);
103+
}
104+
}
105+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ public <PT extends SciJavaPlugin> List<PT> createInstances(
236236
return p;
237237
}
238238
catch (final Throwable t) {
239-
final String errorMessage = "Cannot create plugin: " + info;
239+
final String errorMessage = //
240+
"Cannot create plugin: " + info.getClassName();
240241
if (log.isDebug()) log.debug(errorMessage, t);
241242
else log.error(errorMessage);
242243
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2017 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.command;
34+
35+
import static org.junit.Assert.assertEquals;
36+
37+
import java.util.Arrays;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
41+
import org.junit.After;
42+
import org.junit.Before;
43+
import org.junit.Test;
44+
import org.scijava.Context;
45+
import org.scijava.InstantiableException;
46+
import org.scijava.log.LogLevel;
47+
import org.scijava.log.LogService;
48+
import org.scijava.module.Module;
49+
import org.scijava.module.ModuleItem;
50+
import org.scijava.module.MutableModuleItem;
51+
import org.scijava.module.process.AbstractPreprocessorPlugin;
52+
import org.scijava.module.process.PreprocessorPlugin;
53+
import org.scijava.plugin.PluginInfo;
54+
import org.scijava.plugin.PluginService;
55+
import org.scijava.widget.InputHarvester;
56+
import org.scijava.widget.NumberWidget;
57+
58+
/**
59+
* Tests {@link Inputs}.
60+
*
61+
* @author Curtis Rueden
62+
* @author Deborah Schmidt
63+
*/
64+
public class InputsTest {
65+
66+
private Context context;
67+
68+
@Before
69+
public void setUp() {
70+
context = new Context();
71+
context.service(PluginService.class);
72+
}
73+
74+
@After
75+
public void tearDown() {
76+
context.dispose();
77+
}
78+
79+
/** Tests single input, no configuration. */
80+
@Test
81+
public void testSingleInput() {
82+
setExpected(new HashMap<String, Object>() {{
83+
put("sigma", 3.9f);
84+
}});
85+
Inputs inputs = new Inputs(context);
86+
inputs.getInfo().setName("testSingleInput");//TEMP
87+
inputs.addInput("sigma", Float.class);
88+
float sigma = (Float) inputs.harvest().get("sigma");
89+
assertEquals(3.9f, sigma, 0);
90+
}
91+
92+
/** Tests two inputs, no configuration. */
93+
@Test
94+
public void testTwoInputs() {
95+
setExpected(new HashMap<String, Object>() {{
96+
put("name", "Chuckles");
97+
put("age", 37);
98+
}});
99+
Inputs inputs = new Inputs(context);
100+
inputs.getInfo().setName("testTwoInputs");//TEMP
101+
inputs.addInput("name", String.class);
102+
inputs.addInput("age", Integer.class);
103+
Map<String, Object> values = inputs.harvest();
104+
String name = (String) values.get("name");
105+
int age = (Integer) values.get("age");
106+
assertEquals("Chuckles", name);
107+
assertEquals(37, age);
108+
}
109+
110+
/** Tests inputs with configuration. */
111+
@Test
112+
public void testWithConfiguration() {
113+
setExpected(new HashMap<String, Object>() {{
114+
put("word", "brown");
115+
put("opacity", 0.8);
116+
}});
117+
Inputs inputs = new Inputs(context);
118+
inputs.getInfo().setName("testWithConfiguration");//TEMP
119+
MutableModuleItem<String> wordInput = inputs.addInput("word", String.class);
120+
wordInput.setLabel("Favorite word");
121+
wordInput.setChoices(Arrays.asList("quick", "brown", "fox"));
122+
wordInput.setDefaultValue("fox");
123+
MutableModuleItem<Double> opacityInput = inputs.addInput("opacity", Double.class);
124+
opacityInput.setMinimumValue(0.0);
125+
opacityInput.setMaximumValue(1.0);
126+
opacityInput.setDefaultValue(0.5);
127+
opacityInput.setWidgetStyle(NumberWidget.SCROLL_BAR_STYLE);
128+
inputs.harvest();
129+
String word = wordInput.getValue(inputs);
130+
double opacity = opacityInput.getValue(inputs);
131+
assertEquals("brown", word);
132+
assertEquals(0.8, opacity, 0);
133+
}
134+
135+
public void setExpected(final Map<String, Object> expected) {
136+
final PluginInfo<PreprocessorPlugin> info =
137+
new PluginInfo<PreprocessorPlugin>(MockInputHarvester.class,
138+
PreprocessorPlugin.class)
139+
{
140+
@Override
141+
public PreprocessorPlugin createInstance() throws InstantiableException {
142+
final PreprocessorPlugin pp = super.createInstance();
143+
((MockInputHarvester) pp).setExpected(expected);
144+
return pp;
145+
}
146+
};
147+
info.setPriority(InputHarvester.PRIORITY);
148+
context.service(PluginService.class).addPlugin(info);
149+
}
150+
151+
public static class MockInputHarvester extends AbstractPreprocessorPlugin {
152+
private Map<String, Object> expected;
153+
public void setExpected(final Map<String, Object> expected) {
154+
this.expected = expected;
155+
}
156+
157+
@Override
158+
public void process(final Module module) {
159+
for (final ModuleItem<?> input : module.getInfo().inputs()) {
160+
if (module.isInputResolved(input.getName())) continue;
161+
final String name = input.getName();
162+
if (!expected.containsKey(name)) {
163+
throw new AssertionError("No value for input: " + input.getName());
164+
}
165+
final Object value = expected.get(name);
166+
module.setInput(name, value);
167+
}
168+
}
169+
}
170+
}

0 commit comments

Comments
 (0)