diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java index a3ca35b5..04111684 100644 --- a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java @@ -172,14 +172,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme try { return mStream.length(); } - catch (IOException e) { - throw unchecked(e, RuntimeException.class); + catch (IOException ignore) { } - } - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) - private T unchecked(IOException pExcption, Class pClass) { - // Ugly hack to fool the compiler.. - return (T) pExcption; + return -1; } } diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java new file mode 100644 index 00000000..2c4ce03c --- /dev/null +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java @@ -0,0 +1,102 @@ +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.lang.Validate; + +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageInputStreamImpl; +import java.io.IOException; + +/** + * A wrapper for {@link ImageInputStream} to limit the number of bytes that can be read. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: SubImageInputStream.java,v 1.0 Nov 8, 2009 2:50:58 PM haraldk Exp$ + */ +public final class SubImageInputStream extends ImageInputStreamImpl { + // NOTE: This class is based on com.sun.imageio.plugins.common.SubImageInputStream, but fixes some of its bugs. + + private final ImageInputStream mStream; + private final long mStartPos; + private final long mLength; + + /** + * Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream. + * + * @param pStream the underlying stream + * @param pLength the maximum length to read from the stream. + * Note that {@code pStream} may contain less than this maximum number of bytes. + * + * @throws IOException if {@code pStream}'s position can't be determined. + * @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0} + */ + public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException { + Validate.notNull(pStream, "stream"); + if (pLength < 0) { + throw new IllegalArgumentException("length < 0"); + } + + mStream = pStream; + mStartPos = pStream.getStreamPosition(); + mLength = pLength; + } + + public int read() throws IOException { + if (streamPos >= mLength) { // Local EOF + return -1; + } + else { + int read = mStream.read(); + + if (read >= 0) { + streamPos++; + } + + return read; + } + } + + public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (streamPos >= mLength) { // Local EOF + return -1; + } + + // Safe cast, as pLength can never cause int overflow + int length = (int) Math.min(pLength, mLength - streamPos); + int count = mStream.read(pBytes, pOffset, length); + + if (count >= 0) { + streamPos += count; + } + + return count; + } + + @Override + public long length() { + try { + long length = mStream.length(); + return length < 0 ? -1 : Math.min(length - mStartPos, mLength); + } + catch (IOException ignore) { + } + + return -1; + } + + @Override + public void seek(final long pPosition) throws IOException { + if (pPosition < getFlushedPosition()) { + throw new IndexOutOfBoundsException("pos < flushedPosition"); + } + + mStream.seek(mStartPos + pPosition); + streamPos = pPosition; + } + + @SuppressWarnings({"FinalizeDoesntCallSuperFinalize"}) + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call super.finalize() in this case) + } +} diff --git a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java new file mode 100644 index 00000000..665582d8 --- /dev/null +++ b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java @@ -0,0 +1,168 @@ +package com.twelvemonkeys.imageio.stream; + +import junit.framework.TestCase; + +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageInputStreamImpl; +import javax.imageio.stream.MemoryCacheImageInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +/** + * SubImageInputStreamTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: SubImageInputStreamTestCase.java,v 1.0 Nov 8, 2009 3:03:32 PM haraldk Exp$ + */ +public class SubImageInputStreamTestCase extends TestCase { + // TODO: Extract super test case for all stream tests + private final Random mRandom = new Random(837468l); + + private ImageInputStream createStream(final int pSize) { + byte[] bytes = new byte[pSize]; + + mRandom.nextBytes(bytes); + + return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) { + @Override + public long length() { + return pSize; + } + }; + } + + public void testCreateNullStream() throws IOException { + try { + new SubImageInputStream(null, 1); + fail("Expected IllegalArgumentException with null stream"); + } + catch (IllegalArgumentException e) { + } + } + + public void testCreateNegativeLength() throws IOException { + try { + new SubImageInputStream(createStream(0), -1); + fail("Expected IllegalArgumentException with negative length"); + } + catch (IllegalArgumentException e) { + } + } + + public void testCreate() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(11), 7); + + assertEquals(0, stream.getStreamPosition()); + assertEquals(7, stream.length()); + } + + public void testWraphBeyondWrappedLength() throws IOException { + SubImageInputStream stream = new SubImageInputStream(createStream(5), 6); + assertEquals(5, stream.length()); + } + + public void testWrapUnknownLength() throws IOException { + SubImageInputStream stream = new SubImageInputStream(new ImageInputStreamImpl() { + @Override + public int read() throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + + @Override + public long length() { + return -1; + } + }, 6); + + assertEquals(-1, stream.length()); + } + + public void testRead() throws IOException { + ImageInputStream wrapped = createStream(42); + + wrapped.skipBytes(13); + + ImageInputStream stream = new SubImageInputStream(wrapped, 27); + + assertEquals(0, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + stream.read(); + assertEquals(1, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + stream.readFully(new byte[11]); + assertEquals(12, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + assertEquals(25, wrapped.getStreamPosition()); + } + + public void testReadResetRead() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(32), 16); + stream.mark(); + + byte[] first = new byte[16]; + stream.readFully(first); + + stream.reset(); + + byte[] second = new byte[16]; + stream.readFully(second); + + assertTrue(Arrays.equals(first, second)); + } + + public void testSeekNegative() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + try { + stream.seek(-2); + fail("Expected IndexOutOfBoundsException"); + } + catch (IndexOutOfBoundsException expected) { + } + + assertEquals(0, stream.getStreamPosition()); + } + + public void testSeekBeforeFlushedPos() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(3); + stream.flushBefore(3); + + assertEquals(3, stream.getStreamPosition()); + + try { + stream.seek(0); + fail("Expected IndexOutOfBoundsException"); + } + catch (IndexOutOfBoundsException expected) { + } + + assertEquals(3, stream.getStreamPosition()); + } + + public void testSeekAfterEOF() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(6); + + assertEquals(-1, stream.read()); + } + + public void testSeek() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(5); + assertEquals(5, stream.getStreamPosition()); + + stream.seek(1); + assertEquals(1, stream.getStreamPosition()); + } +}