Skip to content

Commit 5a83bcd

Browse files
frauzufallctrueden
authored andcommitted
Add TypedIOService
This is an interface which can be used to write IOServices opening and saving a specific type. The TypedIOServiceTest class demonstrates how to do that with an exemplary TextIOService. So far, IO services like DatasetIOService or TableIOServcie don't share a common IOService interface.
1 parent 6ada478 commit 5a83bcd

File tree

3 files changed

+369
-0
lines changed

3 files changed

+369
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2020 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.io;
31+
32+
import org.scijava.io.location.Location;
33+
import org.scijava.io.location.LocationService;
34+
import org.scijava.plugin.AbstractHandlerService;
35+
import org.scijava.plugin.Parameter;
36+
37+
import java.io.IOException;
38+
import java.net.URISyntaxException;
39+
40+
/**
41+
* Abstract base class for typed {@link IOPlugin}s.
42+
*
43+
* @author Curtis Rueden
44+
* @author Deborah Schmidt
45+
*/
46+
public abstract class AbstractTypedIOService<D> extends AbstractHandlerService<Location, IOPlugin<D>> implements TypedIOService<D>
47+
{
48+
49+
@Parameter
50+
private LocationService locationService;
51+
52+
@Parameter
53+
private IOService ioService;
54+
55+
@Override
56+
public D open(String source) throws IOException {
57+
try {
58+
return open(locationService.resolve(source));
59+
} catch (URISyntaxException e) {
60+
throw new IOException(e);
61+
}
62+
}
63+
64+
@Override
65+
public D open(Location source) throws IOException {
66+
IOPlugin<?> opener = ioService().getOpener(source);
67+
try {
68+
Class<D> ignored = (Class<D>) opener.getDataType();
69+
return (D) opener.open(source);
70+
}
71+
catch(ClassCastException e) {
72+
throw new UnsupportedOperationException("No compatible opener found.");
73+
}
74+
}
75+
76+
@Override
77+
public void save(D data, String destination) throws IOException {
78+
try {
79+
save(data, locationService.resolve(destination));
80+
} catch (URISyntaxException e) {
81+
throw new IOException(e);
82+
}
83+
}
84+
85+
@Override
86+
public void save(D data, Location destination) throws IOException {
87+
IOPlugin<D> saver = ioService().getSaver(data, destination);
88+
if (saver != null) {
89+
saver.save(data, destination);
90+
}
91+
else {
92+
throw new UnsupportedOperationException("No compatible saver found.");
93+
}
94+
}
95+
96+
@Override
97+
public boolean canOpen(String source) {
98+
try {
99+
return canOpen(locationService.resolve(source));
100+
} catch (URISyntaxException e) {
101+
return false;
102+
}
103+
}
104+
105+
@Override
106+
public boolean canOpen(Location source) {
107+
IOPlugin<?> opener = ioService().getOpener(source);
108+
if (opener == null) return false;
109+
try {
110+
Class<D> ignored = (Class<D>) (opener.getDataType());
111+
return true;
112+
} catch(ClassCastException e) {
113+
return false;
114+
}
115+
}
116+
117+
@Override
118+
public boolean canSave(D data, String source) {
119+
try {
120+
return canSave(data, locationService.resolve(source));
121+
} catch (URISyntaxException e) {
122+
return false;
123+
}
124+
}
125+
126+
@Override
127+
public boolean canSave(D data, Location destination) {
128+
IOPlugin<D> saver = ioService.getSaver(data, destination);
129+
if (saver == null) return false;
130+
return saver.supportsSave(destination);
131+
}
132+
133+
protected LocationService locationService() {
134+
return locationService;
135+
}
136+
137+
protected IOService ioService() {
138+
return ioService;
139+
}
140+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* #%L
3+
* SciJava Common shared library for SciJava software.
4+
* %%
5+
* Copyright (C) 2009 - 2020 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.io;
31+
32+
import java.io.IOException;
33+
34+
import org.scijava.io.location.FileLocation;
35+
import org.scijava.io.location.Location;
36+
import org.scijava.plugin.HandlerService;
37+
import org.scijava.service.SciJavaService;
38+
39+
/**
40+
* Interface for high-level data I/O: opening and saving data of a specific type.
41+
*
42+
* @author Curtis Rueden
43+
* @author Deborah Schmidt
44+
*/
45+
public interface TypedIOService<D> extends HandlerService<Location, IOPlugin<D>>,
46+
SciJavaService
47+
{
48+
49+
/**
50+
* Gets the most appropriate {@link IOPlugin} for opening data from the given
51+
* location.
52+
*/
53+
default IOPlugin<D> getOpener(final String source) {
54+
return getOpener(new FileLocation(source));
55+
}
56+
57+
/**
58+
* Gets the most appropriate {@link IOPlugin} for opening data from the given
59+
* location.
60+
*/
61+
default IOPlugin<D> getOpener(Location source) {
62+
for (final IOPlugin<D> handler : getInstances()) {
63+
if (handler.supportsOpen(source)) return handler;
64+
}
65+
return null;
66+
}
67+
68+
/**
69+
* Gets the most appropriate {@link IOPlugin} for saving data to the given
70+
* location.
71+
*/
72+
default IOPlugin<D> getSaver(final D data, final String destination) {
73+
return getSaver(data, new FileLocation(destination));
74+
}
75+
76+
/**
77+
* Gets the most appropriate {@link IOPlugin} for saving data to the given
78+
* location.
79+
*/
80+
default IOPlugin<D> getSaver(D data, Location destination) {
81+
for (final IOPlugin<?> handler : getInstances()) {
82+
if (handler.supportsSave(data, destination)) {
83+
return (IOPlugin<D>) handler;
84+
}
85+
}
86+
return null;
87+
}
88+
89+
/**
90+
* Loads data from the given source. For extensibility, the nature of the
91+
* source is left intentionally general, but two common examples include file
92+
* paths and URLs.
93+
* <p>
94+
* The opener to use is automatically determined based on available
95+
* {@link IOPlugin}s; see {@link #getOpener(String)}.
96+
* </p>
97+
*
98+
* @param source The source (e.g., file path) from which to data should be
99+
* loaded.
100+
* @return An object representing the loaded data, or null if the source is
101+
* not supported.
102+
* @throws IOException if something goes wrong loading the data.
103+
*/
104+
D open(String source) throws IOException;
105+
106+
/**
107+
* Loads data from the given location.
108+
* <p>
109+
* The opener to use is automatically determined based on available
110+
* {@link IOPlugin}s; see {@link #getOpener(Location)}.
111+
* </p>
112+
*
113+
* @param source The location from which to data should be loaded.
114+
* @return An object representing the loaded data, or null if the source is
115+
* not supported.
116+
* @throws IOException if something goes wrong loading the data.
117+
*/
118+
D open(Location source) throws IOException;
119+
120+
/**
121+
* Saves data to the given destination. The nature of the destination is left
122+
* intentionally general, but the most common example is a file path.
123+
* <p>
124+
* The saver to use is automatically determined based on available
125+
* {@link IOPlugin}s; see {@link #getSaver(Object, String)}.
126+
* </p>
127+
*
128+
* @param data The data to be saved to the destination.
129+
* @param destination The destination (e.g., file path) to which data should
130+
* be saved.
131+
* @throws IOException if something goes wrong saving the data.
132+
*/
133+
void save(D data, String destination) throws IOException;
134+
135+
/**
136+
* Saves data to the given location.
137+
* <p>
138+
* The saver to use is automatically determined based on available
139+
* {@link IOPlugin}s; see {@link #getSaver(Object, Location)}.
140+
* </p>
141+
*
142+
* @param data The data to be saved to the destination.
143+
* @param destination The destination location to which data should be saved.
144+
* @throws IOException if something goes wrong saving the data.
145+
*/
146+
void save(D data, Location destination) throws IOException;
147+
148+
boolean canOpen(String source);
149+
150+
boolean canOpen(Location source);
151+
152+
boolean canSave(D data, String destination);
153+
154+
boolean canSave(D data, Location destination);
155+
156+
// -- HandlerService methods --
157+
158+
@Override
159+
@SuppressWarnings({ "rawtypes", "unchecked" })
160+
default Class<IOPlugin<D>> getPluginType() {
161+
return (Class) IOPlugin.class;
162+
}
163+
164+
@Override
165+
default Class<Location> getType() {
166+
return Location.class;
167+
}
168+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.scijava.io;
2+
3+
import org.junit.Test;
4+
import org.scijava.Context;
5+
import org.scijava.plugin.PluginInfo;
6+
import org.scijava.plugin.PluginService;
7+
import org.scijava.plugin.SciJavaPlugin;
8+
import org.scijava.service.SciJavaService;
9+
import org.scijava.text.AbstractTextFormat;
10+
import org.scijava.text.TextFormat;
11+
import org.scijava.text.TextService;
12+
13+
import java.io.IOException;
14+
import java.util.Collections;
15+
import java.util.List;
16+
17+
import static org.junit.Assert.assertNotNull;
18+
import static org.junit.Assert.assertTrue;
19+
20+
public class TypedIOServiceTest {
21+
22+
@Test
23+
public void testTextFile() throws IOException {
24+
// create context, add dummy text format
25+
final Context ctx = new Context();
26+
ctx.getPluginIndex().add(new PluginInfo<>(DummyTextFormat.class, TextFormat.class));
27+
ctx.getPluginIndex().add(new PluginInfo<>(DefaultTextIOService.class, TextIOService.class));
28+
TextIOService instance = (TextIOService) ctx.getService(PluginService.class).createInstance(ctx.getPluginIndex().get(TextIOService.class).get(0));
29+
ctx.getServiceIndex().add(instance);
30+
31+
// try to get the TextIOService
32+
final TextIOService io = ctx.service(TextIOService.class);
33+
assertNotNull(io);
34+
35+
// open text file from resources as String
36+
String localFile = getClass().getResource("test.txt").getPath();
37+
String obj = io.open(localFile);
38+
assertNotNull(obj);
39+
assertTrue(obj.contains("content"));
40+
}
41+
42+
interface TextIOService extends TypedIOService<String> {
43+
}
44+
45+
public static class DefaultTextIOService extends AbstractTypedIOService<String> implements TextIOService {
46+
}
47+
48+
public static class DummyTextFormat extends AbstractTextFormat {
49+
50+
@Override
51+
public List<String> getExtensions() {
52+
return Collections.singletonList("txt");
53+
}
54+
55+
@Override
56+
public String asHTML(String text) {
57+
return text;
58+
}
59+
60+
}
61+
}

0 commit comments

Comments
 (0)