diff --git a/src/main/java/org/scijava/io/AbstractTypedIOService.java b/src/main/java/org/scijava/io/AbstractTypedIOService.java new file mode 100644 index 000000000..45403af49 --- /dev/null +++ b/src/main/java/org/scijava/io/AbstractTypedIOService.java @@ -0,0 +1,140 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2020 SciJava developers. + * %% + * 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.io; + +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; +import org.scijava.plugin.AbstractHandlerService; +import org.scijava.plugin.Parameter; + +import java.io.IOException; +import java.net.URISyntaxException; + +/** + * Abstract base class for typed {@link IOPlugin}s. + * + * @author Curtis Rueden + * @author Deborah Schmidt + */ +public abstract class AbstractTypedIOService extends AbstractHandlerService> implements TypedIOService +{ + + @Parameter + private LocationService locationService; + + @Parameter + private IOService ioService; + + @Override + public D open(String source) throws IOException { + try { + return open(locationService.resolve(source)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public D open(Location source) throws IOException { + IOPlugin opener = ioService().getOpener(source); + try { + Class ignored = (Class) opener.getDataType(); + return (D) opener.open(source); + } + catch(ClassCastException e) { + throw new UnsupportedOperationException("No compatible opener found."); + } + } + + @Override + public void save(D data, String destination) throws IOException { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public void save(D data, Location destination) throws IOException { + IOPlugin saver = ioService().getSaver(data, destination); + if (saver != null) { + saver.save(data, destination); + } + else { + throw new UnsupportedOperationException("No compatible saver found."); + } + } + + @Override + public boolean canOpen(String source) { + try { + return canOpen(locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean canOpen(Location source) { + IOPlugin opener = ioService().getOpener(source); + if (opener == null) return false; + try { + Class ignored = (Class) (opener.getDataType()); + return true; + } catch(ClassCastException e) { + return false; + } + } + + @Override + public boolean canSave(D data, String source) { + try { + return canSave(data, locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean canSave(D data, Location destination) { + IOPlugin saver = ioService.getSaver(data, destination); + if (saver == null) return false; + return saver.supportsSave(destination); + } + + protected LocationService locationService() { + return locationService; + } + + protected IOService ioService() { + return ioService; + } +} diff --git a/src/main/java/org/scijava/io/TypedIOService.java b/src/main/java/org/scijava/io/TypedIOService.java new file mode 100644 index 000000000..550edd67a --- /dev/null +++ b/src/main/java/org/scijava/io/TypedIOService.java @@ -0,0 +1,168 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2020 SciJava developers. + * %% + * 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.io; + +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; + +/** + * Interface for high-level data I/O: opening and saving data of a specific type. + * + * @author Curtis Rueden + * @author Deborah Schmidt + */ +public interface TypedIOService extends HandlerService>, + SciJavaService +{ + + /** + * Gets the most appropriate {@link IOPlugin} for opening data from the given + * 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; + } + return null; + } + + /** + * Gets the most appropriate {@link IOPlugin} for saving data to the given + * 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)) { + return (IOPlugin) handler; + } + } + return null; + } + + /** + * Loads data from the given source. For extensibility, the nature of the + * source is left intentionally general, but two common examples include file + * paths and URLs. + *

+ * 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 + * not supported. + * @throws IOException if something goes wrong loading the data. + */ + D 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. + */ + D 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. + *

+ * 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. + * @throws IOException if something goes wrong saving the data. + */ + void save(D 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(D data, Location destination) throws IOException; + + boolean canOpen(String source); + + boolean canOpen(Location source); + + boolean canSave(D data, String destination); + + boolean canSave(D data, Location destination); + + // -- HandlerService methods -- + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + default Class> getPluginType() { + return (Class) IOPlugin.class; + } + + @Override + default Class getType() { + return Location.class; + } +} diff --git a/src/main/java/org/scijava/text/io/DefaultTextIOService.java b/src/main/java/org/scijava/text/io/DefaultTextIOService.java new file mode 100644 index 000000000..509216a3c --- /dev/null +++ b/src/main/java/org/scijava/text/io/DefaultTextIOService.java @@ -0,0 +1,43 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2020 SciJava developers. + * %% + * 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.text.io; + +import org.scijava.io.AbstractTypedIOService; +import org.scijava.plugin.Plugin; +import org.scijava.service.Service; + +/** + * Default {@link TextIOService} implementation for opening and saving text data + * + * @author Deborah Schmidt + */ +@Plugin(type = Service.class) +public class DefaultTextIOService extends AbstractTypedIOService implements TextIOService { +} diff --git a/src/main/java/org/scijava/text/io/TextIOService.java b/src/main/java/org/scijava/text/io/TextIOService.java new file mode 100644 index 000000000..c87d2847a --- /dev/null +++ b/src/main/java/org/scijava/text/io/TextIOService.java @@ -0,0 +1,40 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2020 SciJava developers. + * %% + * 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.text.io; + +import org.scijava.io.TypedIOService; + +/** + * {@link TypedIOService} for opening and saving text data + * + * @author Deborah Schmidt + */ +public interface TextIOService extends TypedIOService { +} diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 4fd266e4b..ccf76e426 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -114,6 +114,7 @@ public void testFull() { org.scijava.startup.DefaultStartupService.class, org.scijava.task.DefaultTaskService.class, org.scijava.text.DefaultTextService.class, + org.scijava.text.io.DefaultTextIOService.class, org.scijava.thread.DefaultThreadService.class, org.scijava.tool.DefaultToolService.class, org.scijava.ui.DefaultUIService.class, diff --git a/src/test/java/org/scijava/io/TypedIOServiceTest.java b/src/test/java/org/scijava/io/TypedIOServiceTest.java new file mode 100644 index 000000000..de64730e2 --- /dev/null +++ b/src/test/java/org/scijava/io/TypedIOServiceTest.java @@ -0,0 +1,49 @@ +package org.scijava.io; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.PluginInfo; +import org.scijava.text.AbstractTextFormat; +import org.scijava.text.TextFormat; +import org.scijava.text.io.TextIOService; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TypedIOServiceTest { + + @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)); + + // try to get the TextIOService + final TextIOService io = ctx.service(TextIOService.class); + assertNotNull(io); + + // open text file from resources as String + String localFile = getClass().getResource("test.txt").getPath(); + String obj = io.open(localFile); + assertNotNull(obj); + assertTrue(obj.contains("content")); + } + + public static class DummyTextFormat extends AbstractTextFormat { + + @Override + public List getExtensions() { + return Collections.singletonList("txt"); + } + + @Override + public String asHTML(String text) { + return text; + } + + } +}