diff --git a/pom.xml b/pom.xml
index 91b5b993f..8369ce5ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.scijava
pom-scijava
- 13.1.0
+ 17.0.0
diff --git a/src/main/java/org/scijava/convert/FileListConverters.java b/src/main/java/org/scijava/convert/FileListConverters.java
new file mode 100644
index 000000000..386279774
--- /dev/null
+++ b/src/main/java/org/scijava/convert/FileListConverters.java
@@ -0,0 +1,162 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2017 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
+ * Institute of Molecular Cell Biology and Genetics, University of
+ * Konstanz, and KNIME GmbH.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.scijava.convert;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.scijava.Priority;
+import org.scijava.plugin.Plugin;
+import org.scijava.util.StringUtils;
+
+/**
+ * A collection of {@link Converter} plugins for going between {@link String},
+ * {@link File} and {@code File[]}.
+ *
+ * @author Jan Eglinger
+ * @author Curtis Rueden
+ */
+public class FileListConverters {
+ // -- String to File (list) converters --
+
+ @Plugin(type = Converter.class, priority = Priority.NORMAL)
+ public static class StringToFileConverter extends
+ AbstractConverter
+ {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T convert(final Object src, final Class dest) {
+ return (T) new File((String) src);
+ }
+
+ @Override
+ public Class getOutputType() {
+ return File.class;
+ }
+
+ @Override
+ public Class getInputType() {
+ return String.class;
+ }
+
+ }
+
+ @Plugin(type = Converter.class, priority = Priority.NORMAL)
+ public static class StringToFileArrayConverter extends
+ AbstractConverter
+ {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T convert(final Object src, final Class dest) {
+ final String[] tokens = StringUtils.splitUnquoted((String) src, ",");
+ final List fileList = new ArrayList<>();
+ for (final String filePath : tokens) {
+ fileList.add(new File(filePath.replaceAll("^\"|\"$", "")));
+ }
+ return (T) fileList.toArray(new File[fileList.size()]);
+ }
+
+ @Override
+ public Class getOutputType() {
+ return File[].class;
+ }
+
+ @Override
+ public Class getInputType() {
+ return String.class;
+ }
+
+ }
+
+ // TODO add StringToFileListConverter
+
+ // -- File (list) to String converters --
+
+ @Plugin(type = Converter.class, priority = Priority.NORMAL)
+ public static class FileToStringConverter extends
+ AbstractConverter
+ {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T convert(final Object src, final Class dest) {
+ return (T) ((File) src).getAbsolutePath();
+ }
+
+ @Override
+ public Class getOutputType() {
+ return String.class;
+ }
+
+ @Override
+ public Class getInputType() {
+ return File.class;
+ }
+
+ }
+
+ @Plugin(type = Converter.class, priority = Priority.NORMAL)
+ public static class FileArrayToStringConverter extends
+ AbstractConverter
+ {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T convert(final Object src, final Class dest) {
+ final List result = Arrays.asList((File[]) src).stream().map(
+ f -> {
+ final String path = f.getAbsolutePath();
+ return path.contains(",") ? "\"" + path + "\"" : path;
+ }).collect(Collectors.toList());
+ return (T) String.join(",", result);
+ }
+
+ @Override
+ public Class getOutputType() {
+ return String.class;
+ }
+
+ @Override
+ public Class getInputType() {
+ return File[].class;
+ }
+
+ }
+
+ // TODO add FileListToStringConverter
+}
diff --git a/src/main/java/org/scijava/module/DefaultModuleService.java b/src/main/java/org/scijava/module/DefaultModuleService.java
index 2bcf13d24..bdad241f5 100644
--- a/src/main/java/org/scijava/module/DefaultModuleService.java
+++ b/src/main/java/org/scijava/module/DefaultModuleService.java
@@ -297,9 +297,7 @@ public void save(final ModuleItem item, final T value) {
return;
}
- // FIXME: Convert to string, instead of just calling toString.
- // Otherwise many things (e.g. File[]) are persisted improperly.
- final String sValue = value == null ? "" : value.toString();
+ final String sValue = value == null ? "" : convertService.convert(value, String.class);
// do not persist if object cannot be converted back from a string
if (!convertService.supports(sValue, item.getType())) return;
diff --git a/src/main/java/org/scijava/ui/DefaultUIService.java b/src/main/java/org/scijava/ui/DefaultUIService.java
index 76aae7e50..e88f94e55 100644
--- a/src/main/java/org/scijava/ui/DefaultUIService.java
+++ b/src/main/java/org/scijava/ui/DefaultUIService.java
@@ -321,15 +321,15 @@ public File chooseFile(final File file, final String style) {
}
@Override
- public File[] chooseFiles(File[] files, FileFilter filter) {
+ public File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) {
final UserInterface ui = getDefaultUI();
- return ui == null ? null : ui.chooseFiles(files, filter);
+ return ui == null ? null : ui.chooseFiles(parent, files, filter, style);
}
@Override
- public List chooseFiles(List fileList, FileFilter filter) {
+ public List chooseFiles(File parent, List fileList, FileFilter filter, String style) {
final UserInterface ui = getDefaultUI();
- return ui == null ? null : ui.chooseFiles(fileList, filter);
+ return ui == null ? null : ui.chooseFiles(parent, fileList, filter, style);
}
@Override
diff --git a/src/main/java/org/scijava/ui/FileListPreprocessor.java b/src/main/java/org/scijava/ui/FileListPreprocessor.java
index 6cc5ae83f..6a8971898 100644
--- a/src/main/java/org/scijava/ui/FileListPreprocessor.java
+++ b/src/main/java/org/scijava/ui/FileListPreprocessor.java
@@ -56,7 +56,9 @@ public void process(final Module module) {
final File[] files = fileInput.getValue(module);
// show file chooser dialog box
- final File[] result = uiService.chooseFiles(files, null);
+ // TODO decide how to create filter from style attributes
+ // TODO retrieve parent folder??
+ final File[] result = uiService.chooseFiles(null, files, null, fileInput.getWidgetStyle());
if (result == null) {
cancel("");
return;
diff --git a/src/main/java/org/scijava/ui/UIService.java b/src/main/java/org/scijava/ui/UIService.java
index 7191679dd..9b711bd05 100644
--- a/src/main/java/org/scijava/ui/UIService.java
+++ b/src/main/java/org/scijava/ui/UIService.java
@@ -304,7 +304,7 @@ DialogPrompt.Result showDialog(String message, String title,
* @param files The initial value displayed in the file chooser prompt.
* @param filter A filter allowing to restrict the choice of files
*/
- File[] chooseFiles(File[] files, FileFilter filter);
+ File[] chooseFiles(File parent, File[] files, FileFilter filter, String style);
/**
* Prompts the user to select one or multiple files.
@@ -315,7 +315,7 @@ DialogPrompt.Result showDialog(String message, String title,
* @param fileList The initial value displayed in the file chooser prompt.
* @param filter A filter allowing to restrict the choice of files
*/
- List chooseFiles(List fileList, FileFilter filter);
+ List chooseFiles(File parent, List fileList, FileFilter filter, String style);
/**
* Displays a popup context menu for the given display at the specified
diff --git a/src/main/java/org/scijava/ui/UserInterface.java b/src/main/java/org/scijava/ui/UserInterface.java
index 544991cd5..385df24d8 100644
--- a/src/main/java/org/scijava/ui/UserInterface.java
+++ b/src/main/java/org/scijava/ui/UserInterface.java
@@ -191,26 +191,30 @@ default File chooseFile(String title, File file, String style) {
/**
* Prompts the user to choose a list of files.
*
+ * @param parent Parent folder for file selection
* @param files The initial value displayed in the file chooser prompt.
* @param filter A filter allowing to restrict file choice.
+ * @param style File selection style (files, directories, or both) and optional filters
* @return The selected {@link File}s chosen by the user, or null if the
* user cancels the prompt.
*/
- default File[] chooseFiles(File[] files, FileFilter filter) {
+ default File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) {
throw new UnsupportedOperationException();
}
/**
* Prompts the user to choose a list of files.
*
+ * @param parent Parent folder for file selection
* @param fileList The initial value displayed in the file chooser prompt.
* @param filter A filter allowing to restrict file choice.
+ * @param style File selection style (files, directories, or both) and optional filters
* @return The selected {@link File}s chosen by the user, or null if the
* user cancels the prompt.
*/
- default List chooseFiles(List fileList, FileFilter filter) {
+ default List chooseFiles(File parent, List fileList, FileFilter filter, String style) {
final File[] initialFiles = fileList.toArray(new File[fileList.size()]);
- final File[] chosenFiles = chooseFiles(initialFiles, filter);
+ final File[] chosenFiles = chooseFiles(parent, initialFiles, filter, style);
return chosenFiles == null ? null : Arrays.asList(chosenFiles);
}
diff --git a/src/main/java/org/scijava/util/StringUtils.java b/src/main/java/org/scijava/util/StringUtils.java
index 9a09e5939..c2155e277 100644
--- a/src/main/java/org/scijava/util/StringUtils.java
+++ b/src/main/java/org/scijava/util/StringUtils.java
@@ -63,6 +63,7 @@
import java.io.File;
import java.text.DecimalFormatSymbols;
+import java.util.regex.Pattern;
/**
* Useful methods for working with {@link String}s.
@@ -80,6 +81,16 @@ private StringUtils() {
// NB: prevent instantiation of utility class.
}
+ /**
+ * Splits a string only at separators outside of quotation marks ({@code "}).
+ * Does not handle escaped quotes.
+ */
+ public static String[] splitUnquoted(final String s, final String separator) {
+ // See https://stackoverflow.com/a/1757107/1919049
+ return s.split(Pattern.quote(separator) +
+ "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
+ }
+
/** Normalizes the decimal separator for the user's locale. */
public static String sanitizeDouble(String value) {
value = value.replaceAll("[^0-9,\\.]", "");
diff --git a/src/main/java/org/scijava/widget/FileListWidget.java b/src/main/java/org/scijava/widget/FileListWidget.java
index d76c3a1fe..b7f2203a5 100644
--- a/src/main/java/org/scijava/widget/FileListWidget.java
+++ b/src/main/java/org/scijava/widget/FileListWidget.java
@@ -34,5 +34,24 @@
import java.io.File;
public interface FileListWidget extends InputWidget {
- // NB: No changes to interface.
+ /**
+ * Widget style to allow file selection only
+ *
+ * @see org.scijava.plugin.Parameter#style()
+ */
+ String FILES_ONLY = "files";
+
+ /**
+ * Widget style to allow directory selection only
+ *
+ * @see org.scijava.plugin.Parameter#style()
+ */
+ String DIRECTORIES_ONLY = "directories";
+
+ /**
+ * Widget style to allow selection of both files and directories
+ *
+ * @see org.scijava.plugin.Parameter#style()
+ */
+ String FILES_AND_DIRECTORIES = "both";
}
diff --git a/src/test/java/org/scijava/convert/FileListConverterTest.java b/src/test/java/org/scijava/convert/FileListConverterTest.java
new file mode 100644
index 000000000..ed3610e11
--- /dev/null
+++ b/src/test/java/org/scijava/convert/FileListConverterTest.java
@@ -0,0 +1,103 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2017 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
+ * Institute of Molecular Cell Biology and Genetics, University of
+ * Konstanz, and KNIME GmbH.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.scijava.convert;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+
+import org.junit.Test;
+import org.scijava.convert.FileListConverters.FileArrayToStringConverter;
+import org.scijava.convert.FileListConverters.FileToStringConverter;
+import org.scijava.convert.FileListConverters.StringToFileArrayConverter;
+import org.scijava.convert.FileListConverters.StringToFileConverter;
+
+public class FileListConverterTest {
+
+ @Test
+ public void testStringToFileConverter() {
+ final StringToFileConverter conv = new StringToFileConverter();
+ final String path = "C:\\temp\\f,i;l-ename.txt";
+ assertTrue("Cannot convert from String to File",
+ conv.canConvert(String.class, File.class));
+ assertFalse("Can erroneously convert from String to File[]",
+ conv.canConvert(String.class, File[].class));
+ assertEquals(new File(path),
+ conv.convert(path, File.class));
+ }
+
+ @Test
+ public void testStringToFileArrayConverter() {
+ final StringToFileArrayConverter conv = new StringToFileArrayConverter();
+ final String path = "\"C:\\temp\\f,i;l-ename.txt\",C:\\temp";
+ assertTrue("Cannot convert from String to File[]",
+ conv.canConvert(String.class, File[].class));
+ assertFalse("Can erroneously convert from String to File",
+ conv.canConvert(String.class, File.class));
+ assertEquals("Wrong array length", 2,
+ conv.convert(path, File[].class).length);
+ assertEquals("Wrong file name", new File("C:\\temp\\f,i;l-ename.txt"),
+ conv.convert(path, File[].class)[0]);
+ assertEquals("Wrong file name", new File("C:\\temp"),
+ conv.convert(path, File[].class)[1]);
+ }
+
+ @Test
+ public void testFileToStringConverter() {
+ final FileToStringConverter conv = new FileToStringConverter();
+ final File file = new File("C:\\temp\\f,i;l-ename.txt");
+ assertTrue("Cannot convert from File to String",
+ conv.canConvert(File.class, String.class));
+ assertFalse("Can erroneously convert from File[] to String",
+ conv.canConvert(File[].class, String.class));
+ assertEquals(file.getAbsolutePath(),
+ conv.convert(file, String.class));
+ }
+
+ @Test
+ public void testFileArrayToStringConverter() {
+ final FileArrayToStringConverter conv = new FileArrayToStringConverter();
+ final File[] fileArray = new File[2];
+ fileArray[0] = new File("C:\\temp\\f,i;l-ename.txt");
+ fileArray[1] = new File("C:\\temp");
+ final String expected = "\"" + fileArray[0].getAbsolutePath() + "\"," + fileArray[1].getAbsolutePath();
+ assertTrue("Cannot convert from File[] to String",
+ conv.canConvert(File[].class, String.class));
+ assertFalse("Can erroneously convert from File to String",
+ conv.canConvert(File.class, String.class));
+ assertEquals("Wrong output string", expected,
+ conv.convert(fileArray, String.class));
+ }
+}
diff --git a/src/test/java/org/scijava/util/StringUtilsTest.java b/src/test/java/org/scijava/util/StringUtilsTest.java
index 88089986b..9559fd83c 100644
--- a/src/test/java/org/scijava/util/StringUtilsTest.java
+++ b/src/test/java/org/scijava/util/StringUtilsTest.java
@@ -32,6 +32,7 @@
package org.scijava.util;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -47,6 +48,21 @@
*/
public class StringUtilsTest {
+ /** Tests {@link StringUtils#splitUnquoted}. */
+ @Test
+ public void testSplitUnquoted() {
+ // See https://stackoverflow.com/a/1757107/1919049
+ final String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
+ final String[] expected = {
+ "foo",
+ "bar",
+ "c;qual=\"baz,blurb\"",
+ "d;junk=\"quux,syzygy\""
+ };
+ final String[] actual = StringUtils.splitUnquoted(line, ",");
+ assertArrayEquals(expected, actual);
+ }
+
@Test
public void isNullOrEmptyFalseIfString() throws Exception {
assertFalse(StringUtils.isNullOrEmpty("Fresh out of Red Leicester"));