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 (bufferLimit < 0) {
if (!buffer.hasRemaining()) {
if (fill() < 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,30 +109,26 @@ public final class DecoderStream extends FilterInputStream {
}
// End of file?
if ((bufferLimit - bufferPos) < 0) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
return -1;
}
}
// Read until we have read pLength bytes, or have reached EOF
int count = 0;
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) {

View File

@ -33,6 +33,7 @@ import com.twelvemonkeys.io.enc.Decoder;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* LempelZivWelch (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);
}
}
}

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