package com.peterhi.runtime;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* A buffer object that is backed by a {@code byte} array and can be read or written.
* Note that {@link Buffer} supports operations down to {@code bit} level (i.e. reading
* or writing of {@code bit} values).
* @author hytparadisee
*/
public class Buffer {
/**
* A constant representing end of file (EOF) or invalid values.
* This value is {@value #EOF}.
*/
public static final int EOF = -1;
/**
* When storing multi-byte data, big endian stores the most
* significant eight {@code bit}s in the first {@code byte},
* and the second most significant eight {@code bit}s in the
* second {@code byte}, and so on. Java uses big endian.
*/
public static final int BIG_ENDIAN = 0;
/**
* When storing multi-byte data, little endian stores the least
* significant (i.e. the smallest) eight {@code bit}s in the
* first {@code byte}, and the second least significant (the
* second smallest) eight {@code bit}s in the second
* {@code byte}, and so on.
*/
public static final int LITTLE_ENDIAN = 1;
private static final BigInteger TWO = BigInteger.valueOf(2);
/**
* The default capacity, in {@code byte}s, that the buffer holds data of.
* The default value is {@value #CAPACITY_DEFAULT}.
*/
public static final int CAPACITY_DEFAULT = 512;
/**
* The default capacity to grow by if the buffer is to be full.
* The default value is {@value #CAPACITY_INCREMENT}.
*/
public static final int CAPACITY_INCREMENT = 128;
/**
* The default behavior used to copy data from this {@link Buffer}
* object to a {@code byte} array. This behavior will copy the data range
* between the read cursor and the write cursor. Since both cursors
* are specified in {@code bit}s, but the data return are in {@code bytes},
* the default behavior will discard any preceding bits before the read
* cursor at of the starting byte and the remaining bits after the write
* cursor at of the ending byte, and do a copy from the {@code byte} index at
* which the reading cursor is located, to the {@code byte} index at which
* the writing cursor is located. The data returned will contain the bits
* INCLUDING the offset/shift information. For example, the bits
* {@code 11111111 11111111} whose read cursor is four and write cursor is
* fourteen, once copied using this method, will result in the data
* {@code 00111111 11110000 } returned. Compare this with
* {@link #COPY_METHOD_ALIGNED}.
*
* @see #COPY_METHOD_ALIGNED
*/
public static final int COPY_METHOD_DEFAULT = 0;
/**
* This behavior is similar to {@link #COPY_METHOD_DEFAULT}, but the
* {@code bit}s offset/shift information will no longer be kept and
* the bits will always be aligned into byte boundaries. For example,
* the bits {@code 11111111 11111111} whose read cursor is four and
* write cursor is fourteen, the data returned will first be extracted
* to what looks like the returned value using the behavior
* {@link #COPY_METHOD_DEFAULT} (of which the value will be
* {@code 00111111 11110000}), and then the bits will be shifted to
* {@code 00000011 11111111} so that the first bit returned is always
* the starting bit of the data (i.e. the read cursor). Compare this
* with {@link #COPY_METHOD_DEFAULT}
*
* @see #COPY_METHOD_DEFAULT
*/
public static final int COPY_METHOD_ALIGNED = 1;
/**
* This behavior will copy all the data in the {@link Buffer} object,
* ignoring the fact that some parts of the data have already been
* read. Since the read cursor is ignored in this method. The data
* being copied will always start at index zero (i.e. no more problem
* with bit offsetting/shifting etc.).
*/
public static final int COPY_METHOD_ALL_DATA = 2;
/**
* Looks up the available or supported endiannesses the
* {@link Buffer} class has according to the value passed
* in as {@code byteEndianness}, and returns whether this
* value is valid.
*
* @param byteEndianness The endianness that is to be
* validated.
*
* @return {@code true} if the endianness is found,
* available or supported. Otherwise {@code false}.
*
* @see #BIG_ENDIAN
* @see #LITTLE_ENDIAN
*/
public static boolean isValidEndianness(int byteEndianness) {
if (byteEndianness == BIG_ENDIAN) {
return true;
}
if (byteEndianness == LITTLE_ENDIAN) {
return true;
}
return false;
}
private byte[] backingData;
private long readPos;
private long writePos;
private long markPos = EOF;
/**
* Creates a new instance of {@link Buffer} object with the specified capacity.
*
* @param capacityInBytes The capacity, in {@code byte}s, that this {@link Buffer}
* object initially can hold data of.
*
* @throws IllegalArgumentException one of the following happens:
* <ul>
* <li>{@code capacityInBytes} is negative.</li>
* <li>{@code capacityInBytes} is zero.</li>
* </ul>
*/
public Buffer(int capacityInBytes)
throws IllegalArgumentException {
if (capacityInBytes < 0) {
throw new IllegalArgumentException();
}
if (capacityInBytes == 0) {
throw new IllegalArgumentException();
}
backingData = new byte[capacityInBytes];
}
/**
* Creates a new instance of {@link Buffer} object with the capacity
* defined by {@link #CAPACITY_DEFAULT}.
*
* @see #Buffer(int)
*/
public Buffer() {
this(CAPACITY_DEFAULT);
}
/**
* Creates a new instance of {@link Buffer} object from existent data.
* The data will be copied into the {@link Buffer}'s backing byte array.
*
* @param existingData A byte array containing the outside data to be copied into.
*
* @throws NullPointerException {@code existingData} is null.
* @throws IllegalArgumentException length of {@code existingData} is zero.
*
* @see #Buffer(byte[], int, int)
*/
public Buffer(byte[] existingData)
throws NullPointerException,
IllegalArgumentException {
if (existingData == null) {
throw new NullPointerException();
}
int length = existingData.length;
if (length == 0) {
throw new IllegalArgumentException();
}
backingData = Arrays.copyOf(existingData, length);
writePos = Byte.SIZE * length;
}
/**
* Creates a new instance of {@link Buffer} object from existent data with a range.
*
* @param existingData A {@code byte} array containing the outside data to be copied into.
* @param byteOffset The index at which to start copying.
* @param numOfBytes The number of {@code byte}s to copy.
*
* @throws NullPointerException {@code existingData} is null.
* @throws IllegalArgumentException one of the following happens:
* <ul>
* <li>the length of {@code existingData} is zero.</li>
* <li>{@code byteOffset} is negative.</li>
* <li>{@code numOfBytes} is negative.</li>
* <li>{@code numOfBytes} is zero.</li>
* <li>the range defined by {@code byteOffset} and {@code numOfBytes} is invalid <br />
* e.g. ({@code byteOffset} + {@code numOfBytes}) > the length of {@code existingData}.</li>
* </ul>
*/
public Buffer(byte[] existingData, int byteOffset, int numOfBytes)
throws NullPointerException,
IllegalArgumentException {
if (existingData == null) {
throw new NullPointerException();
}
if (existingData.length == 0) {
throw new IllegalArgumentException();
}
if (byteOffset < 0) {
throw new IllegalArgumentException();
}
if (numOfBytes < 0) {
throw new IllegalArgumentException();
}
if (numOfBytes == 0) {
throw new IllegalArgumentException();
}
if (byteOffset + numOfBytes > existingData.length) {
throw new IllegalArgumentException();
}
int from = byteOffset;
int to = from + numOfBytes;
backingData = Arrays.copyOfRange(existingData, from, to);
writePos = Byte.SIZE * numOfBytes;
}
/**
* <p>Creates a new instance of {@link Buffer} object from existent data with a {@code bit} precision range.
* Note that because {@code bit} level operation on manipulating data in computing is not native operation
* (as compared with {@code byte} level operations), {@code bit}wise alignment might occur.</p>
*
* <p>{@code Bit}wise alignment is necessary as modern computers usually represent data
* as {@code bytes} but not {@code bits}. So data will be misaligned if the data is taken in the middle
* of a single {@code byte}. The parameter {@code alignBits} tells {@link Buffer} whether to shift
* the starting {@code bit}, specified by {@code bitOffset}, to be the first {@code bit} of this {@code Buffer}'s
* first {@code bit}.</p>
*
* <p>The following example illustrate the use of {@code alignBits}:</p>
*
* <p>Let's say {@code existingData} is defined as {@code ((byte )63)}, which
* in two's complement is represented as {@code 00111111}. If {@code bitOffset}
* is defined in such a way that is not divisible by {@link Byte#SIZE}, then it
* means an incomplete {@code byte} data will be copied into this {@code Buffer}.
* Let's assume {@code bitOffset} is four. This means we will start copying data
* from the fourth {@code bit} in the first {@code byte}. Therefore the preceding {@code 00001111}
* will be discarded, leaving only {@code 00110000}. If {@code alignBits} is
* {@code true}, {@code 00110000} will be shifted so that {@code bitOffset} will
* become the first {@code bit} of the first {@code byte} of {@link Buffer}'s data. In this case,
* {@code 00110000} will shift right} by four {@code bit}s to become
* {@code xxxx0011}, with {@code xxxx} representing the data from subsequent {@code byte}s which
* will follow in. If {@code alignBits} is {@code false}, then no shift will be done,
* and {@code bitOffset} will keep its bit position. So it will stay to be {@code 00110000}.</p>
*
* <p>The graphical illustration is shown below:</p>
*
* <p>{@code existingData = 11111111 11111111}</p>
*
* <p>if {@code bitOffset} is {@code 4} and if {@code numOfBits} is {@code 10} then, we will see
* that the portion of data we need to take is shown in the following within square brackets
* {@code '[]'}</p>
*
* <p>{@code existingData = 11[111111 1111]1111}</p>
*
* <p>Now we are going to take the data specified within {@code '[]'}. If {@code alignBits} is true,
* the created {@link Buffer}'s internal representation of bits will be: </p>
*
* <p>{@code 11[111111 1111]1111} original<br/>
* {@code 00[111111 1111]0000} cleared out-of-range bits<br/>
* {@code 000000[11 11111111]} shift to the first bit to align</p>
*
* <p>If {@code alignBits} is set to {@code false}, the last step of shift-aligning will be ignored,
* and the bits will stay where they used to be, so the created {@link Buffer}'s internal
* representation of bits will be: </p>
*
* <p>{@code 11[111111 1111]1111} original<br/>
* {@code 00[111111 1111]0000} cleared out-of-range bits<br/>
* {@code 00[111111 11110000]} no shift-align done, bits stay where they are</p>
*
* @param existingData A {@code byte} array containing the outside data to be copied into.
* @param bitOffset The index at which to start copying.
* @param numOfBits The number of {@code bit}s to copy.
* @param alignBits {@code true} to shift {@code bit}s defined by {@code bitOffset} to be the first
* {@code bit} of the {@link Buffer}'s data. {@code false} to keep the {@code bit} position of
* {@code bitOffset}.
*
* @throws NullPointerException {@code existingData} is null.
* @throws IllegalArgumentException one of the following happens:
* <ul>
* <li>the length of {@code existingData} is zero.</li>
* <li>{@code bitOffset} is negative.</li>
* <li>{@code numOfBits} is negative.</li>
* <li>{@code numOfBits} is zero.</li>
* <li>the range defined by {@code bitOffset} and {@code numOfBytes} is invalid <br />
* e.g. ({@code bitOffset} + {@link Byte#SIZE} x {@code numOfBytes}) >
* {@link Byte#SIZE} x the length of {@code existingData}.</li>
* </ul>
* @throws IOException the underlying I/O operation fails while trying to copy {@code existingData}
* into the {@link Buffer}'s backing data.
*/
public Buffer(byte[] existingData, long bitOffset, long numOfBits, boolean alignBits)
throws NullPointerException,
IllegalArgumentException,
IOException {
if (existingData == null) {
throw new NullPointerException();
}
if (existingData.length == 0) {
throw new IllegalArgumentException();
}
if (bitOffset < 0) {
throw new IllegalArgumentException();
}
if (numOfBits < 0) {
throw new IllegalArgumentException();
}
if (numOfBits == 0) {
throw new IllegalArgumentException();
}
if (bitOffset + numOfBits > Byte.SIZE * existingData.length) {
throw new IllegalArgumentException();
}
if (alignBits == false) {
int bytesToSkip = (int )(bitOffset % Byte.SIZE);
skip(bytesToSkip);
}
if (alignBits) {
backingData = alignAndCopyByteArray(existingData, bitOffset, numOfBits);
writePos = numOfBits;
} else {
long endBitIndex = bitOffset + numOfBits;
int startByteIndex = (int )(bitOffset / Byte.SIZE);
int endByteIndex = (int )(endBitIndex / Byte.SIZE);
int startBitOffsetAtFirstByte = (int )(bitOffset % Byte.SIZE);
int endBitOffsetAtLastByte = (int )(endBitIndex % Byte.SIZE);
boolean needOneMoreByteForRemainingBitsAfterLastByte =
endBitOffsetAtLastByte != 0;
int numOfBytesNeededForData = endByteIndex - startByteIndex;
if (needOneMoreByteForRemainingBitsAfterLastByte) {
numOfBytesNeededForData = numOfBytesNeededForData + 1;
}
int from = startByteIndex;
int to = from + numOfBytesNeededForData;
backingData = Arrays.copyOfRange(existingData, from, to);
if (startBitOffsetAtFirstByte != 0) {
for (int shift = 0; shift < startBitOffsetAtFirstByte; shift = shift + 1) {
int mask = 0x01;
int bitValueShifted = mask << shift;
int bitValueShiftedAndFlipped = ~bitValueShifted;
int bitsOfFirstByte = backingData[0];
int bitsOfFirstByteWithBitsRemoved = bitsOfFirstByte & bitValueShiftedAndFlipped;
backingData[0] = (byte )bitsOfFirstByteWithBitsRemoved;
}
}
if (endBitOffsetAtLastByte != 0) {
for (int shift = endBitOffsetAtLastByte; shift < Byte.SIZE; shift = shift + 1) {
int mask = 0x01;
int bitValueShifted = mask << shift;
int bitValueShiftedAndFlipped = ~bitValueShifted;
int bitsOfLastByte = backingData[backingData.length - 1];
int bitsOfLastByteWithBitsRemoved = bitsOfLastByte & bitValueShiftedAndFlipped;
backingData[backingData.length - 1] = (byte )bitsOfLastByteWithBitsRemoved;
}
}
writePos = Byte.SIZE * (endByteIndex + startByteIndex) +
endBitOffsetAtLastByte;
}
}
/**
* Gets the number of {@code bit}s that are currently available for reading.
* Reading data beyond this number will, depending on the
* methods you call, end up either returning an {@link #EOF},
* or throwing a {@link InsufficientBufferException} or
* a {@link EOFException}.
*
* @return The number of {@code bit}s that are currently available for reading.
*/
public long readable() {
long readableBits = writePos - readPos;
return readableBits;
}
/**
* Gets the number of {@code bit}s that are currently available for writing,
* without the need to re-allocate an expanded data buffer to keep
* the data. Please note that this method doesn't not indicate a
* hard limit to the amount of data that can be written, since
* {@link Buffer} does not actually have a limit to the number
* of {@code bit}s to write.
*
* @return The number of {@code bit}s that can be written before this
* {@link Buffer} re-alloc-expands itself.
*/
public long writable() {
long writableBits = (long )Byte.SIZE * (long )backingData.length;
writableBits = writableBits - writePos;
return writableBits;
}
/**
* Gets the number of {@code bit}s that are already written into this
* {@link Buffer}. This number has nothing to do with how much
* data has been read. In other words, no matter how much data
* is read out from the {@link Buffer}, this number stays unchanged
* unless new data arrive and are being written.
* @return The number of bits written.
*/
public long written() {
long writtenBits = writePos;
return writtenBits;
}
/**
* Gets the number of {@code bytes} that this {@link Buffer}'s backing
* data currently allocates. The actual data kept might be
* smaller than this number.
* @return The number of {@code byte}s it contains as the buffer.
*/
public int size() {
int bufferSize = backingData.length;
return bufferSize;
}
/**
* Performs a single {@code bit} read operation, and moves
* the read cursor to the next {@code bit}. If no more data
* is available, returns {@link #EOF}. Please note that this
* method will not throw an or an {@link EOFException} to
* indicate out-of-data situation.
*
* @return The {@code bit} being read, represented by {@code 1 (true)}
* or {@code 0 (false)}. If no more data is available, it
* may also return {@link #EOF}.
* @throws IOException the underlying I/O operation fails.
*/
public int read()
throws IOException {
if (readable() == 0) {
return EOF;
}
long bitOffset = readPos;
int atByte = (int )(bitOffset / Byte.SIZE);
int atBitOfByte = (int )(bitOffset % Byte.SIZE);
int shiftRight = atBitOfByte;
int bitValue = backingData[atByte] >>> shiftRight;
int bitMask = 0x01;
bitValue = bitValue & bitMask;
readPos = readPos + 1;
return bitValue;
}
/**
* <p>Performs a single {@code bit} read operation, and moves
* the read cursor to the next {@code bit}. The return
* value is a {@code boolean} value instead of an {@code int}
* value. Since the return value cannot return an {@link #EOF}
* value, an {@link EOFException} will be thrown if no more
* data is available.</p>
*
* <p>This method will delegate to {@link #read()} and convert
* the return value to the corresponding {@code boolean} value.</p>
*
* @return {@code true} if the data being read is non-{@code zero},
* otherwise, {@code false} if the data being read is
* {@code zero}.
*
* @throws EOFException There is no more data available for reading.
* @throws IOException if the underlying {@code Buffer} operation
* throws an error.
*/
public boolean readAsBoolean()
throws EOFException,
IOException {
int bitValue = read();
if (bitValue == EOF) {
throw new EOFException();
}
return bitValue != 0;
}
/**
* Performs a single {@code bit} write operation, and moves the
* write cursor to the next {@code bit}. If there is currently
* no more room to store the data to write (as defined in
* {@link #writable()}), re-alloc-expands the {@link Buffer}'s
* backing array to adapt to it.
* @param data the {@code bit} being written, represented by {@code 1 (true)}
* or {@code 0 (false)}.
* @throws IllegalArgumentException the parameter {@code data}'s value is
* neither {@code 1 (one)} nor {@code 0 (zero)}.
* @throws IOException the underlying I/O operation fails.
*/
public void write(int data)
throws IllegalArgumentException,
IOException {
if (data != 0 &&
data != 1) {
throw new IllegalArgumentException();
}
if (writable() == 0) {
int numOfBytesForNewBuffer = size() + CAPACITY_INCREMENT;
backingData = Arrays.copyOf(backingData, numOfBytesForNewBuffer);
}
long bitOffset = writePos;
int atByte = (int )(bitOffset / Byte.SIZE);
int atBitOfByte = (int )(bitOffset % Byte.SIZE);
int shiftLeft = atBitOfByte;
int bitMask = 0x01;
int bitMaskShifted = bitMask << shiftLeft;
if (data == 0) {
backingData[atByte] &= ~bitMaskShifted;
} else {
backingData[atByte] |= bitMaskShifted;
}
writePos = writePos + 1;
}
/**
* Performs a single {@code bit} write operation, and moves the
* write cursor to the next {@code bit}. It takes a {@code boolean}
* parameter instead of a {@code byte} to represent the data. If
* there is currently no more room to store the data to write
* (as defined in {@link #writable()}), re-alloc-expands the
* {@link Buffer}'s backing array to adapt to it.
*
* @param data {@code true} to write {@code 1 (one)} as a {@code bit}
* into the {@link Buffer}. Otherwise, use {@code false} to write
* {@code 0 (zero)} as a {@code bit} into the {@link Buffer}.
*
* @throws IOException the underlying I/O operation fails.
*/
public void writeAsBoolean(boolean data)
throws IOException {
if (data) {
write(1);
} else {
write(0);
}
}
/**
* Performs an arbitrary multi-{@code bit} read operation, and moves
* the read cursor by {@code numOfBits} bits. This method returns
* a {@link BigInteger} representing the {@code bit}s. The {@link BigInteger}
* returned is always unsigned unless {@code emulateOverflow} is {@code true}.
* If no more data is available, this method throws a {@code EOFException}.
* If the amount of data available is less than the required data length
* (that is, less than {@code numOfBits}), this method throws a
* {@code InsufficientBufferException}. This method supports certain
* byte endiannesses.
* @param numOfBits The number of {@code bit}s to read.
* @param emulateOverflow {@code true} to emulate field overflow, so that if
* the value is greater than the maximum allowed value, defined as
* {@code (2 ^ n - 1) / 2}, the value is then immediately subtracted
* by {@code 2 ^ n} to emulate an overflow situation. If emulateOverflow
* is {@code true}, the returned {@link BigInteger} is emulated as a
* signed value with an overflow. If {@code false} is specified,
* the {@link BigInteger} will not be processed by the field overflow
* mechanism, and will always be unsigned.
* @param byteEndianness The {@code byte} level endianness used to represent
* the order in which different values are stored as bytes. Refer to
* constants starting with {@code ENDIANNESS_*}. By default, java uses
* {@link #BIG_ENDIAN}. Please note that this parameter does not
* affect the {@code bit} endianness, as {@code bit} endianness is
* assumed universal across platforms and languages.
* @return A {@link BigInteger} representing the {@code bit}s.
* @throws IllegalArgumentException one of the following occurs:
* <ul>
* <li>{@code numOfBits} is negative.</li>
* <li>{@code numOfBits} is zero.</li>
* <li>{@code byteEndianness} is not one of the values defined
* by {@code ENDIANNESS_*} constants.
* See {@link #isValidEndianness(int)}.</li>
* </ul>
* @throws InsufficientBufferException the amount of data available is less
* than the required data length specified by {@code numOfBits}, so
* that a complete read operation of the {@code bits} is not possible.
* @throws EOFException no more data is available (available data is zero).
* @throws IOException other underlying I/O operations fail.
*/
public BigInteger read(int numOfBits, boolean emulateOverflow, int byteEndianness)
throws IllegalArgumentException,
InsufficientBufferException,
EOFException,
IOException {
if (numOfBits < 0) {
throw new IllegalArgumentException();
}
if (numOfBits == 0) {
throw new IllegalArgumentException();
}
if (!isValidEndianness(byteEndianness)) {
throw new IllegalArgumentException();
}
long numOfBitsReadable = readable();
if (numOfBitsReadable == 0) {
throw new EOFException();
}
if (numOfBitsReadable < numOfBits) {
throw new InsufficientBufferException();
}
BigInteger bitsValue = BigInteger.ZERO;
int numOfBytesWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
if (byteEndianness == LITTLE_ENDIAN) {
for (int curByteIndex = 0;
curByteIndex < numOfBytesWithoutFractionalBits;
curByteIndex = curByteIndex + 1) {
for (int curBitShift = 0;
curBitShift < Byte.SIZE;
curBitShift = curBitShift + 1) {
int bitValue = read();
int bitShift = Byte.SIZE * curByteIndex + curBitShift;
bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
}
}
int numOfRemainingBitsAfterLastByte = (int )(numOfBits % Byte.SIZE);
for (int curLastBitShift = 0;
curLastBitShift < numOfRemainingBitsAfterLastByte;
curLastBitShift = curLastBitShift + 1) {
int bitValue = read();
int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curLastBitShift;
bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
}
} else {
int numOfPrecedingBitsBeforeFirstByte = (int )(numOfBits % Byte.SIZE);
for (int curFirstBitShift = 0;
curFirstBitShift < numOfPrecedingBitsBeforeFirstByte;
curFirstBitShift = curFirstBitShift + 1) {
int bitValue = read();
int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curFirstBitShift;
bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
}
for (int curByteIndex = 0;
curByteIndex < numOfBytesWithoutFractionalBits;
curByteIndex = curByteIndex + 1) {
for (int curBitShift = 0;
curBitShift < Byte.SIZE;
curBitShift = curBitShift + 1) {
int bitValue = read();
int bitShift =
Byte.SIZE * (numOfBytesWithoutFractionalBits - curByteIndex - 1) + curBitShift;
bitsValue = setAndUpdateBitsValueBit(bitsValue, bitShift, bitValue);
}
}
}
if (emulateOverflow) {
BigInteger overflowBits = TWO.pow(numOfBits);
bitsValue = bitsValue.subtract(overflowBits);
}
return bitsValue;
}
/**
* Performs an arbitrary multi-{@code bit} write operation, and moves
* the write cursor by {@code numOfBits} bits. This method requires
* a {@link BigInteger} representing the {@code bit}s. The {@link BigInteger}
* passed in is always unsigned. This method supports certain byte endiannesses.
* @param numOfBits The number of {@code bit}s to write.
* @param bitsValue A {@link BigInteger} that contains the {@code bit}s to write.
* @param byteEndianness The {@code byte} level endianness used to represent
* the order in which different values are stored as bytes. Refer to
* constants starting with {@code ENDIANNESS_*}. By default, java uses
* {@link #BIG_ENDIAN}. Please note that this parameter does not
* affect the {@code bit} endianness, as {@code bit} endianness is
* assumed universal across platforms and languages.
* @throws IllegalArgumentException one of the following occurs:
* <ul>
* <li>{@code numOfBits} is negative.</li>
* <li>{@code numOfBits} is zero.</li>
* <li>{@code byteEndianness} is not one of the values defined
* by {@code ENDIANNESS_*} constants.
* See {@link #isValidEndianness(int)}.</li>
* </ul>
* @throws IOException the underlying I/O operation fails.
*/
public void write(int numOfBits, BigInteger bitsValue, int byteEndianness)
throws IllegalArgumentException,
IOException {
if (numOfBits < 0) {
throw new IllegalArgumentException();
}
if (numOfBits == 0) {
throw new IllegalArgumentException();
}
if (!isValidEndianness(byteEndianness)) {
throw new IllegalArgumentException();
}
int numOfBytesWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
if (byteEndianness == LITTLE_ENDIAN) {
for (int curByteIndex = 0;
curByteIndex < numOfBytesWithoutFractionalBits;
curByteIndex = curByteIndex + 1) {
for (int curBitShift = 0; curBitShift < Byte.SIZE; curBitShift = curBitShift + 1) {
int bitShift = Byte.SIZE * curByteIndex + curBitShift;
int bitValue = getBitsValueBit(bitsValue, bitShift);
write(bitValue);
}
}
int numOfRemainingBitsAfterLastByte = (int )(numOfBits % Byte.SIZE);
for (int curLastBitShift = 0;
curLastBitShift < numOfRemainingBitsAfterLastByte;
curLastBitShift = curLastBitShift + 1) {
int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curLastBitShift;
int bitValue = getBitsValueBit(bitsValue, bitShift);
write(bitValue);
}
} else {
int numOfPrecedingBitsBeforeFirstByte = (int )(numOfBits % Byte.SIZE);
for (int curFirstBitShift = 0;
curFirstBitShift < numOfPrecedingBitsBeforeFirstByte;
curFirstBitShift = curFirstBitShift + 1) {
int bitShift = Byte.SIZE * numOfBytesWithoutFractionalBits + curFirstBitShift;
int bitValue = getBitsValueBit(bitsValue, bitShift);
write(bitValue);
}
for (int curByteIndex = 0;
curByteIndex < numOfBytesWithoutFractionalBits;
curByteIndex = curByteIndex + 1) {
for (int curBitShift = 0; curBitShift < Byte.SIZE; curBitShift = curBitShift + 1) {
int curByteIndexFromBehind = numOfBytesWithoutFractionalBits - curByteIndex - 1;
int bitShift = Byte.SIZE * curByteIndexFromBehind + curBitShift;
int bitValue = getBitsValueBit(bitsValue, bitShift);
write(bitValue);
}
}
}
}
/**
* Skips a number of bits, specified by {@code numOfBitsToSkip}, so
* that the read cursor will fast forward to the desired location.
* The reference implementation simply reads and discards the data.
* Note that according to the availability of the data in the
* current {@code Buffer}, the actually skipped number of bits
* might be less than the one passed as the parameter. So it is
* advised to always use the return value to check for it.
* @param numOfBitsToSkip The number of {@code bit}s to skip.
* @return The number of {@code bit}s actually skipped. If
* the return value is smaller than {@code numOfBitsToSkip}
* it usually indicates that the cursor has been fast
* forwarded to the end of the available bits stream.
* @throws IllegalArgumentException one of the following occurs:
* <ul>
* <li>{@code numOfBitsToSkip} is negative.</li>
* <li>{@code numOfBitsToSkip} is zero.</li>
* </ul>
* @throws IOException the underlying I/O operation fails.
*/
public long skip(long numOfBitsToSkip) throws IOException {
if (numOfBitsToSkip < 0) {
throw new IllegalArgumentException();
}
if (numOfBitsToSkip == 0) {
throw new IllegalArgumentException();
}
long numOfBitsSkippable = readable();
long numOfBitsActuallySkipped = Math.min(numOfBitsToSkip, numOfBitsSkippable);
for (int curSkipBitIndex = 0;
curSkipBitIndex < numOfBitsActuallySkipped;
curSkipBitIndex = curSkipBitIndex + 1) {
int discardedBit = read();
}
return numOfBitsActuallySkipped;
}
/**
* Fills a number of {@code bit}s, specified by {@code numOfBitsToFill},
* with a {@code bit} value specified by {@code bitValuePerBit}, so that
* the write cursor will fast forward to the desired location. The reference
* implementation will write the data specified by {@code bitValuePerBit}
* repeatedly until {@code numOfBitsToFill bits} of data are written.
* If the {@code bit}s being filled exceeds the current size of the
* {@code Buffer}'s maximum allowed space, re-alloc-expanding will occur.
* @param numOfBitsToFill The number of bits to fill to fast forward
* the write cursor. Note that re-alloc-expanding may occur.
* @param bitValuePerBit The {@code bit} value to fill the buffer.
* @throws IllegalArgumentException one of the following occurs:
* <ul>
* <li>{@code numOfBitsToFill} is negative.</li>
* <li>{@code numOfBitsToFill} is zero.</li>
* <li>{@code bitValuePerBit} is not a valid bit value
* (neither {@code 1 (true)} nor {@code 0 (false)}).</li>
* </ul>
* @throws IOException the underlying I/O operation fails.
*/
public void fill(long numOfBitsToFill, int bitValuePerBit)
throws IllegalArgumentException,
IOException {
if (numOfBitsToFill < 0) {
throw new IllegalArgumentException();
}
if (numOfBitsToFill == 0) {
throw new IllegalArgumentException();
}
if (bitValuePerBit != 0 &&
bitValuePerBit != 1) {
throw new IllegalArgumentException();
}
for (long curBitFillIndex = 0;
curBitFillIndex < numOfBitsToFill;
curBitFillIndex = curBitFillIndex + 1) {
write(bitValuePerBit);
}
}
/**
* Saves a mark at the current read cursor, subsequent read operations will
* run normally, once {@link #reset()} is called, the recorded mark will be
* used to substitute any read cursor position so the read cursor will be
* rolled back to the previously marked position. This method is useful for
* pre-reading and checking values. The caller may call {@link #mark()} first
* and read as per normal, and if a desired value is found or an error is
* encountered, call the reset to roll back to keep the cursor intact.
*
* @see #reset()
*/
public void mark() {
markPos = readPos;
}
/**
* Replaces the current read cursor with the previously recorded position
* assigned by a call to the {@link #mark()} method. Once this method is
* called, the read cursor will be rolled back to the previously recorded
* position, and the data that position will become available for read again.
* Please note that a call to {@link #reset()} will also reset the marked
* position to {@link #EOF}. So after calling {@link #reset()} to roll
* back your read cursor, if you wish to be able to roll back to this
* position again, call {@link #mark()} immediately after for the mark
* position to be set to that position again. If there was no previously
* marked position, {@link #reset()} does nothing.
*/
public void reset() {
if (markPos != EOF) {
readPos = markPos;
markPos = EOF;
}
}
/**
* Aligns the read cursor to the beginning of the next {@code byte}.
* If the read cursor is in the middle of a byte, moves the cursor
* to the start of next byte. If the cursor is already at the start
* of a byte, {@link #alignRead()} does nothing and returns {@code zero}.
* Note that {@link #alignRead()} will never make the read cursor
* beyond the write cursor to make the data corrupted. If the write
* cursor does not end in a full {@code byte} and if the read cursor
* is near the write cursor, calling {@link #alignRead()} will always
* return {@link #EOF} as the read cursor cannot go past the write
* cursor to make a full {@code byte}.
*
* @return The number of {@code bits} skipped to traverse to the starting
* bit of the next {@code byte}. If the read cursor is already at
* the starting bit of a {@code byte}, returns {@code zero}; if
* the align operation cannot move the read cursor to the beginning
* {@code bit} of the next {@code byte} because the write cursor
* is not yet that far, returns {@link #EOF}.
*/
public int alignRead() {
int readByteIndex = (int )(readPos / Byte.SIZE);
int readBitShiftAtIndex = (int )(readPos % Byte.SIZE);
boolean alignNeeded = readBitShiftAtIndex != 0;
long traverseResult = 0;
if (alignNeeded) {
int nextReadByteIndex = readByteIndex + 1;
long alignedPos = (long )Byte.SIZE * (long )nextReadByteIndex;
if (alignedPos > writePos) {
traverseResult = EOF;
} else {
traverseResult = alignedPos - readPos;
readPos = alignedPos;
}
}
return (int )traverseResult;
}
/**
* Aligns the write cursor to the beginning of the next {@code byte}.
* If the write cursor is in the middle of a byte, moves the cursor
* to the start of next byte. If the cursor is already at the start
* of a byte, {@link #alignRead()} does nothing and returns {@code zero}.
*
* @return The number of {@code bits} skipped to traverse to the starting
* bit of the next {@code byte}. If the read cursor is already at
* the starting bit of a {@code byte}, returns {@code zero}.
*/
public int alignWrite() {
int writeByteIndex = (int )(writePos / Byte.SIZE);
int writeBitShiftAtIndex = (int )(writePos % Byte.SIZE);
boolean alignNeeded = writeBitShiftAtIndex != 0;
long traverseResult = 0;
if (alignNeeded) {
int nextWriteByteIndex = writeByteIndex + 1;
long alignedPos = (long )Byte.SIZE * (long )nextWriteByteIndex;
traverseResult = alignedPos - writePos;
writePos = alignedPos;
}
return (int )traverseResult;
}
/**
* Exports the data in this {@link Buffer} to a {@code byte} array.
* the parameter {@code copyMethod} specifies how to copy the data.
* @param copyMethod How to copy the data from this {@link Buffer}
* to an array. Refer to {@code COPY_METHOD_*} for details.
* @return The {@code byte} array containing the data of this
* {@link Buffer}.
* @throws IllegalArgumentException the {@code copyMethod} is not
* a valid value (none of the specified {@code COPY_METHOD_*}
* values).
* @see #COPY_METHOD_DEFAULT
* @see #COPY_METHOD_ALIGNED
* @see #COPY_METHOD_ALL_DATA
*/
public byte[] toByteArray(int copyMethod) {
if (copyMethod != COPY_METHOD_DEFAULT &&
copyMethod != COPY_METHOD_ALIGNED &&
copyMethod != COPY_METHOD_ALL_DATA) {
throw new IllegalArgumentException();
}
byte[] byteArray;
if (copyMethod == COPY_METHOD_ALL_DATA) {
int numOfBytesWithoutFractionalBits = (int )(writePos / Byte.SIZE);
int numOfRemainingBitsAfterLastByte = (int )(writePos % Byte.SIZE);
boolean needOneMoreByteForRemainingBitsAfterLastByte =
numOfRemainingBitsAfterLastByte != 0;
int numOfBytesNeededForData = numOfBytesWithoutFractionalBits;
if (needOneMoreByteForRemainingBitsAfterLastByte) {
numOfBytesNeededForData = numOfBytesNeededForData + 1;
}
byteArray = Arrays.copyOf(backingData, numOfBytesNeededForData);
} else if (copyMethod == COPY_METHOD_ALIGNED) {
long numOfBitsToCopy = writePos - readPos;
byteArray = alignAndCopyByteArray(backingData, readPos, numOfBitsToCopy);
} else {
int startByteIndex = (int )(readPos / Byte.SIZE);
int endByteIndex = (int )(writePos / Byte.SIZE);
int startBitShift = (int )(readPos % Byte.SIZE);
int endBitShift = (int )(writePos % Byte.SIZE);
boolean needOneMoreByteForRemainingBitsAfterLastByte =
endBitShift != 0;
int numOfBytesNeededForData = endByteIndex - startByteIndex;
if (needOneMoreByteForRemainingBitsAfterLastByte) {
numOfBytesNeededForData = numOfBytesNeededForData + 1;
}
byteArray = Arrays.copyOfRange(backingData, startByteIndex, startByteIndex + numOfBytesNeededForData);
int mask = 0x01;
if (startBitShift != 0) {
for (int bitShift = 0; bitShift < startBitShift; bitShift = bitShift + 1) {
int bitValueShifted = (mask << bitShift);
int bitValueShiftedAndFlippedToClearBits = ~bitValueShifted;
byteArray[startByteIndex] =
(byte )(byteArray[startByteIndex] & bitValueShiftedAndFlippedToClearBits);
}
}
if (endBitShift != 0) {
for (int bitShift = endBitShift; bitShift < Byte.SIZE; bitShift = bitShift + 1) {
int bitValueShifted = (mask << bitShift);
int bitValueShiftedAndFlippedToClearBits = ~bitValueShifted;
byteArray[endByteIndex] =
(byte )(byteArray[endByteIndex] & bitValueShiftedAndFlippedToClearBits);
}
}
}
return byteArray;
}
private byte[] alignAndCopyByteArray(byte[] existingData, long bitOffset, long numOfBits) {
int numOfBitsWithoutFractionalBits = (int )(numOfBits / Byte.SIZE);
int numOfFractionalBits = (int )(numOfBits % Byte.SIZE);
boolean needOneMoreByteForFractionalBits = numOfFractionalBits != 0;
int numOfBytesNeededForData = numOfBitsWithoutFractionalBits;
if (needOneMoreByteForFractionalBits) {
numOfBytesNeededForData = numOfBytesNeededForData + 1;
}
byte[] byteArrayCopy = new byte[numOfBytesNeededForData];
for (long curBitIndex = 0;
curBitIndex < numOfBits;
curBitIndex = curBitIndex + 1) {
int srcByteIndex = (int )((bitOffset + curBitIndex) / Byte.SIZE);
int srcBitShift = (int )((bitOffset + curBitIndex) % Byte.SIZE);
int destByteIndex = (int )(curBitIndex / Byte.SIZE);
int destBitShift = (int )(curBitIndex % Byte.SIZE);
int mask = 0x01;
int srcByteValue = existingData[srcByteIndex];
int srcByteValueShifted = srcByteValue >>> srcBitShift;
int srcBitValue = srcByteValueShifted & mask;
int destBitValue = srcBitValue;
int destBitValueShifted = destBitValue << destBitShift;
byteArrayCopy[destByteIndex] = (byte )(byteArrayCopy[destByteIndex] | destBitValueShifted);
}
return byteArrayCopy;
}
private BigInteger setAndUpdateBitsValueBit(BigInteger bitsValue, int bitShift, int bitValue) {
if (bitsValue == null) {
throw new NullPointerException();
}
if (bitShift < 0) {
throw new IllegalArgumentException();
}
BigInteger shiftedBitValue = BigInteger.ONE;
shiftedBitValue = shiftedBitValue.shiftLeft(bitShift);
if (bitValue != 0) {
bitsValue = bitsValue.or(shiftedBitValue);
} else {
shiftedBitValue = shiftedBitValue.not();
bitsValue = bitsValue.and(shiftedBitValue);
}
return bitsValue;
}
private int getBitsValueBit(BigInteger bitsValue, int bitShift) {
if (bitsValue == null) {
throw new NullPointerException();
}
if (bitShift < 0) {
throw new IllegalArgumentException();
}
BigInteger shiftedBitValue = bitsValue.shiftRight(bitShift);
int shiftedBitIntValue = shiftedBitValue.intValue();
int bitMask = 0x01;
int bitValue = shiftedBitIntValue & bitMask;
return bitValue;
}
}