diff --git a/src/main/java/org/scijava/module/DefaultModuleService.java b/src/main/java/org/scijava/module/DefaultModuleService.java index 26fac0d84..a80515955 100644 --- a/src/main/java/org/scijava/module/DefaultModuleService.java +++ b/src/main/java/org/scijava/module/DefaultModuleService.java @@ -260,27 +260,23 @@ public M waitFor(final Future future) { } @Override - public ModuleItem getSingleInput(final Module module, - final Class type) - { - return getTypedSingleItem(module, type, module.getInfo().inputs()); + public ModuleItem getSingleInput(final Module module, final Class type, boolean acrossTypes) { + return getTypedSingleItem(module, type, module.getInfo().inputs(), acrossTypes); } @Override - public ModuleItem getSingleOutput(final Module module, - final Class type) - { - return getTypedSingleItem(module, type, module.getInfo().outputs()); + public ModuleItem getSingleOutput(final Module module, final Class type, boolean acrossTypes) { + return getTypedSingleItem(module, type, module.getInfo().outputs(), acrossTypes); } @Override - public ModuleItem getSingleInput(Module module, Collection> types) { - return getSingleItem(module, types, module.getInfo().inputs()); + public ModuleItem getSingleInput(Module module, Collection> types, boolean acrossTypes) { + return getSingleItem(module, types, module.getInfo().inputs(), acrossTypes); } @Override - public ModuleItem getSingleOutput(Module module, Collection> types) { - return getSingleItem(module, types, module.getInfo().outputs()); + public ModuleItem getSingleOutput(Module module, Collection> types, boolean acrossTypes) { + return getSingleItem(module, types, module.getInfo().outputs(), acrossTypes); } @Override @@ -478,17 +474,17 @@ private void assignInputs(final Module module, } private ModuleItem getTypedSingleItem(final Module module, - final Class type, final Iterable> items) + final Class type, final Iterable> items, boolean acrossTypes) { Set> types = new HashSet<>(); types.add(type); @SuppressWarnings("unchecked") - ModuleItem result = (ModuleItem) getSingleItem(module, types, items); + ModuleItem result = (ModuleItem) getSingleItem(module, types, items, acrossTypes); return result; } private ModuleItem getSingleItem(final Module module, - final Collection> types, final Iterable> items) + final Collection> types, final Iterable> items, boolean acrossTypes) { ModuleItem result = null; @@ -496,6 +492,7 @@ private ModuleItem getSingleItem(final Module module, final String name = item.getName(); if (!item.isAutoFill()) continue; // skip unfillable inputs if (module.isInputResolved(name)) continue; // skip resolved inputs + if (acrossTypes && result != null) return null; // multiple unresolved items final Class itemType = item.getType(); for (final Class type : types) { if (type.isAssignableFrom(itemType)) { diff --git a/src/main/java/org/scijava/module/ModuleService.java b/src/main/java/org/scijava/module/ModuleService.java index 0767fb2f4..a1930fa8e 100644 --- a/src/main/java/org/scijava/module/ModuleService.java +++ b/src/main/java/org/scijava/module/ModuleService.java @@ -272,26 +272,66 @@ Future run(M module, * given type, returning the relevant {@link ModuleItem} if found, or null if * not exactly one unresolved fillable input of that type. */ - ModuleItem getSingleInput(Module module, Class type); + default ModuleItem getSingleInput(Module module, Class type) { + return getSingleInput(module, type, false); + } + + /** + * Checks the given module for a solitary unresolved fillable input of the + * given type, returning the relevant {@link ModuleItem} if found, or null if + * not exactly one unresolved fillable input. + * + * If {@code acrossTpyes} is true, all inputs independent of type are taken + * into account when checking for singularity of the unresolved input. + */ + ModuleItem getSingleInput(Module module, Class type, boolean acrossTypes); /** * Checks the given module for a solitary unresolved output of the given type, * returning the relevant {@link ModuleItem} if found, or null if not exactly * one unresolved output of that type. */ - ModuleItem getSingleOutput(Module module, Class type); + default ModuleItem getSingleOutput(Module module, Class type) { + return getSingleOutput(module, type, false); + } + + /** + * Checks the given module for a solitary unresolved output of the given type, + * returning the relevant {@link ModuleItem} if found, or null if not exactly + * one unresolved output. + * + * If {@code acrossTpyes} is true, all outputs independent of type are taken + * into account when checking for singularity of the unresolved output. + */ + ModuleItem getSingleOutput(Module module, Class type, boolean acrossTypes); /** * As {@link #getSingleInput(Module, Class)} but will match with a set of * potential classes, at the cost of generic parameter safety. */ - ModuleItem getSingleInput(Module module, Collection> types); + default ModuleItem getSingleInput(Module module, Collection> types) { + return getSingleInput(module, types, false); + } + + /** + * As {@link #getSingleInput(Module, Class, boolean)} but will match with a set of + * potential classes, at the cost of generic parameter safety. + */ + ModuleItem getSingleInput(Module module, Collection> types, boolean acrossTypes); /** * As {@link #getSingleOutput(Module, Class)} but will match with a set of * potential classes, at the cost of generic parameter safety. */ - ModuleItem getSingleOutput(Module module, Collection> types); + default ModuleItem getSingleOutput(Module module, Collection> types) { + return getSingleOutput(module, types, false); + } + + /** + * As {@link #getSingleOutput(Module, Class, boolean)} but will match with a set of + * potential classes, at the cost of generic parameter safety. + */ + ModuleItem getSingleOutput(Module module, Collection> types, boolean acrossTypes); /** * Registers the given value for the given {@link ModuleItem} using the diff --git a/src/main/java/org/scijava/ui/FileListPreprocessor.java b/src/main/java/org/scijava/ui/FileListPreprocessor.java index fbb5cea5b..6ba9aa096 100644 --- a/src/main/java/org/scijava/ui/FileListPreprocessor.java +++ b/src/main/java/org/scijava/ui/FileListPreprocessor.java @@ -32,6 +32,7 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; +import org.scijava.module.ModuleService; import org.scijava.module.process.AbstractPreprocessorPlugin; import org.scijava.module.process.PreprocessorPlugin; import org.scijava.plugin.Parameter; @@ -44,10 +45,13 @@ public class FileListPreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) private UIService uiService; + @Parameter(required = false) + private ModuleService moduleService; + @Override public void process(final Module module) { - if (uiService == null) return; - final ModuleItem fileInput = getFilesInput(module); + if (uiService == null || moduleService == null) return; + final ModuleItem fileInput = moduleService.getSingleInput(module, File[].class, true); if (fileInput == null) return; final File[] files = fileInput.getValue(module); @@ -65,30 +69,4 @@ public void process(final Module module) { module.resolveInput(fileInput.getName()); } - // -- Helper methods -- - - /** - * Gets the single unresolved {@link File} input parameter. If there is not - * exactly one unresolved {@link File} input parameter, or if there are other - * types of unresolved parameters, this method returns null. - */ - private ModuleItem getFilesInput(final Module module) { - ModuleItem result = null; - for (final ModuleItem input : module.getInfo().inputs()) { - if (module.isInputResolved(input.getName())) continue; - final Class type = input.getType(); - if (!File[].class.isAssignableFrom(type)) { - // not a File[] parameter; abort - return null; - } - if (result != null) { - // second File parameter; abort - return null; - } - @SuppressWarnings("unchecked") - final ModuleItem fileInput = (ModuleItem) input; - result = fileInput; - } - return result; - } } diff --git a/src/main/java/org/scijava/ui/FilePreprocessor.java b/src/main/java/org/scijava/ui/FilePreprocessor.java index 02b8d1a04..104929314 100644 --- a/src/main/java/org/scijava/ui/FilePreprocessor.java +++ b/src/main/java/org/scijava/ui/FilePreprocessor.java @@ -33,6 +33,7 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; +import org.scijava.module.ModuleService; import org.scijava.module.process.AbstractPreprocessorPlugin; import org.scijava.module.process.PreprocessorPlugin; import org.scijava.plugin.Parameter; @@ -53,12 +54,15 @@ public class FilePreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) private UIService uiService; + @Parameter(required = false) + private ModuleService moduleService; + // -- ModuleProcessor methods -- @Override public void process(final Module module) { - if (uiService == null) return; - final ModuleItem fileInput = getFileInput(module); + if (uiService == null || moduleService == null) return; + final ModuleItem fileInput = moduleService.getSingleInput(module, File.class, true); if (fileInput == null) return; final File file = fileInput.getValue(module); @@ -75,31 +79,4 @@ public void process(final Module module) { module.resolveInput(fileInput.getName()); } - // -- Helper methods -- - - /** - * Gets the single unresolved {@link File} input parameter. If there is not - * exactly one unresolved {@link File} input parameter, or if there are other - * types of unresolved parameters, this method returns null. - */ - private ModuleItem getFileInput(final Module module) { - ModuleItem result = null; - for (final ModuleItem input : module.getInfo().inputs()) { - if (module.isInputResolved(input.getName())) continue; - final Class type = input.getType(); - if (!File.class.isAssignableFrom(type)) { - // not a File parameter; abort - return null; - } - if (result != null) { - // second File parameter; abort - return null; - } - @SuppressWarnings("unchecked") - final ModuleItem fileInput = (ModuleItem) input; - result = fileInput; - } - return result; - } - } diff --git a/src/test/java/org/scijava/ui/FileListPreprocessorTest.java b/src/test/java/org/scijava/ui/FileListPreprocessorTest.java new file mode 100644 index 000000000..7458160cd --- /dev/null +++ b/src/test/java/org/scijava/ui/FileListPreprocessorTest.java @@ -0,0 +1,109 @@ +package org.scijava.ui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.io.FileFilter; +import java.util.concurrent.ExecutionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.command.Command; +import org.scijava.command.CommandInfo; +import org.scijava.module.Module; +import org.scijava.module.ModuleService; +import org.scijava.plugin.Parameter; +import org.scijava.ui.headless.HeadlessUI; + +public class FileListPreprocessorTest { + + public static final String DUMMY_FILE_NAME = "dummy_file"; + private Context context; + private ModuleService moduleService; + + @Before + public void setUp() { + context = new Context(); + context.service(UIService.class).setDefaultUI(new CustomUI()); + context.service(UIService.class).setHeadless(false); + moduleService = context.service(ModuleService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + @Test + public void test() throws InterruptedException, ExecutionException { + // assert that single File[] parameters get filled by ui.chooseFiles() + CommandInfo info = new CommandInfo(SingleParameterFileListCommand.class); + Module mod = moduleService.run(info, true).get(); + assertNotNull(mod); + assertEquals(DUMMY_FILE_NAME, ((File[])mod.getInput("inputFiles"))[0].getName()); + + // assert that the presence of any other unresolved parameters prevents + // calls to ui.chooseFiles() + CommandInfo info2 = new CommandInfo(ParameterFileListAndOthersCommand.class); + Module mod2 = moduleService.run(info2, true).get(); + assertNotNull(mod2); + assertNull(mod2.getInput("inputFiles")); + + // assert that 'autoFill = false' prevents calls to ui.chooseFiles() + CommandInfo info3 = new CommandInfo(NoAutofillParameterFileListCommand.class); + Module mod3 = moduleService.run(info3, true).get(); + assertNotNull(mod3); + assertNull(mod3.getInput("inputFiles")); + } + + private class CustomUI extends HeadlessUI { + + @Override + public File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) { + return new File[] { new File(DUMMY_FILE_NAME) }; + } + } + + public static class SingleParameterFileListCommand implements Command { + + @Parameter(persist = false) + private File[] inputFiles; + + @Override + public void run() { + // do nothing + } + + } + + public static class ParameterFileListAndOthersCommand implements Command { + + @Parameter(persist = false) + private File[] inputFiles; + + @Parameter(required = false) + private String dummyString; + + @Override + public void run() { + // do nothing + } + + } + + public static class NoAutofillParameterFileListCommand implements Command { + + @Parameter(autoFill = false, persist = false) + private File[] inputFiles; + + @Override + public void run() { + // do nothing + } + + } +} diff --git a/src/test/java/org/scijava/ui/FilePreprocessorTest.java b/src/test/java/org/scijava/ui/FilePreprocessorTest.java new file mode 100644 index 000000000..63c582385 --- /dev/null +++ b/src/test/java/org/scijava/ui/FilePreprocessorTest.java @@ -0,0 +1,108 @@ +package org.scijava.ui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.util.concurrent.ExecutionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.command.Command; +import org.scijava.command.CommandInfo; +import org.scijava.module.Module; +import org.scijava.module.ModuleService; +import org.scijava.plugin.Parameter; +import org.scijava.ui.headless.HeadlessUI; + +public class FilePreprocessorTest { + + public static final String DUMMY_FILE_NAME = "dummy_file"; + private Context context; + private ModuleService moduleService; + + @Before + public void setUp() { + context = new Context(); + context.service(UIService.class).setDefaultUI(new CustomUI()); + context.service(UIService.class).setHeadless(false); + moduleService = context.service(ModuleService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + @Test + public void test() throws InterruptedException, ExecutionException { + // assert that single File parameters get filled by ui.chooseFile() + CommandInfo info = new CommandInfo(SingleParameterFileCommand.class); + Module mod = moduleService.run(info, true).get(); + assertNotNull(mod); + assertEquals(DUMMY_FILE_NAME, ((File)mod.getInput("inputFile")).getName()); + + // assert that the presence of any other unresolved parameters prevents + // calls to ui.chooseFile() + CommandInfo info2 = new CommandInfo(ParameterFileAndOthersCommand.class); + Module mod2 = moduleService.run(info2, true).get(); + assertNotNull(mod2); + assertNull(mod2.getInput("inputFile")); + + // assert that 'autoFill = false' prevents calls to ui.chooseFile() + CommandInfo info3 = new CommandInfo(NoAutofillParameterFileCommand.class); + Module mod3 = moduleService.run(info3, true).get(); + assertNotNull(mod3); + assertNull(mod3.getInput("inputFile")); + } + + private class CustomUI extends HeadlessUI { + + @Override + public File chooseFile(String title, File file, String style) { + return new File(DUMMY_FILE_NAME); + } + } + + public static class SingleParameterFileCommand implements Command { + + @Parameter(persist = false) + private File inputFile; + + @Override + public void run() { + // do nothing + } + + } + + public static class ParameterFileAndOthersCommand implements Command { + + @Parameter(persist = false) + private File inputFile; + + @Parameter(required = false) + private String dummyString; + + @Override + public void run() { + // do nothing + } + + } + + public static class NoAutofillParameterFileCommand implements Command { + + @Parameter(autoFill = false, persist = false) + private File inputFile; + + @Override + public void run() { + // do nothing + } + + } +}