TMC-IOENC: Refactored Decoder to use ByteBuffer instead of byte[] for better readability/simpler code.

This commit is contained in:
Harald Kuhr
2013-09-08 13:39:13 +02:00
parent 9a27f62dec
commit f2ff00580a
9 changed files with 102 additions and 126 deletions

View File

@@ -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();
}
/**

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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) {