From 4a365aa64e36658a12de0a3148fec4d17349b5ab Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Mon, 15 Oct 2018 17:10:41 +0200 Subject: [PATCH 1/6] DataHandle: fix findString(..) and support handles with unknown length The findString(..) method used to require the exact length of the handle, this length can be unknown, e.g. for compressed handles. This is no longer the case. --- .../org/scijava/io/handle/DataHandle.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index ccb93d434..c7cbfb654 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -350,10 +350,7 @@ default String findString(final boolean saveString, final int blockSize, final StringBuilder out = new StringBuilder(); final long startPos = offset(); long bytesDropped = 0; - final long inputLen = length(); - long maxLen = inputLen - startPos; - final boolean tooLong = saveString && maxLen > MAX_SEARCH_SIZE; - if (tooLong) maxLen = MAX_SEARCH_SIZE; + final long maxLen = saveString ? MAX_SEARCH_SIZE : Long.MAX_VALUE; boolean match = false; int maxTermLen = 0; for (final String term : terminators) { @@ -366,7 +363,10 @@ default String findString(final boolean saveString, final int blockSize, new DataHandleInputStream<>(this), getEncoding()); final char[] buf = new char[blockSize]; long loc = 0; - while (loc < maxLen && offset() < length() - 1) { + int r = 0; + + // NB: we need at least 2 bytes to read a char + while (loc < maxLen && ((r = in.read(buf, 0, blockSize)) > 1)) { // if we're not saving the string, drop any old, unnecessary output if (!saveString) { final int outLen = out.length(); @@ -378,16 +378,12 @@ default String findString(final boolean saveString, final int blockSize, bytesDropped += dropIndex; } } - - // read block from stream - final int r = in.read(buf, 0, blockSize); - if (r <= 0) throw new IOException("Cannot read from stream: " + r); - // append block to output out.append(buf, 0, r); // check output, returning smallest possible string - int min = Integer.MAX_VALUE, tagLen = 0; + int min = Integer.MAX_VALUE; + int tagLen = 0; for (final String t : terminators) { final int len = t.length(); final int start = (int) (loc - bytesDropped - len); @@ -415,7 +411,9 @@ default String findString(final boolean saveString, final int blockSize, } // no match - if (tooLong) throw new IOException("Maximum search length reached."); + if (loc > MAX_SEARCH_SIZE) { + throw new IOException("Maximum search length reached."); + } return saveString ? out.toString() : null; } From a922e158a31e3a0469cc090cdad5ea67ac7f5cc7 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 16 Oct 2018 10:59:55 +0200 Subject: [PATCH 2/6] Add test for edge cases in the DataHandle default methods --- .../io/handle/DataHandleEdgeCaseTests.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java diff --git a/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java b/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java new file mode 100644 index 000000000..268faee59 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java @@ -0,0 +1,142 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2018 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.io.handle; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; + +/** + * Additional Tests for edge case behavior of {@link DataHandle}. + * + * @author Gabriel Einsdorf + */ +public class DataHandleEdgeCaseTests { + + private static final byte[] BYTES = { // + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', // + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // + 125, 127, -127, -125, -3, 'h', 'e' }; + + /** + * Test to ensure {@link DataHandle#findString(String...)} and + * {@link DataHandle#readCString()} work with {@link DataHandle} + * implementations that have unknown length. + * + * @throws IOException + */ + @Test + public void testFindStringsOnUnknonwLengthHandle() throws IOException { + final Context ctx = new Context(); + final DataHandleService dhs = ctx.getService(DataHandleService.class); + final DummyHandle dummy = new DummyHandle(dhs.create(new BytesLocation( + BYTES))); + + assertEquals("Hello,", dummy.findString(",")); + assertEquals(" world\n", dummy.findString("\n")); + + dummy.seek(41); + assertEquals("he", dummy.findString("\n")); + + dummy.seek(16); + assertArrayEquals(Arrays.copyOfRange(BYTES, 16, 23), dummy.readCString() + .getBytes()); + dummy.seek(42); + assertNull(dummy.readCString()); + } + + private class DummyHandle extends AbstractHigherOrderHandle { + + public DummyHandle(final DataHandle handle) { + super(handle); + } + + @Override + public long length() throws IOException { + return -1; + } + + @Override + public long offset() throws IOException { + return handle().offset(); + } + + @Override + public void seek(final long pos) throws IOException { + handle().seek(pos); + } + + @Override + public void setLength(final long length) throws IOException { + handle().setLength(length); + } + + @Override + public int read(final byte[] b, final int off, final int len) + throws IOException + { + return handle().read(b, off, len); + } + + @Override + public byte readByte() throws IOException { + return handle().readByte(); + } + + @Override + public void write(final int b) throws IOException { + handle().write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException + { + handle().write(b, off, len); + } + + @Override + protected void cleanup() throws IOException { + // + } + } + +} From 54d93cddb68bbc47e9f5f7bcf1d22e4a4cb1d1e1 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 14 Mar 2017 16:49:03 +0100 Subject: [PATCH 3/6] Add StreamHandle This basic version does not fully support random access, which will be provided by subclasses to be added in future commits. --- .../io/handle/AbstractStreamHandle.java | 65 ++++++ .../org/scijava/io/handle/StreamHandle.java | 192 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/main/java/org/scijava/io/handle/AbstractStreamHandle.java create mode 100644 src/main/java/org/scijava/io/handle/StreamHandle.java diff --git a/src/main/java/org/scijava/io/handle/AbstractStreamHandle.java b/src/main/java/org/scijava/io/handle/AbstractStreamHandle.java new file mode 100644 index 000000000..f06e9fbb6 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/AbstractStreamHandle.java @@ -0,0 +1,65 @@ +/* + * #%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, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * 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.handle; + +import org.scijava.io.location.Location; + +/** + * Abstract base class for {@link StreamHandle} implementations. + * + * @author Curtis Rueden + * @author Melissa Linkert + */ +public abstract class AbstractStreamHandle extends + AbstractDataHandle implements StreamHandle +{ + + // -- Fields -- + + /** Current position within the stream(s). */ + private long offset; + + // -- StreamHandle methods -- + + @Override + public void setOffset(final long offset) { + this.offset = offset; + } + + // -- DataHandle methods -- + + @Override + public long offset() { + return offset; + } + +} diff --git a/src/main/java/org/scijava/io/handle/StreamHandle.java b/src/main/java/org/scijava/io/handle/StreamHandle.java new file mode 100644 index 000000000..64902d404 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/StreamHandle.java @@ -0,0 +1,192 @@ +/* + * #%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, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * 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.handle; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. + * + * @author Curtis Rueden + * @author Melissa Linkert + * @author Gabriel Einsdorf + */ +public interface StreamHandle extends DataHandle { + + // -- StreamHandle methods -- + + /** + * Gets an input stream for reading data, positioned at the current offset. + * + * @return the appropriate input stream, or null if the handle is write-only. + * @throws IOException + */ + InputStream in() throws IOException; + + /** + * Gets an output stream for writing data, positioned at the current offset. + * + * @return the appropriate output stream, or null if the handle is read-only. + */ + OutputStream out() throws IOException; + + /** + * Sets the offset of the handle to the given position. + *

+ * This method is intended to be called only in conjunction with reading from + * the input stream, or writing to the output stream. Otherwise, the contents + * may get out of sync. + *

+ */ + void setOffset(long offset); + + /** + * Increments the handle's offset by the given amount. + *

+ * This method is intended to be called only in conjunction with reading from + * the input stream, or writing to the output stream. Otherwise, the contents + * may get out of sync. + *

+ */ + default void advance(final long bytes) throws IOException { + setOffset(offset() + bytes); + } + + // -- DataHandle methods -- + + @Override + default void seek(final long pos) throws IOException { + if (pos == offset()) return; + if (pos > offset()) { + jump(pos - offset()); + } + else { + throw new UnsupportedOperationException( + "Can't seek backwards through this StreamHandle"); + } + } + + /** + * Resets the stream to its start. + * + * @throws IOException If something goes wrong with the reset + */ + void resetStream() throws IOException; + + default void jump(final long n) throws IOException, EOFException { + long remain = n; + while (remain > 0) { + final long r = in().skip(remain); + if (r < 0) throw new EOFException(); + remain -= r; + } + } + + @Override + default void ensureReadable(final long count) throws IOException { + if (in() == null) throw new IOException("This handle is write-only."); + DataHandle.super.ensureReadable(count); + } + + @Override + default boolean ensureWritable(final long count) throws IOException { + if (out() == null) throw new IOException("This handle is read-only."); + return DataHandle.super.ensureWritable(count); + } + + @Override + default int read() throws IOException { + ensureReadable(0); + final int v = in().read(); + if (v >= 0) advance(1); + return v; + } + + @Override + default byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) throw new EOFException(); + return (byte) (ch); + } + + @Override + default int read(final byte[] b, final int off, final int len) + throws IOException + { + final int n = in().read(b, off, len); + if (n >= 0) advance(n); + return n; + } + + // -- DataOutput methods -- + + @Override + default void write(final int v) throws IOException { + ensureWritable(1); + out().write(v); + advance(1); + } + + @Override + default void writeByte(int v) throws IOException { + write(v); + } + + @Override + default void write(final byte[] b, final int off, final int len) + throws IOException + { + ensureWritable(len); + out().write(b, off, len); + advance(len); + } + + // -- Closeable methods -- + + @Override + default void close() throws IOException { + // TODO: Double check this logic. + try (final InputStream in = in()) { + if (in != null) in.close(); + } + try (final OutputStream out = out()) { + if (out != null) out.close(); + } + } + +} From 2f54eed4f0516506dd521dabadee9304e17f5f58 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 14 Mar 2017 18:39:16 +0100 Subject: [PATCH 4/6] Add ResettableStreamHandle + SeekableStreamHandle These subinterfaces of StreamHandle provide random access over the contained streams. --- .../handle/AbstractSeekableStreamHandle.java | 105 ++++++++++++++++++ .../io/handle/ResettableStreamHandle.java | 72 ++++++++++++ .../io/handle/SeekableStreamHandle.java | 53 +++++++++ 3 files changed, 230 insertions(+) create mode 100644 src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java create mode 100644 src/main/java/org/scijava/io/handle/ResettableStreamHandle.java create mode 100644 src/main/java/org/scijava/io/handle/SeekableStreamHandle.java diff --git a/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java b/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java new file mode 100644 index 000000000..4015deb68 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java @@ -0,0 +1,105 @@ +/* + * #%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, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * 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.handle; + +import java.io.IOException; + +import org.scijava.io.location.Location; + +public abstract class AbstractSeekableStreamHandle extends + AbstractStreamHandle implements SeekableStreamHandle +{ + + private long jumpCutoff = 10000; + + @Override + public void seek(final long pos) throws IOException { + + // how much and which direction we have to jump + final long delta = pos - offset(); + + if (delta == 0) { + return; + // nothing to do + } + else if (delta > 0) { + // offset position is "downstream" + + // try to reconnect instead of linearly reading large chunks + if (recreatePossible() && delta > jumpCutoff) { + recreateStreamFromPos(pos); + } + else { + jump(delta); + } + + } + else { // delta < 0 + // need to recreate the stream + if (recreatePossible()) { + recreateStreamFromPos(pos); + } + else { + resetStream(); + jump(pos); + } + } + setOffset(pos); + } + + /** + * Recreates the internal input stream available through {@link #in()}, so + * that it starts from the specified position. + * + * @param pos + * @throws IOException + */ + protected abstract void recreateStreamFromPos(long pos) throws IOException; + + /** + * In some implementations of this class, the ability to recreate the stream + * depends on external factors (e.g. server support). This influences a + * + * @return if recreate is actually possible. + * @throws IOException + */ + protected abstract boolean recreatePossible() throws IOException; + + /** + * Sets the maximum of bytes which are read from the stream when seeking + * forward. Any larger number will result in a call to + * {@link #recreateStreamFromPos(long)}. + */ + protected void setJumpCutoff(long jumpCutoff) { + this.jumpCutoff = jumpCutoff; + } +} diff --git a/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java b/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java new file mode 100644 index 000000000..b22f17c1a --- /dev/null +++ b/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java @@ -0,0 +1,72 @@ +/* + * #%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, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * 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.handle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. Supports resetting the handle to the start of the + * internal stream(s). + */ +public interface ResettableStreamHandle extends + StreamHandle +{ + + @Override + default void seek(final long pos) throws IOException { + final long off = offset(); + if (pos == off) return; // nothing to do + if (pos > off) { + // jump from the current offset + jump(pos - off); + } + else { + // jump from the beginning of the stream + resetStream(); + jump(pos); + } + setOffset(pos); + } + + /** + * Resets the stream to its start. + * + * @throws IOException If something goes wrong with the reset + */ + @Override + void resetStream() throws IOException; +} diff --git a/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java b/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java new file mode 100644 index 000000000..3ad97e581 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java @@ -0,0 +1,53 @@ +/* + * #%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, and Max Planck + * Institute of Molecular Cell Biology and Genetics. + * %% + * 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.handle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. Supports seeking to an arbitrary position within the + * stream. + * + * @author Gabriel Einsdorf + */ +public interface SeekableStreamHandle extends + ResettableStreamHandle +{ + + @Override + void seek(long pos) throws IOException; +} From c56df67d2a1b1a90104f7a5881cc6398b5c278c6 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 16 Oct 2018 16:56:10 +0200 Subject: [PATCH 5/6] DataHandleTest: allow for handles with unknown length Some handles don't know their length and instead return -1, this commit adds an option to the length check to assure this. --- src/test/java/org/scijava/io/handle/DataHandleTest.java | 7 ++++--- .../org/scijava/io/handle/ReadBufferDataHandleTest.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/scijava/io/handle/DataHandleTest.java b/src/test/java/org/scijava/io/handle/DataHandleTest.java index 7c38e3e1a..859456ebe 100644 --- a/src/test/java/org/scijava/io/handle/DataHandleTest.java +++ b/src/test/java/org/scijava/io/handle/DataHandleTest.java @@ -113,7 +113,7 @@ public void testEndianesSettings() throws IOException { @Test public void testReading() throws IOException { try (final DataHandle handle = createHandle()) { - checkBasicReadMethods(handle); + checkBasicReadMethods(handle, true); checkEndiannessReading(handle); } } @@ -168,13 +168,14 @@ public void populateData(final OutputStream out) throws IOException { * Checks basic byte reading methods. * * @param handle the handle to test + * @param lengthKnown whether the length of the handle is know * @throws IOException */ public void checkBasicReadMethods( - final DataHandle handle) throws IOException + final DataHandle handle, boolean lengthKnown) throws IOException { assertEquals(0, handle.offset()); - assertEquals(BYTES.length, handle.length()); + assertEquals(lengthKnown ? BYTES.length : -1, handle.length()); assertEquals("UTF-8", handle.getEncoding()); // test read() diff --git a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java index e7cb332f2..99d04e3fb 100644 --- a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java +++ b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java @@ -64,7 +64,7 @@ public void testSmallBuffer() throws IOException { new ReadBufferDataHandle(handle, 5)) { // check with small buffersize - checkBasicReadMethods(bufferedHandle); + checkBasicReadMethods(bufferedHandle, true); checkEndiannessReading(bufferedHandle); } } From 183c2e4ba897f1c44405df5ac8c6349a69f294f9 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 16 Oct 2018 16:57:07 +0200 Subject: [PATCH 6/6] Remove @Ignore from empty tests --- .../java/org/scijava/io/handle/ReadBufferDataHandleTest.java | 1 - .../java/org/scijava/io/handle/WriteBufferDataHandleTest.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java index 99d04e3fb..9f05bd38b 100644 --- a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java +++ b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java @@ -127,7 +127,6 @@ public void testLargeRead() throws Exception { } @Test - @Ignore @Override public void testWriting() throws IOException { // nothing to do here diff --git a/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java b/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java index 40f035e3a..6ea468872 100644 --- a/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java +++ b/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java @@ -64,14 +64,12 @@ public DataHandle createHandle() { } @Test - @Ignore @Override public void testReading() throws IOException { // nothing to do } @Test - @Ignore @Override public void checkSkip() throws IOException { // nothing to do