diff --git a/pom.xml b/pom.xml index 92757258f..df8282af0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.scijava pom-scijava - 28.0.0 + 29.2.1 diff --git a/src/main/java/org/scijava/io/AbstractIOPlugin.java b/src/main/java/org/scijava/io/AbstractIOPlugin.java index 6ccd92f2c..98dd6fef7 100644 --- a/src/main/java/org/scijava/io/AbstractIOPlugin.java +++ b/src/main/java/org/scijava/io/AbstractIOPlugin.java @@ -29,15 +29,60 @@ 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); + } + } + + @Override + public D open(final String destination) throws IOException { + try { + return open(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..0d6ad9340 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,22 @@ 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. + */ + default Object open(Location source) throws IOException { + throw new UnsupportedOperationException(); + } + /** * 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 +127,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 +135,21 @@ 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. + */ + default void save(Object data, Location destination) throws IOException { + throw new UnsupportedOperationException(); + } + // -- HandlerService methods -- @Override @@ -110,7 +159,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..2d9c50929 100644 --- a/src/main/java/org/scijava/io/event/DataOpenedEvent.java +++ b/src/main/java/org/scijava/io/event/DataOpenedEvent.java @@ -29,22 +29,40 @@ package org.scijava.io.event; + +import org.scijava.io.location.FileLocation; +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); + public DataOpenedEvent(final Location location, final Object data) { + super(location, data); } - // -- DataOpenedEvent methods -- + /** + * @deprecated use {@link #DataOpenedEvent(Location, Object)} instead + */ + @Deprecated + public DataOpenedEvent(final String source, final Object data) { + this(new FileLocation(source), data); + } - /** Gets the source from which data was opened. */ + /** + * @deprecated use {@link #getLocation} instead + */ + @Deprecated public String getSource() { - return getDescriptor(); + try { + FileLocation fileLocation = (FileLocation) getLocation(); + return fileLocation.getFile().getAbsolutePath(); + } catch(ClassCastException e) { + return getLocation().getURI().toString(); + } } } diff --git a/src/main/java/org/scijava/io/event/DataSavedEvent.java b/src/main/java/org/scijava/io/event/DataSavedEvent.java index cd6d22439..600eccafc 100644 --- a/src/main/java/org/scijava/io/event/DataSavedEvent.java +++ b/src/main/java/org/scijava/io/event/DataSavedEvent.java @@ -29,6 +29,10 @@ package org.scijava.io.event; + +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; + /** * An event indicating that data has been saved to a destination. * @@ -36,15 +40,28 @@ */ 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 -- + /** + * @deprecated use {@link #DataSavedEvent(Location, Object)} instead + */ + @Deprecated + public DataSavedEvent(final String destination, final Object data) { + this(new FileLocation(destination), data); + } - /** Gets the destination to which data was saved. */ + /** + * @deprecated use {@link #getLocation} instead + */ + @Deprecated public String getDestination() { - return getDescriptor(); + try { + FileLocation fileLocation = (FileLocation) getLocation(); + return fileLocation.getFile().getAbsolutePath(); + } catch(ClassCastException e) { + return getLocation().getURI().toString(); + } } - } diff --git a/src/main/java/org/scijava/io/event/IOEvent.java b/src/main/java/org/scijava/io/event/IOEvent.java index 1a62e6fca..89bfe0f27 100644 --- a/src/main/java/org/scijava/io/event/IOEvent.java +++ b/src/main/java/org/scijava/io/event/IOEvent.java @@ -30,6 +30,8 @@ package org.scijava.io.event; import org.scijava.event.SciJavaEvent; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; /** * An event indicating that I/O (e.g., opening or saving) has occurred. @@ -38,20 +40,28 @@ */ 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; + /** + * @deprecated use {@link #IOEvent(Location, Object)} instead + */ + @Deprecated public IOEvent(final String descriptor, final Object data) { - this.descriptor = descriptor; + this(new FileLocation(descriptor), data); + } + + 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 +73,21 @@ 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; + } + + /** + * @deprecated use {@link #getLocation()} instead + */ + @Deprecated + public String getDescriptor() { + try { + FileLocation fileLocation = (FileLocation) getLocation(); + return fileLocation.getFile().getAbsolutePath(); + } catch(ClassCastException e) { + return getLocation().getURI().toString(); + } } } 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/IOServiceTest.java b/src/test/java/org/scijava/io/IOServiceTest.java new file mode 100644 index 000000000..bd3f7a3af --- /dev/null +++ b/src/test/java/org/scijava/io/IOServiceTest.java @@ -0,0 +1,53 @@ +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.AbstractTextFormat; +import org.scijava.text.TextFormat; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +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()); + } + + + public static 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/event/DataEventTest.java b/src/test/java/org/scijava/io/event/DataEventTest.java new file mode 100644 index 000000000..8e2ce0aa0 --- /dev/null +++ b/src/test/java/org/scijava/io/event/DataEventTest.java @@ -0,0 +1,25 @@ +package org.scijava.io.event; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class DataEventTest { + + @Test + public void testDeprecatedMethods() { + String localPath = "/local/absolute/path.txt"; + Object obj = null; + DataOpenedEvent openedEvent = new DataOpenedEvent(localPath, obj); + DataSavedEvent savedEvent = new DataSavedEvent(localPath, obj); + assertEquals(localPath, openedEvent.getSource()); + assertEquals(localPath, savedEvent.getDestination()); + +// String remotepath = "https://remote.org/path.txt"; +// openedEvent = new DataOpenedEvent(remotepath, obj); +// savedEvent = new DataSavedEvent(remotepath, obj); +// assertEquals(remotepath, openedEvent.getSource()); +// assertEquals(remotepath, savedEvent.getDestination()); + } + +} 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