From f2ff00580a5d77d85f52fc122b301d5edb1376f0 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Sep 2013 13:39:13 +0200 Subject: [PATCH] TMC-IOENC: Refactored Decoder to use ByteBuffer instead of byte[] for better readability/simpler code. --- .../io/enc/AbstractRLEDecoder.java | 18 ++--- .../twelvemonkeys/io/enc/Base64Decoder.java | 43 ++++------ .../com/twelvemonkeys/io/enc/Decoder.java | 5 +- .../twelvemonkeys/io/enc/DecoderStream.java | 81 ++++++++----------- .../io/enc/PackBits16Decoder.java | 19 ++--- .../twelvemonkeys/io/enc/PackBitsDecoder.java | 26 +++--- .../io/enc/DecoderAbstractTestCase.java | 3 +- .../imageio/plugins/tiff/LZWDecoder.java | 28 +++---- .../twelvemonkeys/io/enc/InflateDecoder.java | 5 +- 9 files changed, 102 insertions(+), 126 deletions(-) diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java index 661a2dcf..9a427220 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java @@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format. @@ -86,25 +87,24 @@ abstract class AbstractRLEDecoder implements Decoder { * Decodes as much data as possible, from the stream into the buffer. * * @param pStream the input stream containing RLE data - * @param pBuffer tge buffer to decode the data to + * @param pBuffer the buffer to decode the data to * * @return the number of bytes decoded from the stream, to the buffer * * @throws IOException if an I/O related exception ocurs while reading */ - public final int decode(InputStream pStream, byte[] pBuffer) throws IOException { - int decoded = 0; - - while (decoded < pBuffer.length && dstY >= 0) { + public final int decode(InputStream pStream, ByteBuffer pBuffer) throws IOException { + while (pBuffer.hasRemaining() && dstY >= 0) { // NOTE: Decode only full rows, don't decode if y delta if (dstX == 0 && srcY == dstY) { decodeRow(pStream); } - int length = Math.min(row.length - dstX, pBuffer.length - decoded); - System.arraycopy(row, dstX, pBuffer, decoded, length); + int length = Math.min(row.length - dstX, pBuffer.remaining()); +// System.arraycopy(row, dstX, pBuffer, decoded, length); + pBuffer.put(row, 0, length); dstX += length; - decoded += length; +// decoded += length; if (dstX == row.length) { dstX = 0; @@ -120,7 +120,7 @@ abstract class AbstractRLEDecoder implements Decoder { } } - return decoded; + return pBuffer.position(); } /** diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java index 38179949..dc9f319a 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java @@ -28,9 +28,9 @@ package com.twelvemonkeys.io.enc; -import com.twelvemonkeys.io.FastByteArrayOutputStream; - -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; /** * {@code Decoder} implementation for standard base64 encoding. @@ -62,8 +62,6 @@ public final class Base64Decoder implements Decoder { final static byte[] PEM_CONVERT_ARRAY; private byte[] decodeBuffer = new byte[4]; - private ByteArrayOutputStream wrapped; - private Object wrappedObject; static { PEM_CONVERT_ARRAY = new byte[256]; @@ -93,7 +91,7 @@ public final class Base64Decoder implements Decoder { return pLength; } - protected boolean decodeAtom(final InputStream pInput, final OutputStream pOutput, final int pLength) + protected boolean decodeAtom(final InputStream pInput, final ByteBuffer pOutput, final int pLength) throws IOException { byte byte0 = -1; @@ -147,16 +145,16 @@ public final class Base64Decoder implements Decoder { default: switch (length) { case 2: - pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); break; case 3: - pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); - pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); break; case 4: - pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); - pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); - pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63)); + pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3)); + pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15)); + pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63)); break; } @@ -166,34 +164,23 @@ public final class Base64Decoder implements Decoder { return true; } - void decodeBuffer(final InputStream pInput, final ByteArrayOutputStream pOutput, final int pLength) throws IOException { + public int decode(final InputStream pStream, final ByteBuffer pBuffer) throws IOException { do { int k = 72; int i; for (i = 0; i + 4 < k; i += 4) { - if(!decodeAtom(pInput, pOutput, 4)) { + if(!decodeAtom(pStream, pBuffer, 4)) { break; } } - if (!decodeAtom(pInput, pOutput, k - i)) { + if (!decodeAtom(pStream, pBuffer, k - i)) { break; } } - while (pOutput.size() + 54 < pLength); // 72 char lines should produce no more than 54 bytes - } + while (pBuffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes - public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { - if (wrappedObject != pBuffer) { - // NOTE: Array not cloned in FastByteArrayOutputStream - wrapped = new FastByteArrayOutputStream(pBuffer); - wrappedObject = pBuffer; - } - - wrapped.reset(); // NOTE: This only resets count to 0 - decodeBuffer(pStream, wrapped, pBuffer.length); - - return wrapped.size(); + return pBuffer.position(); } } diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java index 45219c91..0ceda346 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java @@ -28,8 +28,9 @@ package com.twelvemonkeys.io.enc; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; /** * Interface for decoders. @@ -60,5 +61,5 @@ public interface Decoder { * @throws IOException if an I/O error occurs * @throws java.io.EOFException if a premature end-of-file is encountered */ - int decode(InputStream pStream, byte[] pBuffer) throws IOException; + int decode(InputStream pStream, ByteBuffer pBuffer) throws IOException; } diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java index 318ccb86..9a36ed15 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java @@ -28,9 +28,10 @@ package com.twelvemonkeys.io.enc; -import java.io.InputStream; -import java.io.IOException; import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; /** * An {@code InputStream} that provides on-the-fly decoding from an underlying @@ -43,12 +44,12 @@ import java.io.FilterInputStream; * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $ */ public final class DecoderStream extends FilterInputStream { - - protected int bufferPos; - protected int bufferLimit; - protected final byte[] buffer; + protected final ByteBuffer buffer; protected final Decoder decoder; + // TODO: Consider replacing the wrapped input stream with a channel like this + // ReadableByteChannel inChannel = Channels.newChannel(stream); + /** * Creates a new decoder stream and chains it to the * input stream specified by the {@code pStream} argument. @@ -77,29 +78,22 @@ public final class DecoderStream extends FilterInputStream { public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) { super(pStream); decoder = pDecoder; - buffer = new byte[pBufferSize]; - bufferPos = 0; - bufferLimit = 0; + buffer = ByteBuffer.allocate(pBufferSize); + buffer.flip(); } public int available() throws IOException { - return bufferLimit - bufferPos + super.available(); + return buffer.remaining(); } public int read() throws IOException { - if (bufferPos == bufferLimit) { - bufferLimit = fill(); + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return -1; + } } - if (bufferLimit < 0) { - return -1; - } - - return buffer[bufferPos++] & 0xff; - } - - public int read(final byte pBytes[]) throws IOException { - return read(pBytes, 0, pBytes.length); + return buffer.get() & 0xff; } public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException { @@ -115,8 +109,10 @@ public final class DecoderStream extends FilterInputStream { } // End of file? - if ((bufferLimit - bufferPos) < 0) { - return -1; + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return -1; + } } // Read until we have read pLength bytes, or have reached EOF @@ -124,21 +120,15 @@ public final class DecoderStream extends FilterInputStream { int off = pOffset; while (pLength > count) { - int avail = bufferLimit - bufferPos; - - if (avail <= 0) { - bufferLimit = fill(); - - if (bufferLimit < 0) { + if (!buffer.hasRemaining()) { + if (fill() < 0) { break; } } // Copy as many bytes as possible - int dstLen = Math.min(pLength - count, avail); - System.arraycopy(buffer, bufferPos, pBytes, off, dstLen); - - bufferPos += dstLen; + int dstLen = Math.min(pLength - count, buffer.remaining()); + buffer.get(pBytes, off, dstLen); // Update offset (rest) off += dstLen; @@ -152,29 +142,25 @@ public final class DecoderStream extends FilterInputStream { public long skip(final long pLength) throws IOException { // End of file? - if (bufferLimit - bufferPos < 0) { - return 0; + if (!buffer.hasRemaining()) { + if (fill() < 0) { + return 0; // Yes, 0, not -1 + } } // Skip until we have skipped pLength bytes, or have reached EOF long total = 0; while (total < pLength) { - int avail = bufferLimit - bufferPos; - - if (avail == 0) { - bufferLimit = fill(); - - if (bufferLimit < 0) { + if (!buffer.hasRemaining()) { + if (fill() < 0) { break; } } // NOTE: Skipped can never be more than avail, which is // an int, so the cast is safe - int skipped = (int) Math.min(pLength - total, avail); - - bufferPos += skipped; // Just skip these bytes + int skipped = (int) Math.min(pLength - total, buffer.remaining()); total += skipped; } @@ -190,19 +176,20 @@ public final class DecoderStream extends FilterInputStream { * @throws IOException if an I/O error occurs */ protected int fill() throws IOException { + buffer.clear(); int read = decoder.decode(in, buffer); // TODO: Enforce this in test case, leave here to aid debugging - if (read > buffer.length) { + if (read > buffer.capacity()) { throw new AssertionError( String.format( "Decode beyond buffer (%d): %d (using %s decoder)", - buffer.length, read, decoder.getClass().getName() + buffer.capacity(), read, decoder.getClass().getName() ) ); } - bufferPos = 0; + buffer.flip(); if (read == 0) { return -1; diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java index a7b1dde0..a141d30f 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java @@ -28,9 +28,10 @@ package com.twelvemonkeys.io.enc; -import java.io.InputStream; -import java.io.IOException; import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; /** * Decoder implementation for 16 bit-chunked Apple PackBits-like run-length @@ -84,13 +85,13 @@ public final class PackBits16Decoder implements Decoder { * * @throws java.io.IOException */ - public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { + public int decode(final InputStream pStream, final ByteBuffer pBuffer) throws IOException { if (reachedEOF) { return -1; } int read = 0; - final int max = pBuffer.length; + final int max = pBuffer.capacity(); while (read < max) { int n; @@ -126,7 +127,7 @@ public final class PackBits16Decoder implements Decoder { if (n >= 0) { // Copy next n + 1 shorts literally int len = 2 * (n + 1); - readFully(pStream, pBuffer, read, len); + readFully(pStream, pBuffer, len); read += len; } // Allow -128 for compatibility, see above @@ -136,8 +137,8 @@ public final class PackBits16Decoder implements Decoder { byte value2 = readByte(pStream); for (int i = -n + 1; i > 0; i--) { - pBuffer[read++] = value1; - pBuffer[read++] = value2; + pBuffer.put(value1); + pBuffer.put(value2); } } // else NOOP (-128) @@ -160,7 +161,7 @@ public final class PackBits16Decoder implements Decoder { return (byte) read; } - private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + private static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException { if (pLength < 0) { throw new IndexOutOfBoundsException(); } @@ -168,7 +169,7 @@ public final class PackBits16Decoder implements Decoder { int read = 0; while (read < pLength) { - int count = pStream.read(pBuffer, pOffset + read, pLength - read); + int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + read, pLength - read); if (count < 0) { throw new EOFException("Unexpected end of PackBits stream"); diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java index 4140f298..9cb9ad73 100644 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java @@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * Decoder implementation for Apple PackBits run-length encoding. @@ -98,16 +99,13 @@ public final class PackBitsDecoder implements Decoder { * * @throws java.io.IOException */ - public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { + public int decode(final InputStream pStream, final ByteBuffer pBuffer) throws IOException { if (reachedEOF) { return -1; } - int read = 0; - final int max = pBuffer.length; - // TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream... - while (read < max) { + while (pBuffer.hasRemaining()) { int n; if (splitRun) { @@ -126,12 +124,12 @@ public final class PackBitsDecoder implements Decoder { } // Split run at or before max - if (n >= 0 && n + 1 + read > max) { + if (n >= 0 && n + 1 > pBuffer.remaining()) { leftOfRun = n; splitRun = true; break; } - else if (n < 0 && -n + 1 + read > max) { + else if (n < 0 && -n + 1 > pBuffer.remaining()) { leftOfRun = n; splitRun = true; break; @@ -140,9 +138,7 @@ public final class PackBitsDecoder implements Decoder { try { if (n >= 0) { // Copy next n + 1 bytes literally - readFully(pStream, pBuffer, read, n + 1); - - read += n + 1; + readFully(pStream, pBuffer, n + 1); } // Allow -128 for compatibility, see above else if (disableNoop || n != -128) { @@ -150,7 +146,7 @@ public final class PackBitsDecoder implements Decoder { byte value = readByte(pStream); for (int i = -n + 1; i > 0; i--) { - pBuffer[read++] = value; + pBuffer.put(value); } } // else NOOP (-128) @@ -160,7 +156,7 @@ public final class PackBitsDecoder implements Decoder { } } - return read; + return pBuffer.position(); } static byte readByte(final InputStream pStream) throws IOException { @@ -173,7 +169,7 @@ public final class PackBitsDecoder implements Decoder { return (byte) read; } - static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException { if (pLength < 0) { throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength)); } @@ -181,7 +177,7 @@ public final class PackBitsDecoder implements Decoder { int total = 0; while (total < pLength) { - int count = pStream.read(pBuffer, pOffset + total, pLength - total); + int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total); if (count < 0) { throw new EOFException("Unexpected end of PackBits stream"); @@ -189,5 +185,7 @@ public final class PackBitsDecoder implements Decoder { total += count; } + + pBuffer.position(pBuffer.position() + total); } } diff --git a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java index 70dcfa04..51788010 100644 --- a/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java +++ b/common/common-io/src/test/java/com/twelvemonkeys/io/enc/DecoderAbstractTestCase.java @@ -5,6 +5,7 @@ import com.twelvemonkeys.lang.ObjectAbstractTestCase; import org.junit.Test; import java.io.*; +import java.nio.ByteBuffer; import static org.junit.Assert.*; @@ -39,7 +40,7 @@ public abstract class DecoderAbstractTestCase extends ObjectAbstractTestCase { ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]); try { - int count = decoder.decode(bytes, new byte[128]); + int count = decoder.decode(bytes, ByteBuffer.allocate(128)); assertEquals("Should not be able to read any bytes", 0, count); } catch (EOFException allowed) { diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index bf573fa9..789190d6 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -33,6 +33,7 @@ import com.twelvemonkeys.io.enc.Decoder; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; /** * Lempel–Ziv–Welch (LZW) decompression. LZW is a universal loss-less data compression algorithm @@ -94,10 +95,9 @@ abstract class LZWDecoder implements Decoder { maxString = 1; } - public int decode(final InputStream stream, final byte[] buffer) throws IOException { + public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ - int bufferPos = 0; int code; while ((code = getNextCode(stream)) != EOI_CODE) { @@ -109,30 +109,30 @@ abstract class LZWDecoder implements Decoder { break; } - bufferPos += table[code].writeTo(buffer, bufferPos); + table[code].writeTo(buffer); } else { if (isInTable(code)) { - bufferPos += table[code].writeTo(buffer, bufferPos); + table[code].writeTo(buffer); addStringToTable(table[oldCode].concatenate(table[code].firstChar)); } else { String outString = table[oldCode].concatenate(table[oldCode].firstChar); - bufferPos += outString.writeTo(buffer, bufferPos); + outString.writeTo(buffer); addStringToTable(outString); } } oldCode = code; - if (bufferPos >= buffer.length - maxString - 1) { + if (buffer.remaining() < maxString + 1) { // Buffer full, stop decoding for now break; } } - return bufferPos; + return buffer.position(); } private void addStringToTable(final String string) throws IOException { @@ -301,24 +301,24 @@ abstract class LZWDecoder implements Decoder { return new String(firstChar, this.firstChar, length + 1, this); } - public final int writeTo(final byte[] buffer, final int offset) { + public final void writeTo(final ByteBuffer buffer) { if (length == 0) { - return 0; + return; } - else if (length == 1) { - buffer[offset] = value; - return 1; + if (length == 1) { + buffer.put(value); } else { String e = this; + final int offset = buffer.position(); for (int i = length - 1; i >= 0; i--) { - buffer[offset + i] = e.value; + buffer.put(offset + i, e.value); e = e.previous; } - return length; + buffer.position(offset + length); } } } diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java index 78d26ee0..15c9c7a1 100644 --- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java @@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -75,11 +76,11 @@ final class InflateDecoder implements Decoder { buffer = new byte[1024]; } - public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { + public int decode(final InputStream pStream, final ByteBuffer pBuffer) throws IOException { try { int decoded; - while ((decoded = inflater.inflate(pBuffer, 0, pBuffer.length)) == 0) { + while ((decoded = inflater.inflate(pBuffer.array(), pBuffer.arrayOffset(), pBuffer.capacity())) == 0) { if (inflater.finished() || inflater.needsDictionary()) { return 0; }