Committing com.riiablo.io.nio experiment

Replaces com.riiablo.io if I decide to go this route
This commit is contained in:
Collin Smith 2020-08-08 01:07:28 -07:00
parent dd1444ad9a
commit 673b437c61
7 changed files with 838 additions and 0 deletions

View File

@ -0,0 +1,5 @@
package com.riiablo.io.nio;
interface Aligned {
boolean aligned();
}

View File

@ -0,0 +1,22 @@
package com.riiablo.io.nio;
interface AlignedReader extends Aligned {
int bytesRead();
int bytesRemaining();
int numBytes();
byte read8();
short read16();
int read32();
long read64();
short read8u();
int read16u();
long read32u();
byte[] readBytes(int len);
byte[] readBytes(byte[] dst);
byte[] readBytes(byte[] dst, int dstOffset, int len);
String readString(int len);
}

View File

@ -0,0 +1,67 @@
package com.riiablo.io.nio;
class BitConstraints {
private BitConstraints() {}
private static int _validateSize(int min, int max, int bits) {
assert min >= 0 : "min(" + min + ") < " + 0 + "!";
assert max > 0 : "max(" + max + ") <= " + 0;
assert min <= max : "min(" + min + ") > max(" + max + ")";
if (bits < min) {
throw new IllegalArgumentException("bits(" + bits + ") < " + min);
}
if (bits > max) {
throw new IllegalArgumentException("bits(" + bits + ") > " + max);
}
return bits;
}
private static int validateSizeU(int max, int bits) {
return _validateSize(0, max - 1, bits);
}
public static int validate7u(int bits) {
return validateSizeU(Byte.SIZE, bits);
}
public static int validate15u(int bits) {
return validateSizeU(Short.SIZE, bits);
}
public static int validate31u(int bits) {
return validateSizeU(Integer.SIZE, bits);
}
public static int validate63u(int bits) {
return validateSizeU(Long.SIZE, bits);
}
private static int validateSize(int max, int bits) {
return _validateSize(0, max, bits);
}
public static int validate8(int bits) {
return validateSize(Byte.SIZE, bits);
}
public static int validate16(int bits) {
return validateSize(Short.SIZE, bits);
}
public static int validate32(int bits) {
return validateSize(Integer.SIZE, bits);
}
public static int validate64(int bits) {
return validateSize(Long.SIZE, bits);
}
public static boolean isUnsigned(long value, int size) {
assert 0 < size && size <= Long.SIZE;
return (value & (1 << (size - 1))) == 0;
}
public static int validateAscii(int bits) {
return _validateSize(Byte.SIZE - 1, Byte.SIZE, bits);
}
}

View File

@ -0,0 +1,400 @@
package com.riiablo.io.nio;
import org.apache.commons.lang3.StringUtils;
public class BitInput implements Aligned, AlignedReader, UnalignedReader {
private static final int MAX_SAFE_CACHED_BITS = Long.SIZE - Byte.SIZE;
private static final long[] MASKS = new long[Long.SIZE];
static {
for (int i = 1; i < Long.SIZE; i++) {
MASKS[i] = (MASKS[i - 1] << 1) + 1;
}
}
final ByteInput byteInput;
// final ByteBuf buffer;
final long numBits;
long bitsRead;
private int bitsCached;
private long cache;
BitInput(ByteInput byteInput, int bitsCached, long cache, long numBits) {
this.byteInput = byteInput;
// this.buffer = byteInput.buffer;
this.bitsCached = bitsCached;
this.cache = cache;
this.numBits = numBits;
}
@Override
public int bytesRead() {
return byteInput.bytesRead();
}
@Override
public int bytesRemaining() {
return byteInput.bytesRemaining();
}
@Override
public int numBytes() {
return byteInput.numBytes();
}
@Override
public int bitsCached() {
return bitsCached;
}
@Override
public long cache() {
return cache;
}
void clearCache() {
bitsCached = 0;
cache = 0L;
}
@Override
public long bitsRead() {
return bitsRead;
}
@Override
public long bitsRemaining() {
assert (numBits - bitsRead) == (bitsCached + ((long) bytesRemaining() * Byte.SIZE))
: "actual(" + (numBits - bitsRead) + ") != expected(" + (bitsCached + ((long) bytesRemaining() * Byte.SIZE)) + ")";
return numBits - bitsRead;
}
@Override
public long numBits() {
return numBits;
}
@Override
public boolean aligned() {
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
return bitsCached == 0;
}
public ByteInput align() {
// consume cache if bits remaining
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
if (bitsCached > 0) {
bitsRead = Math.min(numBits, bitsRead + bitsCached);
clearCache();
}
assert bitsRead <= numBits : "bitsRead(" + bitsRead + ") > numBits(" + numBits + ")";
return byteInput;
}
public BitInput readSlice(long numBits) {
// since this shouldn't go more than 1 level deep, can also generate a new
// ByteInput with a new BitInput if allowing align
if (numBits == 0) return ByteInput.emptyByteInput().unalign();
if (numBits < 0) throw new IllegalArgumentException("numBits(" + numBits + ") < " + 0);
if (bitsRead + numBits > this.numBits) {
throw new IllegalArgumentException(
"bitsRead(" + bitsRead + ") + sliceBits(" + numBits + ") > numBits(" + this.numBits + ")");
}
assert bitsCached < Byte.SIZE : "bitsCached(" + bitsCached + ") > " + (Byte.SIZE - 1);
// length should include the last byte that bits belong (round to ceil)
final long numBytes = (numBits - bitsCached + Byte.SIZE - 1) / Byte.SIZE;
return byteInput.readSlice(numBytes, bitsCached, cache, numBits).unalign();
}
public BitInput discard(long bits) {
if (bits < 0) throw new IllegalArgumentException("bits(" + bits + ") < " + 0);
if (bits == 0) return this;
final long startingBitsRead = bitsRead;
final long bytes = bits / Byte.SIZE;
assert bytes <= Integer.MAX_VALUE : "bytes(" + bytes + ") > Integer.MAX_VALUE";
if (bytes > 0) align().discard((int) bytes);
final long overflowBits = (startingBitsRead + bits) - bitsRead;
// checks single byte, multi-byte and expected max value
assert bytes != 0 || overflowBits < Byte.SIZE : "overflowBits(" + overflowBits + ") > " + (Byte.SIZE - 1);
assert bytes == 0 || overflowBits < Short.SIZE : "overflowBits(" + overflowBits + ") > " + (Short.SIZE - 1);
assert overflowBits < Short.SIZE : "overflowBits(" + overflowBits + ") > " + (Short.SIZE - 1);
if (overflowBits > 0) _readRaw((int) overflowBits);
return this;
}
long incrementBitsRead(long bits) {
if ((bitsRead += bits) > numBits) {
bitsRead = numBits;
throw new EndOfInput();
}
return bitsRead;
}
long decrementBitsRead(long bits) {
if ((bitsRead -= bits) < 0) {
assert false : "bitsRead(" + bitsRead + ") < " + 0;
bitsRead = 0;
}
return bitsRead;
}
/**
* Reads up to {@value #MAX_UNSIGNED_BITS} bits as unsigned and casts the
* result into a {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value #MAX_UNSIGNED_BITS}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readUnsigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
if (bits <= 0) return 0;
incrementBitsRead(bits);
ensureCache(bits);
return bitsCached < bits
? readCacheSafe(bits)
: readCacheUnsafe(bits);
}
/**
* Ensures {@link #cache} contains at least <i>n</i> bits, up to
* {@value #MAX_SAFE_CACHED_BITS} bits due to overflow.
*/
private int ensureCache(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
while (bitsCached < bits && bitsCached <= MAX_SAFE_CACHED_BITS) {
final long nextByte = byteInput._read8u();
cache |= (nextByte << bitsCached);
bitsCached += Byte.SIZE;
}
return bitsCached;
}
/**
* Reads <i>n</i> bits from {@link #cache}, consuming the next byte in the
* underlying byte stream.
* <p/>
* This function asserts that {@link #cache} would have overflowed if
* <i>n</i> bits were read from the underlying byte stream and thus reads
* the next byte accounting for this case.
*/
private long readCacheSafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final int bitsToAddCount = bits - bitsCached;
final int overflowBits = Byte.SIZE - bitsToAddCount;
final long nextByte = byteInput._read8u();
long bitsToAdd = nextByte & MASKS[bitsToAddCount];
cache |= (bitsToAdd << bitsCached);
final long overflow = (nextByte >>> bitsToAddCount) & MASKS[overflowBits];
final long bitsOut = bitsCached & MASKS[bits];
cache = overflow;
bitsCached = overflowBits;
return bitsOut;
}
/**
* Reads <i>n</i> bits from {@link #cache}.
* <p/>
* This function asserts {@link #cache} contains at least <i>n</i> bits.
*/
private long readCacheUnsafe(int bits) {
assert bits > 0 : "bits(" + bits + ") < " + 1;
assert bits < Long.SIZE : "bits(" + bits + ") > " + (Long.SIZE - 1);
final long bitsOut = cache & MASKS[bits];
cache >>>= bits;
bitsCached -= bits;
return bitsOut;
}
/**
* Reads up to {@value Long#SIZE} bits and sign extending the result as a
* {@code long}.
* <p/>
* <p>{@code bits} should be in [0, {@value Long#SIZE}].
* <p>Reading {@code 0} bits will always return {@code 0}.
*/
long readSigned(int bits) {
assert bits >= 0 : "bits(" + bits + ") < " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
if (bits <= 0) return 0;
if (bits == Long.SIZE) return _readRaw(Long.SIZE);
final int shift = Long.SIZE - bits;
assert shift > 0;
final long value = readUnsigned(bits);
return value << shift >> shift;
}
long _readRaw(int bits) {
assert bits > 0 : "bits(" + bits + ") <= " + 0;
assert bits <= Long.SIZE : "bits(" + bits + ") > " + Long.SIZE;
long lo = readUnsigned(Math.min(Integer.SIZE, bits));
long hi = readUnsigned(Math.max(bits - Integer.SIZE, 0));
return (hi << Integer.SIZE) | lo;
}
@Override
public long readRaw(int bits) {
BitConstraints.validate64(bits);
return _readRaw(bits);
}
@Override
public byte read7u(int bits) {
BitConstraints.validate7u(bits);
final byte value = (byte) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Byte.SIZE);
return value;
}
@Override
public short read15u(int bits) {
BitConstraints.validate15u(bits);
final short value = (short) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Short.SIZE);
return value;
}
@Override
public int read31u(int bits) {
BitConstraints.validate31u(bits);
final int value = (int) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Integer.SIZE);
return value;
}
@Override
public long read63u(int bits) {
BitConstraints.validate63u(bits);
final long value = (long) readUnsigned(bits);
assert BitConstraints.isUnsigned(value, Long.SIZE);
return value;
}
@Override
public boolean readBoolean() {
return read1() != 0;
}
@Override
public byte read1() {
final byte value = read7u(1);
assert (value & ~1) == 0;
return value;
}
@Override
public byte read8(int bits) {
BitConstraints.validate8(bits);
return (byte) readSigned(bits);
}
@Override
public short read16(int bits) {
BitConstraints.validate16(bits);
return (short) readSigned(bits);
}
@Override
public int read32(int bits) {
BitConstraints.validate32(bits);
return (int) readSigned(bits);
}
@Override
public long read64(int bits) {
BitConstraints.validate64(bits);
return readSigned(bits);
}
@Override
public short read8u() {
return read15u(Byte.SIZE);
}
@Override
public int read16u() {
return read31u(Short.SIZE);
}
@Override
public long read32u() {
return read63u(Integer.SIZE);
}
@Override
public byte read8() {
return read8(Byte.SIZE);
}
@Override
public short read16() {
return read16(Short.SIZE);
}
@Override
public int read32() {
return read32(Integer.SIZE);
}
@Override
public long read64() {
return read64(Long.SIZE);
}
/**
* Aligns bit stream and reads from {@link #align()}
*/
@Override
public byte[] readBytes(int len) {
return align().readBytes(len);
}
/**
* Aligns bit stream and reads from {@link #align()}
*/
@Override
public byte[] readBytes(byte[] dst) {
return align().readBytes(dst);
}
/**
* Aligns bit stream and reads from {@link #align()}
*/
@Override
public byte[] readBytes(byte[] dst, int dstOffset, int len) {
return align().readBytes(dst, dstOffset, len);
}
@Override
public String readString(int len) {
return readString(len, Byte.SIZE, false);
}
@Override
public String readString(int len, int bits, boolean nullTerminated) {
if (len < 0) throw new IllegalArgumentException("len(" + len + ") < " + 0);
BitConstraints.validateAscii(bits);
if (len == 0) return StringUtils.EMPTY;
final byte[] dst = new byte[len];
for (int i = 0; i < len; i++) {
final byte b = dst[i] = (byte) readUnsigned(bits);
if (nullTerminated && b == '\0') break;
}
return new String(dst);
}
}

View File

@ -0,0 +1,243 @@
package com.riiablo.io.nio;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import com.riiablo.util.DebugUtils;
public class ByteInput implements Aligned, AlignedReader {
private static final ByteInput EMPTY_BYTEINPUT = new ByteInput(Unpooled.EMPTY_BUFFER);
public static ByteInput emptyByteInput() {
return EMPTY_BYTEINPUT;
}
public static ByteInput wrap(byte[] bytes) {
return bytes == null ? emptyByteInput() : new ByteInput(Unpooled.wrappedBuffer(bytes));
}
final BitInput bitInput;
final ByteBuf buffer;
ByteInput(ByteBuf buffer) {
this(buffer, 0, 0L, (long) buffer.readableBytes() * Byte.SIZE);
}
ByteInput(ByteBuf buffer, int bitsCached, long cache, long numBits) {
this.buffer = buffer;
this.bitInput = new BitInput(this, bitsCached, cache, numBits);
}
@Override
public int bytesRead() {
return buffer.readerIndex();
}
@Override
public int bytesRemaining() {
return buffer.readableBytes();
}
@Override
public int numBytes() {
return buffer.capacity();
}
@Override
public boolean aligned() {
return bitInput.aligned();
}
public BitInput unalign() {
return bitInput;
}
public ByteInput discard(int bytes) {
buffer.skipBytes(bytes);
return this;
}
/**
* Consumes bytes until the specified sequence of bytes are encountered.
* After this method executes, position will be such that the next read
* operation is at the first byte in the signature.
* <p/>
* <b>Precondition:</b> {@code signature.length == 2}.
*
* @see #discard(int)
*/
public ByteInput discardUntil(byte[] signature) {
assert aligned() : "not aligned";
if (signature.length != 2) {
throw new IllegalArgumentException(
"signature.length(" + signature.length + ") != " + 2 + ": " + DebugUtils.toByteArray(signature));
}
final byte fb0 = signature[0];
final byte fb1 = signature[1];
byte b0, b1;
b1 = read8();
for (;;) {
b0 = b1;
b1 = read8();
if (b0 == fb0 && b1 == fb1) {
buffer.readerIndex(buffer.readerIndex() - signature.length);
decrementBitsRead((long) signature.length * Byte.SIZE);
assert bytesRemaining() >= signature.length
: "bytesRemaining(" + bytesRemaining() + ") < signature.length(" + signature.length + ")";
break;
}
}
// TODO: support dynamic signature lengths
// create a byte[] of size signature.length
// use as a circular buffer with each read byte incrementing index and then going back to
// 0 when signature.length is reached. Comparisons will need to be index..length
// and 0..index (and 0..length in case where index == 0)
return this;
}
public ByteInput readSlice(long numBytes) {
return readSlice(numBytes, 0, 0L, numBytes * Byte.SIZE);
}
public ByteInput readSlice(long numBytes, int bitsCached, long cache, long numBits) {
assert numBytes <= Integer.MAX_VALUE : "ByteBuf only supports int length";
final ByteBuf slice = buffer.readSlice((int) numBytes);
return new ByteInput(slice, bitsCached, cache, numBits);
}
/**
* Reads the next byte from the byte stream, ignoring alignment.
*/
short _read8u() {
try {
final short octet = buffer.readUnsignedByte();
assert 0 <= octet && octet < (1 << Byte.SIZE);
return octet;
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
long incrementBitsRead(long bits) {
assert (bits & (Byte.SIZE - 1)) == 0;
return bitInput.incrementBitsRead(bits);
}
long decrementBitsRead(long bits) {
assert (bits & (Byte.SIZE - 1)) == 0;
return bitInput.decrementBitsRead(bits);
}
@Override
public short read8u() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Byte.SIZE);
return buffer.readUnsignedByte();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public int read16u() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Short.SIZE);
return buffer.readUnsignedShortLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public long read32u() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Integer.SIZE);
return buffer.readUnsignedIntLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public byte read8() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Byte.SIZE);
return buffer.readByte();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public short read16() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Short.SIZE);
return buffer.readShortLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public int read32() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Integer.SIZE);
return buffer.readIntLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public long read64() {
assert aligned() : "not aligned";
try {
incrementBitsRead(Long.SIZE);
return buffer.readLongLE();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public byte[] readBytes(int len) {
return readBytes(new byte[len]);
}
@Override
public byte[] readBytes(byte[] dst) {
return readBytes(dst, 0, dst.length);
}
@Override
public byte[] readBytes(byte[] dst, int dstOffset, int len) {
assert aligned() : "not aligned";
try {
incrementBitsRead((long) len * Byte.SIZE);
buffer.readBytes(dst, dstOffset, len);
return dst;
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
@Override
public String readString(int len) {
assert aligned() : "not aligned";
try {
incrementBitsRead((long) len * Byte.SIZE);
return buffer.readCharSequence(len, CharsetUtil.US_ASCII).toString();
} catch (IndexOutOfBoundsException t) {
throw new EndOfInput(t);
}
}
}

View File

@ -0,0 +1,13 @@
package com.riiablo.io.nio;
public class EndOfInput extends RuntimeException {
private static final String DEFAULT_MESSAGE = "The end of the input has been reached!";
EndOfInput() {
super(DEFAULT_MESSAGE);
}
EndOfInput(Throwable cause) {
super(DEFAULT_MESSAGE, cause);
}
}

View File

@ -0,0 +1,88 @@
package com.riiablo.io.nio;
interface UnalignedReader extends Aligned, AlignedReader {
int MAX_ULONG_BITS = Long.SIZE - 1;
int MAX_UINT_BITS = Integer.SIZE - 1;
int MAX_USHORT_BITS = Short.SIZE - 1;
int MAX_UBYTE_BITS = Byte.SIZE - 1;
int MAX_UNSIGNED_BITS = MAX_ULONG_BITS;
int bitsCached();
long cache();
long bitsRead();
long bitsRemaining();
long numBits();
/**
* Reads up to {@value #MAX_UBYTE_BITS} bits as unsigned and casts the result
* into a {@code byte}.
*/
byte read7u(int bits);
/**
* Reads up to {@value #MAX_USHORT_BITS} bits as unsigned and casts the
* result into a {@code short}.
*/
short read15u(int bits);
/**
* Reads up to {@value #MAX_UINT_BITS} bits as unsigned and casts the result
* into a {@code int}.
*/
int read31u(int bits);
/**
* Reads up to {@value #MAX_ULONG_BITS} bits as unsigned and casts the result
* into a {@code long}.
*/
long read63u(int bits);
/**
* Reads {@code 1} bit as a {@code boolean}.
*
* @see #read1()
*/
boolean readBoolean();
/**
* Reads {@code 1} bit as a {@code byte}.
*/
byte read1();
/**
* Reads up to {@value Byte#SIZE} bits as a sign-extended {@code byte}.
*/
byte read8(int bits);
/**
* Reads up to {@value Short#SIZE} bits as a sign-extended {@code short}.
*/
short read16(int bits);
/**
* Reads up to {@value Integer#SIZE} bits as a sign-extended {@code int}.
*/
int read32(int bits);
/**
* Reads up to {@value Long#SIZE} bits as a sign-extended {@code long}.
*/
long read64(int bits);
/**
* Reads up to {@value Long#SIZE} bits as a {@code long}. This method is
* intended to be used to read raw memory (i.e., flags).
*/
long readRaw(int bits);
/**
* Reads <i>n</i> characters of size {@code bits} and constructs a string.
*
* @param len number of characters to read
* @param bits size of each character ({@code 7} or {@code 8})
* @param nullTerminated {@code true} to stop reading at {@code \0}, otherwise
* {@code len} characters will be read (variable-width string)
*/
String readString(int len, int bits, boolean nullTerminated);
}