From b7285c5f807891097b5405b270952fcd32f2253c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sat, 7 Jun 2014 13:19:45 -0500 Subject: [PATCH] Use Location, not String, in the I/O API This is much more elegant. It is also a very breaking change. Co-authored-by: Deborah Schmidt --- .../java/org/scijava/io/AbstractIOPlugin.java | 41 ++++++++++++- .../java/org/scijava/io/DefaultIOService.java | 30 +++++++++- .../scijava/io/DefaultRecentFileService.java | 7 ++- src/main/java/org/scijava/io/IOPlugin.java | 34 +++++++++-- src/main/java/org/scijava/io/IOService.java | 59 ++++++++++++++++--- .../org/scijava/io/event/DataOpenedEvent.java | 16 ++--- .../org/scijava/io/event/DataSavedEvent.java | 12 ++-- .../java/org/scijava/io/event/IOEvent.java | 18 +++--- .../org/scijava/script/io/ScriptIOPlugin.java | 11 +++- .../org/scijava/text/io/TextIOPlugin.java | 15 +++-- .../ui/dnd/FileDragAndDropHandler.java | 9 +-- .../java/org/scijava/io/DummyTextFormat.java | 22 +++++++ .../java/org/scijava/io/IOServiceTest.java | 38 ++++++++++++ src/test/resources/org/scijava/io/test.txt | 1 + 14 files changed, 256 insertions(+), 57 deletions(-) create mode 100644 src/test/java/org/scijava/io/DummyTextFormat.java create mode 100644 src/test/java/org/scijava/io/IOServiceTest.java create mode 100644 src/test/resources/org/scijava/io/test.txt diff --git a/src/main/java/org/scijava/io/AbstractIOPlugin.java b/src/main/java/org/scijava/io/AbstractIOPlugin.java index 6ccd92f2c..9d314f8a3 100644 --- a/src/main/java/org/scijava/io/AbstractIOPlugin.java +++ b/src/main/java/org/scijava/io/AbstractIOPlugin.java @@ -29,15 +29,50 @@ package org.scijava.io; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; import org.scijava.plugin.AbstractHandlerPlugin; +import org.scijava.plugin.Parameter; + +import java.io.IOException; +import java.net.URISyntaxException; /** * Abstract base class for {@link IOPlugin}s. * * @author Curtis Rueden */ -public abstract class AbstractIOPlugin extends AbstractHandlerPlugin - implements IOPlugin +public abstract class AbstractIOPlugin extends + AbstractHandlerPlugin implements IOPlugin { - // NB: No implementation needed. + + @Parameter + private LocationService locationService; + + @Override + public boolean supportsOpen(final String source) { + try { + return supportsOpen(locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean supportsSave(final String destination) { + try { + return supportsSave(locationService.resolve(destination)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public void save(final D data, final String destination) throws IOException { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } } diff --git a/src/main/java/org/scijava/io/DefaultIOService.java b/src/main/java/org/scijava/io/DefaultIOService.java index c71896453..bfdaf35a2 100644 --- a/src/main/java/org/scijava/io/DefaultIOService.java +++ b/src/main/java/org/scijava/io/DefaultIOService.java @@ -30,10 +30,13 @@ package org.scijava.io; import java.io.IOException; +import java.net.URISyntaxException; import org.scijava.event.EventService; import org.scijava.io.event.DataOpenedEvent; import org.scijava.io.event.DataSavedEvent; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; import org.scijava.log.LogService; import org.scijava.plugin.AbstractHandlerService; import org.scijava.plugin.Parameter; @@ -47,7 +50,7 @@ */ @Plugin(type = Service.class) public final class DefaultIOService - extends AbstractHandlerService> implements IOService + extends AbstractHandlerService> implements IOService { @Parameter @@ -56,10 +59,31 @@ public final class DefaultIOService @Parameter private EventService eventService; - // -- IOService methods -- + @Parameter + private LocationService locationService; @Override public Object open(final String source) throws IOException { + try { + return open(locationService.resolve(source)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public void save(final Object data, final String destination) + throws IOException + { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public Object open(final Location source) throws IOException { final IOPlugin opener = getOpener(source); if (opener == null) { log.error("No opener IOPlugin found for " + source + "."); @@ -77,7 +101,7 @@ public Object open(final String source) throws IOException { } @Override - public void save(final Object data, final String destination) + public void save(final Object data, final Location destination) throws IOException { final IOPlugin saver = getSaver(data, destination); diff --git a/src/main/java/org/scijava/io/DefaultRecentFileService.java b/src/main/java/org/scijava/io/DefaultRecentFileService.java index 28a3804ef..a15b172b5 100644 --- a/src/main/java/org/scijava/io/DefaultRecentFileService.java +++ b/src/main/java/org/scijava/io/DefaultRecentFileService.java @@ -41,6 +41,8 @@ import org.scijava.event.EventHandler; import org.scijava.event.EventService; import org.scijava.io.event.IOEvent; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.menu.MenuConstants; import org.scijava.module.ModuleInfo; import org.scijava.module.ModuleService; @@ -181,7 +183,10 @@ public void dispose() { @EventHandler protected void onEvent(final IOEvent event) { - add(event.getDescriptor()); + final Location loc = event.getLocation(); + if (!(loc instanceof FileLocation)) return; + final FileLocation fileLoc = (FileLocation) loc; + add(fileLoc.getFile().getPath()); } // -- Helper methods -- diff --git a/src/main/java/org/scijava/io/IOPlugin.java b/src/main/java/org/scijava/io/IOPlugin.java index 86bcee5b2..298cf6252 100644 --- a/src/main/java/org/scijava/io/IOPlugin.java +++ b/src/main/java/org/scijava/io/IOPlugin.java @@ -31,6 +31,8 @@ import java.io.IOException; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.HandlerPlugin; import org.scijava.plugin.Plugin; @@ -48,7 +50,7 @@ * @see Plugin * @see IOService */ -public interface IOPlugin extends HandlerPlugin { +public interface IOPlugin extends HandlerPlugin { /** The type of data opened and/or saved by the plugin. */ Class getDataType(); @@ -56,44 +58,66 @@ public interface IOPlugin extends HandlerPlugin { /** Checks whether the I/O plugin can open data from the given source. */ @SuppressWarnings("unused") default boolean supportsOpen(final String source) { + return supportsOpen(new FileLocation(source)); + } + + /** Checks whether the I/O plugin can open data from the given location. */ + default boolean supportsOpen(Location source) { return false; } /** Checks whether the I/O plugin can save data to the given destination. */ @SuppressWarnings("unused") default boolean supportsSave(final String destination) { + return supportsSave(new FileLocation(destination)); + } + + /** Checks whether the I/O plugin can save data to the given location. */ + default boolean supportsSave(Location destination) { return false; } /** * Checks whether the I/O plugin can save the given data to the specified - * destination. + * location. */ default boolean supportsSave(final Object data, final String destination) { return supportsSave(destination) && getDataType().isInstance(data); } + default boolean supportsSave(Object data, Location destination) { + return supportsSave(destination) && getDataType().isInstance(data); + } + /** Opens data from the given source. */ @SuppressWarnings("unused") default D open(final String source) throws IOException { throw new UnsupportedOperationException(); } + /** Opens data from the given location. */ + default D open(Location source) throws IOException { + throw new UnsupportedOperationException(); + } /** Saves the given data to the specified destination. */ @SuppressWarnings("unused") default void save(final D data, final String destination) throws IOException { + save(data, new FileLocation(destination)); + } + + /** Saves the given data to the specified location. */ + default void save(D data, Location destination) throws IOException { throw new UnsupportedOperationException(); } // -- Typed methods -- - @Override default boolean supports(final String descriptor) { return supportsOpen(descriptor) || supportsSave(descriptor); } @Override - default Class getType() { - return String.class; + default Class getType() { + return Location.class; } } diff --git a/src/main/java/org/scijava/io/IOService.java b/src/main/java/org/scijava/io/IOService.java index f31ffdf64..4e49f4aee 100644 --- a/src/main/java/org/scijava/io/IOService.java +++ b/src/main/java/org/scijava/io/IOService.java @@ -31,6 +31,8 @@ import java.io.IOException; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.HandlerService; import org.scijava.service.SciJavaService; @@ -39,15 +41,23 @@ * * @author Curtis Rueden */ -public interface IOService extends HandlerService>, +public interface IOService extends HandlerService>, SciJavaService { /** * Gets the most appropriate {@link IOPlugin} for opening data from the given - * source. + * location. */ default IOPlugin getOpener(final String source) { + return getOpener(new FileLocation(source)); + } + + /** + * Gets the most appropriate {@link IOPlugin} for opening data from the given + * location. + */ + default IOPlugin getOpener(Location source) { for (final IOPlugin handler : getInstances()) { if (handler.supportsOpen(source)) return handler; } @@ -56,9 +66,17 @@ default IOPlugin getOpener(final String source) { /** * Gets the most appropriate {@link IOPlugin} for saving data to the given - * destination. + * location. */ default IOPlugin getSaver(final D data, final String destination) { + return getSaver(data, new FileLocation(destination)); + } + + /** + * Gets the most appropriate {@link IOPlugin} for saving data to the given + * location. + */ + default IOPlugin getSaver(D data, Location destination) { for (final IOPlugin handler : getInstances()) { if (handler.supportsSave(data, destination)) { @SuppressWarnings("unchecked") @@ -77,7 +95,7 @@ default IOPlugin getSaver(final D data, final String destination) { * The opener to use is automatically determined based on available * {@link IOPlugin}s; see {@link #getOpener(String)}. *

- * + * * @param source The source (e.g., file path) from which to data should be * loaded. * @return An object representing the loaded data, or null if the source is @@ -86,6 +104,20 @@ default IOPlugin getSaver(final D data, final String destination) { */ Object open(String source) throws IOException; + /** + * Loads data from the given location. + *

+ * The opener to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getOpener(Location)}. + *

+ * + * @param source The location from which to data should be loaded. + * @return An object representing the loaded data, or null if the source is + * not supported. + * @throws IOException if something goes wrong loading the data. + */ + Object open(Location source) throws IOException; + /** * Saves data to the given destination. The nature of the destination is left * intentionally general, but the most common example is a file path. @@ -93,7 +125,7 @@ default IOPlugin getSaver(final D data, final String destination) { * The saver to use is automatically determined based on available * {@link IOPlugin}s; see {@link #getSaver(Object, String)}. *

- * + * * @param data The data to be saved to the destination. * @param destination The destination (e.g., file path) to which data should * be saved. @@ -101,6 +133,19 @@ default IOPlugin getSaver(final D data, final String destination) { */ void save(Object data, String destination) throws IOException; + /** + * Saves data to the given location. + *

+ * The saver to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getSaver(Object, Location)}. + *

+ * + * @param data The data to be saved to the destination. + * @param destination The destination location to which data should be saved. + * @throws IOException if something goes wrong saving the data. + */ + void save(Object data, Location destination) throws IOException; + // -- HandlerService methods -- @Override @@ -110,7 +155,7 @@ default Class> getPluginType() { } @Override - default Class getType() { - return String.class; + default Class getType() { + return Location.class; } } diff --git a/src/main/java/org/scijava/io/event/DataOpenedEvent.java b/src/main/java/org/scijava/io/event/DataOpenedEvent.java index 7af006c5a..4cf613856 100644 --- a/src/main/java/org/scijava/io/event/DataOpenedEvent.java +++ b/src/main/java/org/scijava/io/event/DataOpenedEvent.java @@ -29,22 +29,18 @@ package org.scijava.io.event; + +import org.scijava.io.location.Location; + /** - * An event indicating that data has been opened from a source. + * An event indicating that data has been opened from a location. * * @author Curtis Rueden */ public class DataOpenedEvent extends IOEvent { - public DataOpenedEvent(final String source, final Object data) { - super(source, data); - } - - // -- DataOpenedEvent methods -- - - /** Gets the source from which data was opened. */ - public String getSource() { - return getDescriptor(); + public DataOpenedEvent(final Location location, final Object data) { + super(location, data); } } diff --git a/src/main/java/org/scijava/io/event/DataSavedEvent.java b/src/main/java/org/scijava/io/event/DataSavedEvent.java index cd6d22439..fe4b7abc3 100644 --- a/src/main/java/org/scijava/io/event/DataSavedEvent.java +++ b/src/main/java/org/scijava/io/event/DataSavedEvent.java @@ -29,6 +29,9 @@ package org.scijava.io.event; + +import org.scijava.io.location.Location; + /** * An event indicating that data has been saved to a destination. * @@ -36,15 +39,8 @@ */ public class DataSavedEvent extends IOEvent { - public DataSavedEvent(final String destination, final Object data) { + public DataSavedEvent(final Location destination, final Object data) { super(destination, data); } - // -- DataSavedEvent methods -- - - /** Gets the destination to which data was saved. */ - public String getDestination() { - return getDescriptor(); - } - } diff --git a/src/main/java/org/scijava/io/event/IOEvent.java b/src/main/java/org/scijava/io/event/IOEvent.java index 1a62e6fca..9228c94a4 100644 --- a/src/main/java/org/scijava/io/event/IOEvent.java +++ b/src/main/java/org/scijava/io/event/IOEvent.java @@ -30,6 +30,7 @@ package org.scijava.io.event; import org.scijava.event.SciJavaEvent; +import org.scijava.io.location.Location; /** * An event indicating that I/O (e.g., opening or saving) has occurred. @@ -38,20 +39,20 @@ */ public abstract class IOEvent extends SciJavaEvent { - /** The data descriptor (source or destination). */ - private final String descriptor; + /** The data location (source or destination). */ + private final Location location; /** The data for which I/O took place. */ private final Object data; - public IOEvent(final String descriptor, final Object data) { - this.descriptor = descriptor; + public IOEvent(final Location location, final Object data) { + this.location = location; this.data = data; } - /** Gets the data descriptor (source or destination). */ - public String getDescriptor() { - return descriptor; + /** Gets the data location (source or destination). */ + public Location getLocation() { + return location; } /** Gets the data for which I/O took place. */ @@ -63,7 +64,8 @@ public Object getData() { @Override public String toString() { - return super.toString() + "\n\tdescriptor = " + data + "\n\tdata = " + data; + return super.toString() + "\n\tlocation = " + location + "\n\tdata = " + + data; } } diff --git a/src/main/java/org/scijava/script/io/ScriptIOPlugin.java b/src/main/java/org/scijava/script/io/ScriptIOPlugin.java index 56d201770..f881dc429 100644 --- a/src/main/java/org/scijava/script/io/ScriptIOPlugin.java +++ b/src/main/java/org/scijava/script/io/ScriptIOPlugin.java @@ -33,6 +33,8 @@ import org.scijava.io.AbstractIOPlugin; import org.scijava.io.IOPlugin; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.Parameter; import org.scijava.script.ScriptService; @@ -55,13 +57,16 @@ public Class getDataType() { } @Override - public boolean supportsOpen(final String source) { + public boolean supportsOpen(final Location source) { if (scriptService == null) return false; // no service for opening scripts - return scriptService.canHandleFile(source); + // TODO: Update ScriptService to use Location instead of File. + if (!(source instanceof FileLocation)) return false; + final FileLocation loc = (FileLocation) source; + return scriptService.canHandleFile(loc.getFile()); } @Override - public String open(final String source) throws IOException { + public String open(final Location source) throws IOException { if (scriptService == null) return null; // no service for opening scripts // TODO: Use the script service to open the file in the script editor. return null; diff --git a/src/main/java/org/scijava/text/io/TextIOPlugin.java b/src/main/java/org/scijava/text/io/TextIOPlugin.java index 3d2c64172..523ff2c34 100644 --- a/src/main/java/org/scijava/text/io/TextIOPlugin.java +++ b/src/main/java/org/scijava/text/io/TextIOPlugin.java @@ -29,12 +29,13 @@ package org.scijava.text.io; -import java.io.File; import java.io.IOException; import org.scijava.Priority; import org.scijava.io.AbstractIOPlugin; import org.scijava.io.IOPlugin; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.text.TextService; @@ -59,15 +60,19 @@ public Class getDataType() { } @Override - public boolean supportsOpen(final String source) { + public boolean supportsOpen(final Location source) { if (textService == null) return false; // no service for opening text files - return textService.supports(new File(source)); + if (!(source instanceof FileLocation)) return false; + final FileLocation loc = (FileLocation) source; + return textService.supports(loc.getFile()); } @Override - public String open(final String source) throws IOException { + public String open(final Location source) throws IOException { if (textService == null) return null; // no service for opening text files - return textService.asHTML(new File(source)); + if (!(source instanceof FileLocation)) throw new IllegalArgumentException(); + final FileLocation loc = (FileLocation) source; + return textService.asHTML(loc.getFile()); } } diff --git a/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java index 2076c6a20..02289b6e4 100644 --- a/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java @@ -36,6 +36,7 @@ import org.scijava.display.Display; import org.scijava.display.DisplayService; import org.scijava.io.IOService; +import org.scijava.io.location.FileLocation; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -68,7 +69,8 @@ public boolean supports(final File file) { if (!super.supports(file)) return false; // verify that the file can be opened somehow - return ioService.getOpener(file.getAbsolutePath()) != null; + final FileLocation loc = new FileLocation(file); + return ioService.getOpener(loc) != null; } @Override @@ -78,13 +80,12 @@ public boolean drop(final File file, final Display display) { if (file == null) return true; // trivial case // load the data - final String filename = file.getAbsolutePath(); final Object data; try { - data = ioService.open(filename); + data = ioService.open(new FileLocation(file)); } catch (final IOException exc) { - if (log != null) log.error("Error opening file: " + filename, exc); + if (log != null) log.error("Error opening file: " + file, exc); return false; } diff --git a/src/test/java/org/scijava/io/DummyTextFormat.java b/src/test/java/org/scijava/io/DummyTextFormat.java new file mode 100644 index 000000000..21729843f --- /dev/null +++ b/src/test/java/org/scijava/io/DummyTextFormat.java @@ -0,0 +1,22 @@ +package org.scijava.io; + +import org.scijava.plugin.Plugin; +import org.scijava.text.AbstractTextFormat; +import org.scijava.text.TextFormat; + +import java.util.Collections; +import java.util.List; + +@Plugin(type = TextFormat.class) +public class DummyTextFormat extends AbstractTextFormat { + + @Override + public List getExtensions() { + return Collections.singletonList("txt"); + } + + @Override + public String asHTML(String text) { + return text; + } +} diff --git a/src/test/java/org/scijava/io/IOServiceTest.java b/src/test/java/org/scijava/io/IOServiceTest.java new file mode 100644 index 000000000..55ebcf774 --- /dev/null +++ b/src/test/java/org/scijava/io/IOServiceTest.java @@ -0,0 +1,38 @@ +package org.scijava.io; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.location.FileLocation; +import org.scijava.plugin.PluginInfo; +import org.scijava.text.TextFormat; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class IOServiceTest { + + @Test + public void testTextFile() throws IOException { + // create context, add dummy text format + final Context ctx = new Context(); + ctx.getPluginIndex().add(new PluginInfo<>(DummyTextFormat.class, TextFormat.class)); + final IOService io = ctx.getService(IOService.class); + + // open text file from resources as String + String localFile = getClass().getResource("test.txt").getPath(); + Object obj = io.open(localFile); + assertNotNull(obj); + String content = obj.toString(); + assertTrue(content.contains("content")); + + // open text file from resources as FileLocation + obj = io.open(new FileLocation(localFile)); + assertNotNull(obj); + assertEquals(content, obj.toString()); + } +} diff --git a/src/test/resources/org/scijava/io/test.txt b/src/test/resources/org/scijava/io/test.txt new file mode 100644 index 000000000..d95f3ad14 --- /dev/null +++ b/src/test/resources/org/scijava/io/test.txt @@ -0,0 +1 @@ +content