/*
* LittleDecoder.java
* Transform
*
* Copyright (c) 2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* 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 OWNER 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.
*/
package com.flagstone.transform.coder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
/**
* LittleDecoder wraps an InputStream with a buffer to reduce the amount of
* memory required to decode an image or sound and to improve efficiency by
* reading data from a file or external source in blocks. Word data - shorts
* and ints - are read in Little-Endian format with the least significant byte
* decoded first.
*/
@SuppressWarnings("PMD.TooManyMethods")
public final class LittleDecoder {
/** The default size, in bytes, for the internal buffer. */
public static final int BUFFER_SIZE = 4096;
/** Bit mask applied to bytes when converting to unsigned integers. */
private static final int BYTE_MASK = 255;
/** Number of bits in an int. */
private static final int BITS_PER_INT = 32;
/** Number of bits in a byte. */
private static final int BITS_PER_BYTE = 8;
/** Number of bytes in an int. */
private static final int BYTES_PER_INT = 4;
/** Left shift to convert number of bytes to number of bits. */
private static final int BYTES_TO_BITS = 3;
/** Right shift to convert number of bits to number of bytes. */
private static final int BITS_TO_BYTES = 3;
/** Number of bits to shift when aligning a value to the second byte. */
private static final int TO_BYTE1 = 8;
/** Number of bits to shift when aligning a value to the third byte. */
private static final int TO_BYTE2 = 16;
/** Number of bits to shift when aligning a value to the fourth byte. */
private static final int TO_BYTE3 = 24;
/** The underlying input stream. */
private final transient InputStream stream;
/** The buffer for data read from the stream. */
private final transient byte[] buffer;
/** Stack for storing file locations. */
private final transient Stack<Integer>locations;
/** The position of the buffer relative to the start of the stream. */
private transient int pos;
/** The position from the start of the buffer. */
private transient int index;
/** The offset in bits in the current buffer location. */
private transient int offset;
/** The number of bytes available in the current buffer. */
private transient int size;
/**
* Create a new LittleDecoder for the underlying InputStream with the
* specified buffer size.
*
* @param streamIn the stream from which data will be read.
* @param length the size in bytes of the buffer.
*/
public LittleDecoder(final InputStream streamIn, final int length) {
stream = streamIn;
buffer = new byte[length];
locations = new Stack<Integer>();
pos = 0;
}
/**
* Create a new LittleDecoder for the underlying InputStream using the
* default buffer size.
*
* @param streamIn the stream from which data will be read.
*/
public LittleDecoder(final InputStream streamIn) {
stream = streamIn;
buffer = new byte[BUFFER_SIZE];
locations = new Stack<Integer>();
pos = 0;
}
/**
* Fill the internal buffer. Any unread bytes are copied to the start of
* the buffer and the remaining space is filled with data from the
* underlying stream.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public void fill() throws IOException {
final int diff = size - index;
pos += index;
if (index < size) {
for (int i = 0; i < diff; i++) {
buffer[i] = buffer[index++];
}
}
int bytesRead = 0;
int bytesToRead = buffer.length - diff;
index = diff;
size = diff;
do {
bytesRead = stream.read(buffer, index, bytesToRead);
if (bytesRead == -1) {
bytesToRead = 0;
} else {
index += bytesRead;
size += bytesRead;
bytesToRead -= bytesRead;
}
} while (bytesToRead > 0);
index = 0;
}
/**
* Mark the current position.
* @return the current position.
*/
public int mark() {
return locations.push(pos + index);
}
/**
* Discard the last saved position.
*/
public void unmark() {
locations.pop();
}
/**
* Reposition the decoder to the point recorded by the last call to the
* mark() method.
*
* @throws IOException if the internal buffer was filled after mark() was
* called.
*/
public void reset() throws IOException {
int location;
if (locations.isEmpty()) {
location = 0;
} else {
location = locations.peek();
}
if (location - pos < 0) {
throw new IOException();
}
index = location - pos;
}
/**
* Get the number of bytes read from the last saved position.
*
* @return the number of bytes read since the mark() method was last called.
*/
public int bytesRead() {
return pos + index - locations.peek();
}
/**
* Changes the location to the next byte boundary.
*/
public void alignToByte() {
if (offset > 0) {
index += 1;
offset = 0;
}
}
/**
* Skips over and discards n bytes of data.
*
* @param count the number of bytes to skip.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public void skip(final int count) throws IOException {
if (size - index == 0) {
fill();
}
if (count < size - index) {
index += count;
} else {
int toSkip = count;
int diff;
while (toSkip > 0) {
diff = size - index;
if (toSkip <= diff) {
index += toSkip;
toSkip = 0;
} else {
index += diff;
toSkip -= diff;
fill();
if (size - index == 0) {
throw new ArrayIndexOutOfBoundsException();
}
}
}
}
}
/**
* Checks the number of bytes read since the last mark and moves to the
* next word (32-bit) aligned boundary.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public void alignToWord() throws IOException {
if (size - index < BYTES_PER_INT) {
fill();
}
final int diff = (pos + index) % BYTES_PER_INT;
if (diff > 0) {
index += BYTES_PER_INT - diff;
offset = 0;
} else if (offset > 0) {
index += BYTES_PER_INT;
offset = 0;
}
}
/**
* Read a bit field.
*
* @param numberOfBits
* the number of bits to read.
*
* @param signed
* indicates whether the integer value read is signed.
*
* @return the value read.
*
* @throws IOException if there is an error reading data from the
* underlying stream.
*/
public int readBits(final int numberOfBits, final boolean signed)
throws IOException {
int pointer = (index << BYTES_TO_BITS) + offset;
if (((size << BYTES_TO_BITS) - pointer) < numberOfBits) {
fill();
pointer = (index << BYTES_TO_BITS) + offset;
}
int value = 0;
if (numberOfBits > 0) {
if (pointer + numberOfBits > (size << BYTES_TO_BITS)) {
throw new ArrayIndexOutOfBoundsException();
}
for (int i = BITS_PER_INT; (i > 0)
&& (index < buffer.length); i -= BITS_PER_BYTE) {
value |= (buffer[index++] & BYTE_MASK) << (i - BITS_PER_BYTE);
}
value <<= offset;
if (signed) {
value >>= BITS_PER_INT - numberOfBits;
} else {
value >>>= BITS_PER_INT - numberOfBits;
}
pointer += numberOfBits;
index = pointer >>> BITS_TO_BYTES;
offset = pointer & Coder.LOWEST3;
}
return value;
}
/**
* Read an unsigned byte.
*
* @return an 8-bit unsigned value.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public int readByte() throws IOException {
if (size - index < 1) {
fill();
}
if (index + 1 > size) {
throw new ArrayIndexOutOfBoundsException();
}
return buffer[index++] & BYTE_MASK;
}
/**
* Reads an array of bytes.
*
* @param bytes
* the array that will contain the bytes read.
*
* @return the array of bytes.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public byte[] readBytes(final byte[] bytes) throws IOException {
final int wanted = bytes.length;
int dest = 0;
int read = 0;
int available;
int remaining;
while (read < wanted) {
available = size - index;
remaining = wanted - read;
if (available > remaining) {
available = remaining;
}
System.arraycopy(buffer, index, bytes, dest, available);
read += available;
index += available;
dest += available;
if (index == size) {
fill();
}
}
return bytes;
}
/**
* Read an unsigned 16-bit integer.
*
* @return the value read.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public int readUnsignedShort() throws IOException {
if (size - index < 2) {
fill();
}
if (index + 2 > size) {
throw new ArrayIndexOutOfBoundsException();
}
int value = buffer[index++] & BYTE_MASK;
value |= (buffer[index++] & BYTE_MASK) << TO_BYTE1;
return value;
}
/**
* Read an signed 16-bit integer.
*
* @return the value read.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public int readShort() throws IOException {
if (size - index < 2) {
fill();
}
if (index + 2 > size) {
throw new ArrayIndexOutOfBoundsException();
}
int value = buffer[index++] & BYTE_MASK;
value |= buffer[index++] << TO_BYTE1;
return value;
}
/**
* Read an unsigned 32-bit integer.
*
* @return the value read.
*
* @throws IOException if an error occurs reading from the underlying
* input stream.
*/
public int readInt() throws IOException {
if (size - index < BYTES_PER_INT) {
fill();
}
if (index + 4 > size) {
throw new ArrayIndexOutOfBoundsException();
}
int value = buffer[index++] & BYTE_MASK;
value |= (buffer[index++] & BYTE_MASK) << TO_BYTE1;
value |= (buffer[index++] & BYTE_MASK) << TO_BYTE2;
value |= (buffer[index++] & BYTE_MASK) << TO_BYTE3;
return value;
}
}