Skip to content

Commit 4168b3e

Browse files
committed
Avoid calling chooseFiles for non-autofill inputs
This commit changes the API of ModuleService and DefaultModuleService to include an optional parameter 'acrossTypes' that allows checking for solitary unresolved parameters within all inputs/outputs independent of their type, while maintaining the backwards compatible method signatures. We can then replace some duplicated logic in helper methods of FilePreprocessor and FileListPreprocessor by a call to 'moduleService.getSingleInput()', which makes them respect an 'autoFill=false' parameter annotation and avoid calling uiService.chooseFile/chooseFiles for those parameters. The intended behavior is verified by two new tests that, before this commit, are failing on their last assertion.
1 parent beb6de2 commit 4168b3e

File tree

6 files changed

+285
-76
lines changed

6 files changed

+285
-76
lines changed

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

+12-15
Original file line numberDiff line numberDiff line change
@@ -260,27 +260,23 @@ public <M extends Module> M waitFor(final Future<M> future) {
260260
}
261261

262262
@Override
263-
public <T> ModuleItem<T> getSingleInput(final Module module,
264-
final Class<T> type)
265-
{
266-
return getTypedSingleItem(module, type, module.getInfo().inputs());
263+
public <T> ModuleItem<T> getSingleInput(final Module module, final Class<T> type, boolean acrossTypes) {
264+
return getTypedSingleItem(module, type, module.getInfo().inputs(), acrossTypes);
267265
}
268266

269267
@Override
270-
public <T> ModuleItem<T> getSingleOutput(final Module module,
271-
final Class<T> type)
272-
{
273-
return getTypedSingleItem(module, type, module.getInfo().outputs());
268+
public <T> ModuleItem<T> getSingleOutput(final Module module, final Class<T> type, boolean acrossTypes) {
269+
return getTypedSingleItem(module, type, module.getInfo().outputs(), acrossTypes);
274270
}
275271

276272
@Override
277-
public ModuleItem<?> getSingleInput(Module module, Collection<Class<?>> types) {
278-
return getSingleItem(module, types, module.getInfo().inputs());
273+
public ModuleItem<?> getSingleInput(Module module, Collection<Class<?>> types, boolean acrossTypes) {
274+
return getSingleItem(module, types, module.getInfo().inputs(), acrossTypes);
279275
}
280276

281277
@Override
282-
public ModuleItem<?> getSingleOutput(Module module, Collection<Class<?>> types) {
283-
return getSingleItem(module, types, module.getInfo().outputs());
278+
public ModuleItem<?> getSingleOutput(Module module, Collection<Class<?>> types, boolean acrossTypes) {
279+
return getSingleItem(module, types, module.getInfo().outputs(), acrossTypes);
284280
}
285281

286282
@Override
@@ -478,24 +474,25 @@ private void assignInputs(final Module module,
478474
}
479475

480476
private <T> ModuleItem<T> getTypedSingleItem(final Module module,
481-
final Class<T> type, final Iterable<ModuleItem<?>> items)
477+
final Class<T> type, final Iterable<ModuleItem<?>> items, boolean acrossTypes)
482478
{
483479
Set<Class<?>> types = new HashSet<>();
484480
types.add(type);
485481
@SuppressWarnings("unchecked")
486-
ModuleItem<T> result = (ModuleItem<T>) getSingleItem(module, types, items);
482+
ModuleItem<T> result = (ModuleItem<T>) getSingleItem(module, types, items, acrossTypes);
487483
return result;
488484
}
489485

490486
private ModuleItem<?> getSingleItem(final Module module,
491-
final Collection<Class<?>> types, final Iterable<ModuleItem<?>> items)
487+
final Collection<Class<?>> types, final Iterable<ModuleItem<?>> items, boolean acrossTypes)
492488
{
493489
ModuleItem<?> result = null;
494490

495491
for (final ModuleItem<?> item : items) {
496492
final String name = item.getName();
497493
if (!item.isAutoFill()) continue; // skip unfillable inputs
498494
if (module.isInputResolved(name)) continue; // skip resolved inputs
495+
if (acrossTypes && result != null) return null; // multiple unresolved items
499496
final Class<?> itemType = item.getType();
500497
for (final Class<?> type : types) {
501498
if (type.isAssignableFrom(itemType)) {

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

+44-4
Original file line numberDiff line numberDiff line change
@@ -272,26 +272,66 @@ <M extends Module> Future<M> run(M module,
272272
* given type, returning the relevant {@link ModuleItem} if found, or null if
273273
* not exactly one unresolved fillable input of that type.
274274
*/
275-
<T> ModuleItem<T> getSingleInput(Module module, Class<T> type);
275+
default <T> ModuleItem<T> getSingleInput(Module module, Class<T> type) {
276+
return getSingleInput(module, type, false);
277+
}
278+
279+
/**
280+
* Checks the given module for a solitary unresolved fillable input of the
281+
* given type, returning the relevant {@link ModuleItem} if found, or null if
282+
* not exactly one unresolved fillable input.
283+
*
284+
* If {@code acrossTpyes} is true, all inputs independent of type are taken
285+
* into account when checking for singularity of the unresolved input.
286+
*/
287+
<T> ModuleItem<T> getSingleInput(Module module, Class<T> type, boolean acrossTypes);
276288

277289
/**
278290
* Checks the given module for a solitary unresolved output of the given type,
279291
* returning the relevant {@link ModuleItem} if found, or null if not exactly
280292
* one unresolved output of that type.
281293
*/
282-
<T> ModuleItem<T> getSingleOutput(Module module, Class<T> type);
294+
default <T> ModuleItem<T> getSingleOutput(Module module, Class<T> type) {
295+
return getSingleOutput(module, type, false);
296+
}
297+
298+
/**
299+
* Checks the given module for a solitary unresolved output of the given type,
300+
* returning the relevant {@link ModuleItem} if found, or null if not exactly
301+
* one unresolved output.
302+
*
303+
* If {@code acrossTpyes} is true, all outputs independent of type are taken
304+
* into account when checking for singularity of the unresolved output.
305+
*/
306+
<T> ModuleItem<T> getSingleOutput(Module module, Class<T> type, boolean acrossTypes);
283307

284308
/**
285309
* As {@link #getSingleInput(Module, Class)} but will match with a set of
286310
* potential classes, at the cost of generic parameter safety.
287311
*/
288-
ModuleItem<?> getSingleInput(Module module, Collection<Class<?>> types);
312+
default ModuleItem<?> getSingleInput(Module module, Collection<Class<?>> types) {
313+
return getSingleInput(module, types, false);
314+
}
315+
316+
/**
317+
* As {@link #getSingleInput(Module, Class, boolean)} but will match with a set of
318+
* potential classes, at the cost of generic parameter safety.
319+
*/
320+
ModuleItem<?> getSingleInput(Module module, Collection<Class<?>> types, boolean acrossTypes);
289321

290322
/**
291323
* As {@link #getSingleOutput(Module, Class)} but will match with a set of
292324
* potential classes, at the cost of generic parameter safety.
293325
*/
294-
ModuleItem<?> getSingleOutput(Module module, Collection<Class<?>> types);
326+
default ModuleItem<?> getSingleOutput(Module module, Collection<Class<?>> types) {
327+
return getSingleOutput(module, types, false);
328+
}
329+
330+
/**
331+
* As {@link #getSingleOutput(Module, Class, boolean)} but will match with a set of
332+
* potential classes, at the cost of generic parameter safety.
333+
*/
334+
ModuleItem<?> getSingleOutput(Module module, Collection<Class<?>> types, boolean acrossTypes);
295335

296336
/**
297337
* Registers the given value for the given {@link ModuleItem} using the

src/main/java/org/scijava/ui/FileListPreprocessor.java

+6-28
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import org.scijava.module.Module;
3434
import org.scijava.module.ModuleItem;
35+
import org.scijava.module.ModuleService;
3536
import org.scijava.module.process.AbstractPreprocessorPlugin;
3637
import org.scijava.module.process.PreprocessorPlugin;
3738
import org.scijava.plugin.Parameter;
@@ -44,10 +45,13 @@ public class FileListPreprocessor extends AbstractPreprocessorPlugin {
4445
@Parameter(required = false)
4546
private UIService uiService;
4647

48+
@Parameter(required = false)
49+
private ModuleService moduleService;
50+
4751
@Override
4852
public void process(final Module module) {
49-
if (uiService == null) return;
50-
final ModuleItem<File[]> fileInput = getFilesInput(module);
53+
if (uiService == null || moduleService == null) return;
54+
final ModuleItem<File[]> fileInput = moduleService.getSingleInput(module, File[].class, true);
5155
if (fileInput == null) return;
5256

5357
final File[] files = fileInput.getValue(module);
@@ -65,30 +69,4 @@ public void process(final Module module) {
6569
module.resolveInput(fileInput.getName());
6670
}
6771

68-
// -- Helper methods --
69-
70-
/**
71-
* Gets the single unresolved {@link File} input parameter. If there is not
72-
* exactly one unresolved {@link File} input parameter, or if there are other
73-
* types of unresolved parameters, this method returns null.
74-
*/
75-
private ModuleItem<File[]> getFilesInput(final Module module) {
76-
ModuleItem<File[]> result = null;
77-
for (final ModuleItem<?> input : module.getInfo().inputs()) {
78-
if (module.isInputResolved(input.getName())) continue;
79-
final Class<?> type = input.getType();
80-
if (!File[].class.isAssignableFrom(type)) {
81-
// not a File[] parameter; abort
82-
return null;
83-
}
84-
if (result != null) {
85-
// second File parameter; abort
86-
return null;
87-
}
88-
@SuppressWarnings("unchecked")
89-
final ModuleItem<File[]> fileInput = (ModuleItem<File[]>) input;
90-
result = fileInput;
91-
}
92-
return result;
93-
}
9472
}

src/main/java/org/scijava/ui/FilePreprocessor.java

+6-29
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import org.scijava.module.Module;
3535
import org.scijava.module.ModuleItem;
36+
import org.scijava.module.ModuleService;
3637
import org.scijava.module.process.AbstractPreprocessorPlugin;
3738
import org.scijava.module.process.PreprocessorPlugin;
3839
import org.scijava.plugin.Parameter;
@@ -53,12 +54,15 @@ public class FilePreprocessor extends AbstractPreprocessorPlugin {
5354
@Parameter(required = false)
5455
private UIService uiService;
5556

57+
@Parameter(required = false)
58+
private ModuleService moduleService;
59+
5660
// -- ModuleProcessor methods --
5761

5862
@Override
5963
public void process(final Module module) {
60-
if (uiService == null) return;
61-
final ModuleItem<File> fileInput = getFileInput(module);
64+
if (uiService == null || moduleService == null) return;
65+
final ModuleItem<File> fileInput = moduleService.getSingleInput(module, File.class, true);
6266
if (fileInput == null) return;
6367

6468
final File file = fileInput.getValue(module);
@@ -75,31 +79,4 @@ public void process(final Module module) {
7579
module.resolveInput(fileInput.getName());
7680
}
7781

78-
// -- Helper methods --
79-
80-
/**
81-
* Gets the single unresolved {@link File} input parameter. If there is not
82-
* exactly one unresolved {@link File} input parameter, or if there are other
83-
* types of unresolved parameters, this method returns null.
84-
*/
85-
private ModuleItem<File> getFileInput(final Module module) {
86-
ModuleItem<File> result = null;
87-
for (final ModuleItem<?> input : module.getInfo().inputs()) {
88-
if (module.isInputResolved(input.getName())) continue;
89-
final Class<?> type = input.getType();
90-
if (!File.class.isAssignableFrom(type)) {
91-
// not a File parameter; abort
92-
return null;
93-
}
94-
if (result != null) {
95-
// second File parameter; abort
96-
return null;
97-
}
98-
@SuppressWarnings("unchecked")
99-
final ModuleItem<File> fileInput = (ModuleItem<File>) input;
100-
result = fileInput;
101-
}
102-
return result;
103-
}
104-
10582
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package org.scijava.ui;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertNull;
6+
7+
import java.io.File;
8+
import java.io.FileFilter;
9+
import java.util.concurrent.ExecutionException;
10+
11+
import org.junit.After;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
import org.scijava.Context;
15+
import org.scijava.command.Command;
16+
import org.scijava.command.CommandInfo;
17+
import org.scijava.module.Module;
18+
import org.scijava.module.ModuleService;
19+
import org.scijava.plugin.Parameter;
20+
import org.scijava.ui.headless.HeadlessUI;
21+
22+
public class FileListPreprocessorTest {
23+
24+
public static final String DUMMY_FILE_NAME = "dummy_file";
25+
private Context context;
26+
private ModuleService moduleService;
27+
28+
@Before
29+
public void setUp() {
30+
context = new Context();
31+
context.service(UIService.class).setDefaultUI(new CustomUI());
32+
context.service(UIService.class).setHeadless(false);
33+
moduleService = context.service(ModuleService.class);
34+
}
35+
36+
@After
37+
public void tearDown() {
38+
context.dispose();
39+
}
40+
41+
@Test
42+
public void test() throws InterruptedException, ExecutionException {
43+
// assert that single File[] parameters get filled by ui.chooseFiles()
44+
CommandInfo info = new CommandInfo(SingleParameterFileListCommand.class);
45+
Module mod = moduleService.run(info, true).get();
46+
assertNotNull(mod);
47+
assertEquals(DUMMY_FILE_NAME, ((File[])mod.getInput("inputFiles"))[0].getName());
48+
49+
// assert that the presence of any other unresolved parameters prevents
50+
// calls to ui.chooseFiles()
51+
CommandInfo info2 = new CommandInfo(ParameterFileListAndOthersCommand.class);
52+
Module mod2 = moduleService.run(info2, true).get();
53+
assertNotNull(mod2);
54+
assertNull(mod2.getInput("inputFiles"));
55+
56+
// assert that 'autoFill = false' prevents calls to ui.chooseFiles()
57+
CommandInfo info3 = new CommandInfo(NoAutofillParameterFileListCommand.class);
58+
Module mod3 = moduleService.run(info3, true).get();
59+
assertNotNull(mod3);
60+
assertNull(mod3.getInput("inputFiles"));
61+
}
62+
63+
private class CustomUI extends HeadlessUI {
64+
65+
@Override
66+
public File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) {
67+
return new File[] { new File(DUMMY_FILE_NAME) };
68+
}
69+
}
70+
71+
public static class SingleParameterFileListCommand implements Command {
72+
73+
@Parameter(persist = false)
74+
private File[] inputFiles;
75+
76+
@Override
77+
public void run() {
78+
// do nothing
79+
}
80+
81+
}
82+
83+
public static class ParameterFileListAndOthersCommand implements Command {
84+
85+
@Parameter(persist = false)
86+
private File[] inputFiles;
87+
88+
@Parameter(required = false)
89+
private String dummyString;
90+
91+
@Override
92+
public void run() {
93+
// do nothing
94+
}
95+
96+
}
97+
98+
public static class NoAutofillParameterFileListCommand implements Command {
99+
100+
@Parameter(autoFill = false, persist = false)
101+
private File[] inputFiles;
102+
103+
@Override
104+
public void run() {
105+
// do nothing
106+
}
107+
108+
}
109+
}

0 commit comments

Comments
 (0)