diff --git a/Dorkbox-Util/src/dorkbox/util/Sys.java b/Dorkbox-Util/src/dorkbox/util/Sys.java index 5f53dbf..8fbd7e4 100644 --- a/Dorkbox-Util/src/dorkbox/util/Sys.java +++ b/Dorkbox-Util/src/dorkbox/util/Sys.java @@ -373,8 +373,8 @@ public class Sys { // NOTE: this saves the char array in UTF-16 format of bytes. byte[] bytes = new byte[text.length*2]; for(int i=0; i>8); - bytes[2*i+1] = (byte) (text[i] & 0x00FF); + bytes[2*i] = (byte) (text[i] >> 8); + bytes[2*i+1] = (byte) text[i]; } return bytes; @@ -754,7 +754,7 @@ public class Sys { private static final void findModulesInJar(ClassLoader classLoader, String searchLocation, - Class annotation, URL resource, List> annotatedClasses) + Class annotation, URL resource, List> annotatedClasses) throws IOException, ClassNotFoundException { URLConnection connection = resource.openConnection(); @@ -764,7 +764,6 @@ public class Sys { JarURLConnection jarURLConnection = (JarURLConnection) connection; JarFile jarFile = jarURLConnection.getJarFile(); - String fileResource = jarURLConnection.getEntryName(); Enumeration entries = jarFile.entries(); @@ -773,8 +772,8 @@ public class Sys { JarEntry jarEntry = entries.nextElement(); String name = jarEntry.getName(); - if (name.startsWith(fileResource) && // make sure it's at least the correct package - isValid(name)) { + if (name.startsWith(searchLocation) && // make sure it's at least the correct package + isValid(name)) { String classPath = name.replace(File.separatorChar, '.').substring(0, name.lastIndexOf(".")); @@ -795,7 +794,9 @@ public class Sys { // class files will not have an entry name, which is reserved for resources only String name = hiveJarURLConnection.getResourceName(); - if (isValid(name)) { + if (name.startsWith(searchLocation) && // make sure it's at least the correct package + isValid(name)) { + String classPath = name.substring(0, name.lastIndexOf('.')); classPath = classPath.replace('/', '.'); diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java b/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java deleted file mode 100644 index 463c1bf..0000000 --- a/Dorkbox-Util/src/dorkbox/util/bytes/BigEndian.java +++ /dev/null @@ -1,120 +0,0 @@ -package dorkbox.util.bytes; - -import java.nio.ByteBuffer; - -public class BigEndian { - // the following are ALL in Big-Endian (big is to the left, first byte is most significant, unsigned bytes) - - /** SHORT to and from bytes */ - public static class Short_ { - public static final short fromBytes(byte[] bytes) { - return fromBytes(bytes[0], bytes[1]); - } - - public static final short fromBytes(byte b0, byte b1) { - return (short) ((b0 & 0xFF) << 8 | - (b1 & 0xFF) << 0); - } - - - public static final byte[] toBytes(short x) { - return new byte[] {(byte) (x >> 8), - (byte) (x >> 0) - }; - } - - public static final int fromBytes(ByteBuffer buff) { - return fromBytes(buff.get(), buff.get()); - } - } - - /** CHAR to and from bytes */ - public static class Char_ { - public static final char fromBytes(byte[] bytes) { - return fromBytes(bytes[0], bytes[1]); - } - - public static final char fromBytes(byte b0, byte b1) { - return (char) ((b0 & 0xFF) << 8 | - (b1 & 0xFF) << 0); - } - - - public static final byte[] toBytes(char x) { - return new byte[] {(byte) (x >> 8), - (byte) (x >> 0) - }; - } - - public static final int fromBytes(ByteBuffer buff) { - return fromBytes(buff.get(), buff.get()); - } - } - - - /** INT to and from bytes */ - public static class Int_ { - public static final int fromBytes(byte[] bytes) { - return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]); - } - - public static final int fromBytes(byte b0, byte b1, byte b2, byte b3) { - return (b0 & 0xFF) << 24 | - (b1 & 0xFF) << 16 | - (b2 & 0xFF) << 8 | - (b3 & 0xFF) << 0; - } - - public static int fromBytes(byte b0, byte b1) { - return (b0 & 0xFF) << 24 | - (b1 & 0xFF) << 16; - } - - public static final byte[] toBytes(int x) { - return new byte[] {(byte) (x >> 24), - (byte) (x >> 16), - (byte) (x >> 8), - (byte) (x >> 0) - } ; - } - - public static final int fromBytes(ByteBuffer buff) { - return fromBytes(buff.get(), buff.get(), buff.get(), buff.get()); - } - } - - /** LONG to and from bytes */ - public static class Long_ { - public static final long fromBytes(byte[] bytes) { - return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]); - } - - public static final long fromBytes(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7) { - return (long) (b0 & 0xFF) << 56 | - (long) (b1 & 0xFF) << 48 | - (long) (b2 & 0xFF) << 40 | - (long) (b3 & 0xFF) << 32 | - (long) (b4 & 0xFF) << 24 | - (long) (b5 & 0xFF) << 16 | - (long) (b6 & 0xFF) << 8 | - (long) (b7 & 0xFF) << 0; - } - - public static final byte[] toBytes (long x) { - return new byte[] {(byte) (x >> 56), - (byte) (x >> 48), - (byte) (x >> 40), - (byte) (x >> 32), - (byte) (x >> 24), - (byte) (x >> 16), - (byte) (x >> 8), - (byte) (x >> 0), - }; - } - - public static final long fromBytes(ByteBuffer buff) { - return fromBytes(buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get(), buff.get()); - } - } -} - diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java index 1455774..e5f044d 100644 --- a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java +++ b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2.java @@ -1,338 +1,1934 @@ + package dorkbox.util.bytes; import java.nio.BufferUnderflowException; +import java.util.Arrays; /** - * Cleanroom implementation of a self-growing bytebuffer + * A self-growing byte array wrapper. + * + * Utility methods are provided for efficiently writing primitive types and strings. + * + * Encoding of integers: BIG_ENDIAN is used for storing fixed native size integer values LITTLE_ENDIAN is used for a variable + * length encoding of integer values + * + * @author Nathan Sweet , Nathan Robinson */ public class ByteBuffer2 { + private int capacity; // exactly how many bytes have been allocated + private int maxCapacity; // how large we can grow - private byte[] bytes; - private int position = 0; - private int mark = -1; - private int limit = 0; + private int position; // current pointer to the point where data is read/written - public static ByteBuffer2 wrap(byte[] buffer) { - return new ByteBuffer2(buffer); - } - - public static ByteBuffer2 allocate(int capacity) { - ByteBuffer2 byteBuffer2 = new ByteBuffer2(new byte[capacity]); - byteBuffer2.clear(); - return byteBuffer2; - } + private byte[] bytes; // the backing buffer + private char[] chars = new char[32]; // small buffer for reading strings + /** + * Creates an uninitialized object. {@link #setBuffer(byte[], int)} must be called before the object is used. + */ public ByteBuffer2() { - this(0); - } - - public ByteBuffer2(int size) { - this(new byte[size]); - } - - public ByteBuffer2(byte[] bytes) { - this.bytes = bytes; - clear(); - this.position = bytes.length; - } - - public byte getByte() { - if (this.position > this.limit) { - throw new BufferUnderflowException(); - } - - return this.bytes[this.position++]; - } - - public byte getByte(int i) { - if (i > this.limit) { - throw new BufferUnderflowException(); - } - - return this.bytes[i]; - } - - public void getBytes(byte[] buffer) { - getBytes(buffer, 0, buffer.length); - } - - public void getBytes(byte[] buffer, int length) { - getBytes(buffer, 0, length); - } - - public void getBytes(byte[] buffer, int offset, int length) { - if (this.position + length - offset > this.limit) { - throw new BufferUnderflowException(); - } - - System.arraycopy(this.bytes, this.position, buffer, 0, length-offset); - this.position += length-offset; } /** - * MUST call checkBuffer before calling this! + * Creates a new object for writing to a byte array. * - * NOT PROTECTED + * @param bufferSize + * The initial and maximum size of the buffer. An exception is thrown if this size is exceeded. */ - private final void _put(byte b) { - this.bytes[this.position++] = b; - } - - /** NOT PROTECTED! */ - private final void checkBuffer(int threshold) { - if (this.bytes.length < threshold) { - byte[] t = new byte[threshold]; - // grow at back of array - System.arraycopy(this.bytes, 0, t, 0, this.bytes.length); - this.limit = t.length; - - this.bytes = t; - } - } - - public final synchronized void put(ByteBuffer2 buffer) { - putBytes(buffer.array(), buffer.position, buffer.limit); - buffer.position = buffer.limit; - } - - public final synchronized ByteBuffer2 putBytes(byte[] src) { - return putBytes(src, 0, src.length); - } - - public final synchronized ByteBuffer2 putBytes(byte[] src, int offset, int length) { - checkBuffer(this.position + length - offset); - - System.arraycopy(src, offset, this.bytes, this.position, length); - this.position += length; - - return this; - } - - public final synchronized ByteBuffer2 putByte(byte b) { - checkBuffer(this.position + 1); - - _put(b); - return this; - } - - public final synchronized void putByte(int position, byte b) { - this.position = position; - putByte(b); - } - - public final synchronized void putChar(char c) { - checkBuffer(this.position + 2); - - putBytes(BigEndian.Char_.toBytes(c)); - } - - public final synchronized char getChar() { - return BigEndian.Char_.fromBytes(getByte(), getByte()); - } - - public final char getChar(int i) { - return BigEndian.Char_.fromBytes(getByte(i++), getByte(i)); - } - - public void getChars(int srcStart, int srcLength, char[] dest, int destStart) { - for (int i=srcStart;i 0) { + System.arraycopy(this.bytes, 0, newBuffer, 0, position2); + } + return newBuffer; } /** - * Returns this buffer's position. + * Returns the remaining read/write bytes available before the end of the buffer + */ + public int remaining() { + return this.capacity - this.position; + } + + /** + * Returns the size of the backing byte buffer + */ + public int capacity() { + return this.capacity; + } + + /** + * Returns the current position in the buffer. This is the number of bytes that have not been flushed. */ public int position() { return this.position; } /** - * Sets this buffer's position. + * Sets the current position in the buffer. */ - public final synchronized ByteBuffer2 position(int position) { - if (position > this.bytes.length || position < 0) { - throw new IllegalArgumentException(); - } - + public void setPosition(int position) { this.position = position; - if (this.mark > position) { - this.mark = -1; - } - - return this; } /** - * Returns the number of elements between the current position and the - * limit. + * Sets the position to zero. */ - public final synchronized int remaining() { - return this.limit - this.position; + public void clear() { + this.position = 0; } /** - * Tells whether there are any elements between the current position and - * the limit. + * Sets the position to zero. */ - public final synchronized boolean hasRemaining() { - return this.position < this.limit; + public void rewind() { + this.position = 0; } /** - * Sets this buffer's limit. + * Sets the position to zero, and write 0 to all bytes in the buffer */ - public final synchronized void limit(int limit) { - this.limit = limit; - if (this.position > limit) { - this.position = limit; - } - if (this.mark > limit) { - this.mark = -1; + public void clearSecure() { + this.position = 0; + byte[] buffer = this.bytes; + + for (int i=0;i= required) { + return false; + } + if (required > this.maxCapacity) { + throw new RuntimeException("Buffer overflow. Max capacity: " + this.maxCapacity + ", required: " + required); + } + + while (this.capacity - this.position < required) { + if (this.capacity == this.maxCapacity) { + throw new RuntimeException("Buffer overflow. Available: " + (this.capacity - this.position) + + ", required: " + required); + } + + // Grow buffer. + if (this.capacity == 0) { + this.capacity = 1; + } + this.capacity = Math.min((int)(this.capacity * 1.6D), this.maxCapacity); + if (this.capacity < 0) { + this.capacity = this.maxCapacity; + } + byte[] newBuffer = new byte[this.capacity]; + System.arraycopy(this.bytes, 0, newBuffer, 0, this.position); + this.bytes = newBuffer; + } + + return true; + } + + // byte + + /** + * Writes a byte. + */ + public void writeByte(byte value) { + if (this.position == this.capacity) { + require(1); + } + this.bytes[this.position++] = value; } /** - * The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer. - * That is, the byte at index p = position() is copied to index zero, the byte at index p + 1 is copied to index one, - * and so forth until the byte at index limit() - 1 is copied to index n = limit() - 1 - p. The buffer's position is - * then set to n+1 and its limit is set to its capacity. The mark, if defined, is discarded. + * Writes a byte. + */ + public void writeByte(int value) { + if (this.position == this.capacity) { + require(1); + } + this.bytes[this.position++] = (byte) value; + } + + /** + * Writes the bytes. Note the byte[] length is not written. + */ + public void writeBytes(byte[] bytes) { + if (bytes == null) { + throw new IllegalArgumentException("bytes cannot be null."); + } + writeBytes(bytes, 0, bytes.length); + } + + /** + * Writes the bytes. Note the byte[] length is not written. + */ + public void writeBytes(byte[] bytes, int offset, int count) { + if (bytes == null) { + throw new IllegalArgumentException("bytes cannot be null."); + } + + int copyCount = Math.min(this.capacity - this.position, count); + while (true) { + System.arraycopy(bytes, offset, this.bytes, this.position, copyCount); + this.position += copyCount; + count -= copyCount; + if (count == 0) { + return; + } + offset += copyCount; + copyCount = Math.min(this.capacity, count); + require(copyCount); + } + } + + /** + * Reads a single byte. + */ + public byte readByte() { + return this.bytes[this.position++]; + } + + /** + * Reads a byte as an int from 0 to 255. + */ + public int readByteUnsigned() { + return this.bytes[this.position++] & 0xFF; + } + + /** + * Reads a single byte, does not advance the position + */ + public byte readByte(int position) { + return this.bytes[position]; + } + + /** + * Reads a byte as an int from 0 to 255, does not advance the position + */ + public int readByteUnsigned(int position) { + return this.bytes[position] & 0xFF; + } + + /** + * Reads the specified number of bytes into a new byte[]. + */ + public byte[] readBytes(int length) { + byte[] bytes = new byte[length]; + readBytes(bytes, 0, length); + return bytes; + } + + /** + * Reads bytes.length bytes and writes them to the specified byte[], starting at index 0. + */ + public void readBytes(byte[] bytes) { + readBytes(bytes, 0, bytes.length); + } + + /** + * Reads count bytes and writes them to the specified byte[], starting at offset in target byte array. + */ + public void readBytes(byte[] bytes, int offset, int count) { + if (bytes == null) { + throw new IllegalArgumentException("bytes cannot be null."); + } + + System.arraycopy(this.bytes, this.position, bytes, offset, count); + this.position += count; + } + + // int + + /** + * Writes a 4 byte int. Uses BIG_ENDIAN byte order. + */ + public void writeInt(int value) { + require(4); + + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value >> 24); + buffer[this.position++] = (byte) (value >> 16); + buffer[this.position++] = (byte) (value >> 8); + buffer[this.position++] = (byte) value; + } + + /** + * Writes a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. * - * The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method - * can be followed immediately by an invocation of another relative put method. + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (5 bytes). */ - public final synchronized void compact() { - this.mark = -1; - System.arraycopy(this.bytes, this.position, this.bytes, 0, remaining()); - - position(remaining()); - limit(capacity()); + public int writeInt(int value, boolean optimizePositive) { + return writeVarInt(value, optimizePositive); } /** - * Readies the buffer for reading. + * Writes a 1-5 byte int. It is guaranteed that a varible length encoding will be used. * - * Flips this buffer. The limit is set to the current position and then - * the position is set to zero. If the mark is defined then it is - * discarded. + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (5 bytes). */ - public final synchronized void flip() { - this.limit = this.position; - this.position = 0; - this.mark = -1; + public int writeVarInt(int value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 31; + } + + if (value >>> 7 == 0) { + require(1); + this.bytes[this.position++] = (byte) value; + return 1; + } + if (value >>> 14 == 0) { + require(2); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7); + return 2; + } + if (value >>> 21 == 0) { + require(3); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14); + return 3; + } + if (value >>> 28 == 0) { + require(4); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21); + return 4; + } + + require(5); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28); + return 5; } /** - * Clears this buffer. The position is set to zero, the limit is set to - * the capacity, and the mark is discarded. + * Reads a 4 byte int. */ - public final synchronized void clear() { - this.position = 0; - this.limit = capacity(); - this.mark = -1; + public int readInt() { + byte[] buffer = this.bytes; + int position = this.position; + + int value = (buffer[position] & 0xFF) << 24 + | (buffer[position + 1] & 0xFF) << 16 + | (buffer[position + 2] & 0xFF) << 8 + | buffer[position + 3] & 0xFF; + + this.position = position + 4; + return value; } /** - * Rewinds this buffer. The position is set to zero and the mark is - * discarded. + * Reads a 4 byte int, does not advance the position */ - public final synchronized void rewind() { - this.position = 0; - this.mark = -1; + public int readInt(int position) { + byte[] buffer = this.bytes; + int value = (buffer[position] & 0xFF) << 24 + | (buffer[position + 1] & 0xFF) << 16 + | (buffer[position + 2] & 0xFF) << 8 + | buffer[position + 3] & 0xFF; + + this.position = position + 4; + return value; } /** - * Sets this buffer's mark at its position. + * Reads a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. */ - public final synchronized void mark() { - this.mark = this.position; + public int readInt(boolean optimizePositive) { + return readVarInt(optimizePositive); } /** - * Resets this buffer's position to the previously-marked position. + * Reads a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. + *

+ * does not advance the position + */ + public int readInt(int position, boolean optimizePositive) { + int pos = this.position; + this.position = position; + int value = readVarInt(optimizePositive); + this.position = pos; + return value; + } + + /** + * Reads a 1-5 byte int. It is guaranteed that a variable length encoding will be used. + */ + private int readVarInt(boolean optimizePositive) { + byte[] buffer = this.bytes; + + if (this.capacity - this.position < 5) { + return readInt_slow(optimizePositive); + } + + int b = buffer[this.position++]; + int result = b & 0x7F; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 28; + } + } + } + } + return optimizePositive ? result : result >>> 1 ^ -(result & 1); + } + + private int readInt_slow(boolean optimizePositive) { + byte[] buffer = this.bytes; + + // The buffer is guaranteed to have at least 1 byte. + int b = buffer[this.position++]; + int result = b & 0x7F; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 28; + } + } + } + } + return optimizePositive ? result : result >>> 1 ^ -(result & 1); + } + + /** + * Returns true if enough bytes are available to read an int with {@link #readInt(boolean)}. + */ + public boolean canReadInt() { + if (this.capacity - this.position >= 5) { + return true; + } + + if (this.position + 1 > this.capacity) { + return false; + } + + byte[] buffer = this.bytes; + + int p = this.position; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + return true; + } + + /** + * Returns true if enough bytes are available to read an int with {@link #readInt(boolean)}. + */ + public boolean canReadInt(int position) { + if (this.capacity - position >= 5) { + return true; + } + + if (position + 1 > this.capacity) { + return false; + } + + byte[] buffer = this.bytes; + + int p = position; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + return true; + } + + // string + + /** + * Writes the length and string, or null. Short strings are checked and if ASCII they are written more efficiently, + * else they are written as UTF8. If a string is known to be ASCII, {@link ByteBuffer2#writeAscii(String)} may be used. The + * string can be read using {@link ByteBuffer2#readString()} or {@link ByteBuffer2#readStringBuilder()}. * - *

Invoking this method neither changes nor discards the mark's - * value.

+ * @param value + * May be null. */ - public void reset() { - this.position = this.mark; + @SuppressWarnings("deprecation") + public void writeString(String value) { + if (value == null) { + writeByte(0x80); // 0 means null, bit 8 means UTF8. + return; + } + + int charCount = value.length(); + if (charCount == 0) { + writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8. + return; + } + + // Detect ASCII. + boolean ascii = false; + if (charCount > 1 && charCount < 64) { + ascii = true; + for (int i = 0; i < charCount; i++) { + int c = value.charAt(i); + if (c > 127) { + ascii = false; + break; + } + } + } + + if (ascii) { + if (this.capacity - this.position < charCount) { + writeAscii_slow(value, charCount); + } else { + value.getBytes(0, charCount, this.bytes, this.position); + this.position += charCount; + } + this.bytes[this.position - 1] |= 0x80; + } else { + writeUtf8Length(charCount + 1); + int charIndex = 0; + if (this.capacity - this.position >= charCount) { + // Try to write 8 bit chars. + byte[] buffer = this.bytes; + int position = this.position; + for (; charIndex < charCount; charIndex++) { + int c = value.charAt(charIndex); + if (c > 127) { + break; + } + buffer[position++] = (byte) c; + } + this.position = position; + } + + if (charIndex < charCount) { + writeString_slow(value, charCount, charIndex); + } + } } -} \ No newline at end of file + + /** + * Writes the length and CharSequence as UTF8, or null. The string can be read using {@link ByteBuffer2#readString()} or + * {@link ByteBuffer2#readStringBuilder()}. + * + * @param value + * May be null. + */ + public void writeString(CharSequence value) { + if (value == null) { + writeByte(0x80); // 0 means null, bit 8 means UTF8. + return; + } + + int charCount = value.length(); + if (charCount == 0) { + writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8. + return; + } + + writeUtf8Length(charCount + 1); + int charIndex = 0; + if (this.capacity - this.position >= charCount) { + // Try to write 8 bit chars. + byte[] buffer = this.bytes; + int position = this.position; + + for (; charIndex < charCount; charIndex++) { + int c = value.charAt(charIndex); + if (c > 127) { + break; + } + buffer[position++] = (byte) c; + } + this.position = position; + } + + if (charIndex < charCount) { + writeString_slow(value, charCount, charIndex); + } + } + + /** + * Writes a string that is known to contain only ASCII characters. Non-ASCII strings passed to this method will be + * corrupted. Each byte is a 7 bit character with the remaining byte denoting if another character is available. + * This is slightly more efficient than {@link ByteBuffer2#writeString(String)}. The string can be read using + * {@link ByteBuffer2#readString()} or {@link ByteBuffer2#readStringBuilder()}. + * + * @param value + * May be null. + */ + @SuppressWarnings("deprecation") + public void writeAscii(String value) { + if (value == null) { + writeByte(0x80); // 0 means null, bit 8 means UTF8. + return; + } + + int charCount = value.length(); + switch (charCount) { + case 0 : + writeByte(1 | 0x80); // 1 is string length + 1, bit 8 means UTF8. + return; + case 1 : + writeByte(2 | 0x80); // 2 is string length + 1, bit 8 means UTF8. + writeByte(value.charAt(0)); + return; + } + + if (this.capacity - this.position < charCount) { + writeAscii_slow(value, charCount); + } else { + value.getBytes(0, charCount, this.bytes, this.position); + this.position += charCount; + } + + this.bytes[this.position - 1] |= 0x80; // Bit 8 means end of ASCII. + } + + /** + * Writes the length of a string, which is a variable length encoded int except the first byte uses bit 8 to denote + * UTF8 and bit 7 to denote if another byte is present. + */ + private void writeUtf8Length(int value) { + + if (value >>> 6 == 0) { + require(1); + this.bytes[this.position++] = (byte) (value | 0x80); // Set bit 8. + } else if (value >>> 13 == 0) { + require(2); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value | 0x40 | 0x80); // Set bit 7 and 8. + buffer[this.position++] = (byte) (value >>> 6); + } else if (value >>> 20 == 0) { + require(3); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value | 0x40 | 0x80); // Set bit 7 and 8. + buffer[this.position++] = (byte) (value >>> 6 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 13); + } else if (value >>> 27 == 0) { + require(4); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value | 0x40 | 0x80); // Set bit 7 and 8. + buffer[this.position++] = (byte) (value >>> 6 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 13 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 20); + } else { + require(5); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value | 0x40 | 0x80); // Set bit 7 and 8. + buffer[this.position++] = (byte) (value >>> 6 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 13 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 20 | 0x80); // Set bit 8. + buffer[this.position++] = (byte) (value >>> 27); + } + } + + private void writeString_slow(CharSequence value, int charCount, int charIndex) { + byte[] buffer = this.bytes; + for (; charIndex < charCount; charIndex++) { + if (this.position == this.capacity) { + require(Math.min(this.capacity, charCount - charIndex)); + buffer = this.bytes; + } + + int c = value.charAt(charIndex); + if (c <= 0x007F) { + this.bytes[this.position++] = (byte) c; + } else if (c > 0x07FF) { + buffer[this.position++] = (byte) (0xE0 | c >> 12 & 0x0F); + require(2); + + buffer = this.bytes; + buffer[this.position++] = (byte) (0x80 | c >> 6 & 0x3F); + buffer[this.position++] = (byte) (0x80 | c & 0x3F); + } else { + buffer[this.position++] = (byte) (0xC0 | c >> 6 & 0x1F); + require(1); + buffer = this.bytes; + buffer[this.position++] = (byte) (0x80 | c & 0x3F); + } + } + } + + @SuppressWarnings("deprecation") + private void writeAscii_slow(String value, int charCount) { + byte[] buffer = this.bytes; + int charIndex = 0; + int charsToWrite = Math.min(charCount, this.capacity - this.position); + + while (charIndex < charCount) { + value.getBytes(charIndex, charIndex + charsToWrite, buffer, this.position); + charIndex += charsToWrite; + this.position += charsToWrite; + charsToWrite = Math.min(charCount - charIndex, this.capacity); + if (require(charsToWrite)) { + buffer = this.bytes; + } + } + } + + /** + * Reads the length and string of UTF8 characters, or null. This can read strings written by + * {@link ByteBuffer2#writeString(String)} , {@link ByteBuffer2#writeString(CharSequence)}, and + * {@link ByteBuffer2#writeAscii(String)}. + * + * @return May be null. + */ + public String readString() { + int available = this.capacity - this.position; + + int b = this.bytes[this.position++]; + if ((b & 0x80) == 0) { + return readAscii(); // ASCII. + } + + // Null, empty, or UTF8. + int charCount = available >= 5 ? readUtf8Length(b) : readUtf8Length_slow(b); + switch (charCount) { + case 0 : + return null; + case 1 : + return ""; + } + charCount--; + + if (this.chars.length < charCount) { + this.chars = new char[charCount]; + } + + if (available < charCount) { + throw new BufferUnderflowException(); + } + + readUtf8(charCount); + return new String(this.chars, 0, charCount); + } + + private int readUtf8Length(int b) { + int result = b & 0x3F; // Mask all but first 6 bits. + if ((b & 0x40) != 0) { // Bit 7 means another byte, bit 8 means UTF8. + byte[] buffer = this.bytes; + + b = buffer[this.position++]; + result |= (b & 0x7F) << 6; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 13; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 20; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 27; + } + } + } + } + return result; + } + + private int readUtf8Length_slow(int b) { + int result = b & 0x3F; // Mask all but first 6 bits. + if ((b & 0x40) != 0) { // Bit 7 means another byte, bit 8 means UTF8. + byte[] buffer = this.bytes; + + b = buffer[this.position++]; + result |= (b & 0x7F) << 6; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 13; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 20; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 27; + } + } + } + } + return result; + } + + private void readUtf8(int charCount) { + byte[] buffer = this.bytes; + int position = this.position; + char[] chars = this.chars; + + // Try to read 7 bit ASCII chars. + int charIndex = 0; + int spaceAvailable = this.capacity - this.position; + int count = Math.min(spaceAvailable, charCount); + + int b; + while (charIndex < count) { + b = buffer[position++]; + if (b < 0) { + position--; + break; + } + chars[charIndex++] = (char) b; + } + this.position = position; + + // If buffer didn't hold all chars or any were not ASCII, use slow path for remainder. + if (charIndex < charCount) { + readUtf8_slow(charCount, charIndex); + } + } + + private void readUtf8_slow(int charCount, int charIndex) { + char[] chars = this.chars; + byte[] buffer = this.bytes; + + while (charIndex < charCount) { + int b = buffer[this.position++] & 0xFF; + switch (b >> 4) { + case 0 : + case 1 : + case 2 : + case 3 : + case 4 : + case 5 : + case 6 : + case 7 : + chars[charIndex] = (char) b; + break; + case 12 : + case 13 : + chars[charIndex] = (char) ((b & 0x1F) << 6 | buffer[this.position++] & 0x3F); + break; + case 14 : + chars[charIndex] = (char) ((b & 0x0F) << 12 | (buffer[this.position++] & 0x3F) << 6 | buffer[this.position++] & 0x3F); + break; + } + charIndex++; + } + } + + private String readAscii() { + byte[] buffer = this.bytes; + int end = this.position; + int start = end - 1; + int limit = this.capacity; + int b; + + do { + if (end == limit) { + return readAscii_slow(); + } + b = buffer[end++]; + } while ((b & 0x80) == 0); + + buffer[end - 1] &= 0x7F; // Mask end of ascii bit. + + @SuppressWarnings("deprecation") + String value = new String(buffer, 0, start, end - start); + buffer[end - 1] |= 0x80; + + this.position = end; + return value; + } + + private String readAscii_slow() { + this.position--; // Re-read the first byte. + + // Copy chars currently in buffer. + int charCount = this.capacity - this.position; + if (charCount > this.chars.length) { + this.chars = new char[charCount * 2]; + } + + char[] chars = this.chars; + byte[] buffer = this.bytes; + for (int i = this.position, ii = 0, n = this.capacity; i < n; i++, ii++) { + chars[ii] = (char) buffer[i]; + } + this.position = this.capacity; + + // Copy additional chars one by one. + while (true) { + int b = buffer[this.position++]; + if (charCount == chars.length) { + char[] newChars = new char[charCount * 2]; + System.arraycopy(chars, 0, newChars, 0, charCount); + chars = newChars; + this.chars = newChars; + } + if ((b & 0x80) == 0x80) { + chars[charCount++] = (char) (b & 0x7F); + break; + } + chars[charCount++] = (char) b; + } + + return new String(chars, 0, charCount); + } + + /** + * Reads the length and string of UTF8 characters, or null. This can read strings written by + * {@link ByteBuffer2#writeString(String)} , {@link ByteBuffer2#writeString(CharSequence)}, and + * {@link ByteBuffer2#writeAscii(String)}. + * + * @return May be null. + */ + public StringBuilder readStringBuilder() { + int available = this.capacity - this.position; + int b = this.bytes[this.position++]; + if ((b & 0x80) == 0) { + return new StringBuilder(readAscii()); // ASCII. + } + + // Null, empty, or UTF8. + int charCount = available >= 5 ? readUtf8Length(b) : readUtf8Length_slow(b); + switch (charCount) { + case 0 : + return null; + case 1 : + return new StringBuilder(""); + } + charCount--; + if (this.chars.length < charCount) { + this.chars = new char[charCount]; + } + + readUtf8(charCount); + StringBuilder builder = new StringBuilder(charCount); + builder.append(this.chars, 0, charCount); + + return builder; + } + + + // float + + /** + * Writes a 4 byte float. + */ + public void writeFloat(float value) { + writeInt(Float.floatToIntBits(value)); + } + + /** + * Writes a 1-5 byte float with reduced precision. + * + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (5 bytes). + */ + public int writeFloat(float value, float precision, boolean optimizePositive) { + return writeInt((int) (value * precision), optimizePositive); + } + + /** + * Reads a 4 byte float. + */ + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads a 1-5 byte float with reduced precision. + */ + public float readFloat(float precision, boolean optimizePositive) { + return readInt(optimizePositive) / precision; + } + + /** + * Reads a 4 byte float, does not advance the position + */ + public float readFloat(int position) { + return Float.intBitsToFloat(readInt(position)); + } + + /** + * Reads a 1-5 byte float with reduced precision, does not advance the position + */ + public float readFloat(int position, float precision, boolean optimizePositive) { + return readInt(position, optimizePositive) / precision; + } + + // short + + /** + * Writes a 2 byte short. Uses BIG_ENDIAN byte order. + */ + public void writeShort(int value) { + require(2); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value >>> 8); + buffer[this.position++] = (byte) value; + } + + /** + * Reads a 2 byte short. + */ + public short readShort() { + byte[] buffer = this.bytes; + return (short) ((buffer[this.position++] & 0xFF) << 8 | buffer[this.position++] & 0xFF); + } + + /** + * Reads a 2 byte short as an int from 0 to 65535. + */ + public int readShortUnsigned() { + byte[] buffer = this.bytes; + return (buffer[this.position++] & 0xFF) << 8 | buffer[this.position++] & 0xFF; + } + + /** + * Reads a 2 byte short, does not advance the position + */ + public short readShort(int position) { + byte[] buffer = this.bytes; + return (short) ((buffer[position++] & 0xFF) << 8 | buffer[position] & 0xFF); + } + + /** + * Reads a 2 byte short as an int from 0 to 65535, does not advance the position + */ + public int readShortUnsigned(int position) { + byte[] buffer = this.bytes; + return (buffer[position++] & 0xFF) << 8 | buffer[position] & 0xFF; + } + + // long + + /** + * Writes an 8 byte long. Uses BIG_ENDIAN byte order. + */ + public void writeLong(long value) { + require(8); + + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value >>> 56); + buffer[this.position++] = (byte) (value >>> 48); + buffer[this.position++] = (byte) (value >>> 40); + buffer[this.position++] = (byte) (value >>> 32); + buffer[this.position++] = (byte) (value >>> 24); + buffer[this.position++] = (byte) (value >>> 16); + buffer[this.position++] = (byte) (value >>> 8); + buffer[this.position++] = (byte) value; + } + + /** + * Writes a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. + * + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (9 bytes). + */ + public int writeLong(long value, boolean optimizePositive) { + return writeVarLong(value, optimizePositive); + } + + /** + * Writes a 1-9 byte long. It is guaranteed that a varible length encoding will be used. + * + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (9 bytes). + */ + public int writeVarLong(long value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 63; + } + if (value >>> 7 == 0) { + require(1); + this.bytes[this.position++] = (byte) value; + return 1; + } + if (value >>> 14 == 0) { + require(2); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7); + return 2; + } + if (value >>> 21 == 0) { + require(3); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14); + return 3; + } + if (value >>> 28 == 0) { + require(4); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21); + return 4; + } + if (value >>> 35 == 0) { + require(5); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28); + return 5; + } + if (value >>> 42 == 0) { + require(6); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28 | 0x80); + buffer[this.position++] = (byte) (value >>> 35); + return 6; + } + if (value >>> 49 == 0) { + require(7); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28 | 0x80); + buffer[this.position++] = (byte) (value >>> 35 | 0x80); + buffer[this.position++] = (byte) (value >>> 42); + return 7; + } + if (value >>> 56 == 0) { + require(8); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28 | 0x80); + buffer[this.position++] = (byte) (value >>> 35 | 0x80); + buffer[this.position++] = (byte) (value >>> 42 | 0x80); + buffer[this.position++] = (byte) (value >>> 49); + return 8; + } + + require(9); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value & 0x7F | 0x80); + buffer[this.position++] = (byte) (value >>> 7 | 0x80); + buffer[this.position++] = (byte) (value >>> 14 | 0x80); + buffer[this.position++] = (byte) (value >>> 21 | 0x80); + buffer[this.position++] = (byte) (value >>> 28 | 0x80); + buffer[this.position++] = (byte) (value >>> 35 | 0x80); + buffer[this.position++] = (byte) (value >>> 42 | 0x80); + buffer[this.position++] = (byte) (value >>> 49 | 0x80); + buffer[this.position++] = (byte) (value >>> 56); + return 9; + } + + /** + * Returns true if enough bytes are available to read a long with {@link #readLong(boolean)}. + */ + public boolean canReadLong() { + if (this.capacity - this.position >= 9) { + return true; + } + + if (this.position + 1 > this.capacity) { + return false; + } + + byte[] buffer = this.bytes; + int p = this.position; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + return true; + } + + /** + * Returns true if enough bytes are available to read a long with {@link #readLong(boolean)}. + */ + public boolean canReadLong(int position) { + if (this.capacity - position >= 9) { + return true; + } + + if (position + 1 > this.capacity) { + return false; + } + + byte[] buffer = this.bytes; + int p = position; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == this.capacity) { + return false; + } + return true; + } + + /** + * Reads an 8 byte long. + */ + public long readLong() { + byte[] buffer = this.bytes; + + return (long) buffer[this.position++] << 56 + | (long) (buffer[this.position++] & 0xFF) << 48 + | (long) (buffer[this.position++] & 0xFF) << 40 + | (long) (buffer[this.position++] & 0xFF) << 32 + | (long) (buffer[this.position++] & 0xFF) << 24 + | (buffer[this.position++] & 0xFF) << 16 + | (buffer[this.position++] & 0xFF) << 8 + | buffer[this.position++] & 0xFF; + + } + + /** + * Reads a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. + */ + public long readLong(boolean optimizePositive) { + return readVarLong(optimizePositive); + } + + /** + * Reads an 8 byte long, does not advance the position + */ + public long readLong(int position) { + byte[] buffer = this.bytes; + + return (long) buffer[position++] << 56 + | (long) (buffer[position++] & 0xFF) << 48 + | (long) (buffer[position++] & 0xFF) << 40 + | (long) (buffer[position++] & 0xFF) << 32 + | (long) (buffer[position++] & 0xFF) << 24 + | (buffer[position++] & 0xFF) << 16 + | (buffer[position++] & 0xFF) << 8 + | buffer[position++] & 0xFF; + + } + + /** + * Reads a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not + * guaranteed that a variable length encoding will be really used. The stream may decide to use native-sized integer + * representation for efficiency reasons. + *

+ * does not advance the position + */ + public long readLong(int position, boolean optimizePositive) { + int pos = this.position; + this.position = position; + long value = readVarLong(optimizePositive); + this.position = pos; + return value; + } + + /** + * Reads a 1-9 byte long. It is guaranteed that a varible length encoding will be used. + */ + private long readVarLong(boolean optimizePositive) { + if (this.capacity - this.position < 9) { + return readLong_slow(optimizePositive); + } + + int b = this.bytes[this.position++]; + long result = b & 0x7F; + if ((b & 0x80) != 0) { + byte[] buffer = this.bytes; + b = buffer[this.position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 28; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 35; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 42; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 49; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) b << 56; + } + } + } + } + } + } + } + } + + if (!optimizePositive) { + result = result >>> 1 ^ -(result & 1); + } + + return result; + } + + private long readLong_slow(boolean optimizePositive) { + // The buffer is guaranteed to have at least 1 byte. + byte[] buffer = this.bytes; + int b = buffer[this.position++]; + + long result = b & 0x7F; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 28; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 35; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 42; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) (b & 0x7F) << 49; + if ((b & 0x80) != 0) { + b = buffer[this.position++]; + result |= (long) b << 56; + } + } + } + } + } + } + } + } + + if (!optimizePositive) { + result = result >>> 1 ^ -(result & 1); + } + return result; + } + + // boolean + + /** + * Writes a 1 byte boolean. + */ + public void writeBoolean(boolean value) { + require(1); + this.bytes[this.position++] = (byte) (value ? 1 : 0); + } + + /** + * Reads a 1 byte boolean. + */ + public boolean readBoolean () { + return this.bytes[this.position++] == 1; + } + + /** + * Reads a 1 byte boolean, does not advance the position + */ + public boolean readBoolean (int position) { + return this.bytes[position] == 1; + } + + // char + + /** + * Writes a 2 byte char. Uses BIG_ENDIAN byte order. + */ + public void writeChar(char value) { + require(2); + byte[] buffer = this.bytes; + buffer[this.position++] = (byte) (value >>> 8); + buffer[this.position++] = (byte) value; + } + + /** + * Reads a 2 byte char. + */ + public char readChar() { + byte[] buffer = this.bytes; + return (char) ((buffer[this.position++] & 0xFF) << 8 | buffer[this.position++] & 0xFF); + } + + /** + * Reads a 2 byte char, does not advance the position + */ + public char readChar(int position) { + byte[] buffer = this.bytes; + return (char) ((buffer[position++] & 0xFF) << 8 | buffer[position++] & 0xFF); + } + + // double + + /** + * Writes an 8 byte double. + */ + public void writeDouble(double value) { + writeLong(Double.doubleToLongBits(value)); + } + + /** + * Writes a 1-9 byte double with reduced precision + * + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (9 bytes). + */ + public int writeDouble(double value, double precision, boolean optimizePositive) { + return writeLong((long) (value * precision), optimizePositive); + } + + + /** + * Reads an 8 bytes double. + */ + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads a 1-9 byte double with reduced precision. + */ + public double readDouble(double precision, boolean optimizePositive) { + return readLong(optimizePositive) / precision; + } + + /** + * Reads an 8 bytes double, does not advance the position + */ + public double readDouble(int position) { + return Double.longBitsToDouble(readLong(position)); + } + + /** + * Reads a 1-9 byte double with reduced precision, does not advance the position + */ + public double readDouble(int position, double precision, boolean optimizePositive) { + return readLong(position, optimizePositive) / precision; + } + + + + + + /** + * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}. + */ + public static int intLength(int value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 31; + } + if (value >>> 7 == 0) { + return 1; + } + if (value >>> 14 == 0) { + return 2; + } + if (value >>> 21 == 0) { + return 3; + } + if (value >>> 28 == 0) { + return 4; + } + return 5; + } + + /** + * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}. + */ + public static int longLength(long value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 63; + } + if (value >>> 7 == 0) { + return 1; + } + if (value >>> 14 == 0) { + return 2; + } + if (value >>> 21 == 0) { + return 3; + } + if (value >>> 28 == 0) { + return 4; + } + if (value >>> 35 == 0) { + return 5; + } + if (value >>> 42 == 0) { + return 6; + } + if (value >>> 49 == 0) { + return 7; + } + if (value >>> 56 == 0) { + return 8; + } + return 9; + } + + // Methods implementing bulk operations on arrays of primitive types + + /** + * Bulk output of an int array. + */ + public void writeInts(int[] object, boolean optimizePositive) { + for (int i = 0, n = object.length; i < n; i++) { + writeInt(object[i], optimizePositive); + } + } + + /** + * Bulk input of an int array. + */ + public int[] readInts(int length, boolean optimizePositive) { + int[] array = new int[length]; + for (int i = 0; i < length; i++) { + array[i] = readInt(optimizePositive); + } + return array; + } + + /** + * Bulk output of an long array. + */ + public void writeLongs(long[] object, boolean optimizePositive) { + for (int i = 0, n = object.length; i < n; i++) { + writeLong(object[i], optimizePositive); + } + } + + /** + * Bulk input of a long array. + */ + public long[] readLongs(int length, boolean optimizePositive) { + long[] array = new long[length]; + for (int i = 0; i < length; i++) { + array[i] = readLong(optimizePositive); + } + return array; + } + + /** + * Bulk output of an int array. + */ + public void writeInts(int[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeInt(object[i]); + } + } + + /** + * Bulk input of an int array. + */ + public int[] readInts(int length) { + int[] array = new int[length]; + for (int i = 0; i < length; i++) { + array[i] = readInt(); + } + return array; + } + + /** + * Bulk output of an long array. + */ + public void writeLongs(long[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeLong(object[i]); + } + } + + /** + * Bulk input of a long array. + */ + public long[] readLongs(int length) { + long[] array = new long[length]; + for (int i = 0; i < length; i++) { + array[i] = readLong(); + } + return array; + } + + /** + * Bulk output of a float array. + */ + public void writeFloats(float[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeFloat(object[i]); + } + } + + /** + * Bulk input of a float array. + */ + public float[] readFloats(int length) { + float[] array = new float[length]; + for (int i = 0; i < length; i++) { + array[i] = readFloat(); + } + return array; + } + + /** + * Bulk output of a short array. + */ + public void writeShorts(short[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeShort(object[i]); + } + } + + /** + * Bulk input of a short array. + */ + public short[] readShorts(int length) { + short[] array = new short[length]; + for (int i = 0; i < length; i++) { + array[i] = readShort(); + } + return array; + } + + /** + * Bulk output of a char array. + */ + public void writeChars(char[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeChar(object[i]); + } + } + + /** + * Bulk input of a char array. + */ + public char[] readChars(int length) { + char[] array = new char[length]; + for (int i = 0; i < length; i++) { + array[i] = readChar(); + } + return array; + } + + /** + * Bulk output of a double array. + */ + public void writeDoubles(double[] object) { + for (int i = 0, n = object.length; i < n; i++) { + writeDouble(object[i]); + } + } + + /** + * Bulk input of a double array + */ + public double[] readDoubles(int length) { + double[] array = new double[length]; + for (int i = 0; i < length; i++) { + array[i] = readDouble(); + } + return array; + } + + + @Override + public boolean equals(Object other) { + if (!(other instanceof ByteBuffer2)) { + return false; + } + + // CANNOT be null, so we don't have to null check! + return Arrays.equals(this.bytes, ((ByteBuffer2) other).bytes); + } + + @Override + public int hashCode() { + // might be null for a thread because it's stale. who cares, get the value again + return Arrays.hashCode(this.bytes); + } + + @Override + public String toString() { + return "ByteBuffer2 " + java.util.Arrays.toString(this.bytes); + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Fast.java b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Fast.java deleted file mode 100644 index 0e26bd5..0000000 --- a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Fast.java +++ /dev/null @@ -1,338 +0,0 @@ -package dorkbox.util.bytes; - -import java.nio.BufferUnderflowException; - -/** - * Cleanroom implementation of a self-growing bytebuffer. NOT SYNCHRONIZED! - */ -public class ByteBuffer2Fast { - - private byte[] bytes; - - private int position = 0; - private int mark = -1; - private int limit = 0; - - public static ByteBuffer2Fast wrap(byte[] buffer) { - return new ByteBuffer2Fast(buffer); - } - - public static ByteBuffer2Fast allocate(int capacity) { - ByteBuffer2Fast byteBuffer2 = new ByteBuffer2Fast(new byte[capacity]); - byteBuffer2.clear(); - return byteBuffer2; - } - - public ByteBuffer2Fast() { - this(0); - } - - public ByteBuffer2Fast(int size) { - this(new byte[size]); - } - - public ByteBuffer2Fast(byte[] bytes) { - this.bytes = bytes; - clear(); - this.position = bytes.length; - } - - public byte getByte() { - if (this.position > this.limit) { - throw new BufferUnderflowException(); - } - - return this.bytes[this.position++]; - } - - public byte getByte(int i) { - if (i > this.limit) { - throw new BufferUnderflowException(); - } - - return this.bytes[i]; - } - - public void getBytes(byte[] buffer) { - getBytes(buffer, 0, buffer.length); - } - - public void getBytes(byte[] buffer, int length) { - getBytes(buffer, 0, length); - } - - public void getBytes(byte[] buffer, int offset, int length) { - if (this.position + length - offset > this.limit) { - throw new BufferUnderflowException(); - } - - System.arraycopy(this.bytes, this.position, buffer, 0, length-offset); - this.position += length-offset; - } - - /** - * MUST call checkBuffer before calling this! - * - * NOT PROTECTED - */ - private final void _put(byte b) { - this.bytes[this.position++] = b; - } - - /** NOT PROTECTED! */ - private final void checkBuffer(int threshold) { - if (this.bytes.length < threshold) { - byte[] t = new byte[threshold]; - // grow at back of array - System.arraycopy(this.bytes, 0, t, 0, this.bytes.length); - this.limit = t.length; - - this.bytes = t; - } - } - - public final void put(ByteBuffer2Fast buffer) { - putBytes(buffer.array(), buffer.position, buffer.limit); - buffer.position = buffer.limit; - } - - public final ByteBuffer2Fast putBytes(byte[] src) { - return putBytes(src, 0, src.length); - } - - public final ByteBuffer2Fast putBytes(byte[] src, int offset, int length) { - checkBuffer(this.position + length - offset); - - System.arraycopy(src, offset, this.bytes, this.position, length); - this.position += length; - - return this; - } - - public final ByteBuffer2Fast putByte(byte b) { - checkBuffer(this.position + 1); - - _put(b); - return this; - } - - public final void putByte(int position, byte b) { - this.position = position; - putByte(b); - } - - public final void putChar(char c) { - checkBuffer(this.position + 2); - - putBytes(BigEndian.Char_.toBytes(c)); - } - - public final char getChar() { - return BigEndian.Char_.fromBytes(getByte(), getByte()); - } - - public final char getChar(int i) { - return BigEndian.Char_.fromBytes(getByte(i++), getByte(i)); - } - - public void getChars(int srcStart, int srcLength, char[] dest, int destStart) { - for (int i=srcStart;i this.bytes.length || position < 0) { - throw new IllegalArgumentException(); - } - - this.position = position; - if (this.mark > position) { - this.mark = -1; - } - - return this; - } - - /** - * Returns the number of elements between the current position and the - * limit. - */ - public final int remaining() { - return this.limit - this.position; - } - - /** - * Tells whether there are any elements between the current position and - * the limit. - */ - public final boolean hasRemaining() { - return this.position < this.limit; - } - - /** - * Sets this buffer's limit. - */ - public final void limit(int limit) { - this.limit = limit; - if (this.position > limit) { - this.position = limit; - } - if (this.mark > limit) { - this.mark = -1; - } - } - - /** - * Returns this buffer's limit. - */ - public int limit() { - return this.limit; - } - - /** - * Returns this buffer's capacity. - */ - public int capacity() { - return this.bytes.length; - } - - /** - * The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer. - * That is, the byte at index p = position() is copied to index zero, the byte at index p + 1 is copied to index one, - * and so forth until the byte at index limit() - 1 is copied to index n = limit() - 1 - p. The buffer's position is - * then set to n+1 and its limit is set to its capacity. The mark, if defined, is discarded. - * - * The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method - * can be followed immediately by an invocation of another relative put method. - */ - public final void compact() { - this.mark = -1; - System.arraycopy(this.bytes, this.position, this.bytes, 0, remaining()); - - position(remaining()); - limit(capacity()); - } - - /** - * Readies the buffer for reading. - * - * Flips this buffer. The limit is set to the current position and then - * the position is set to zero. If the mark is defined then it is - * discarded. - */ - public final void flip() { - this.limit = this.position; - this.position = 0; - this.mark = -1; - } - - /** - * Clears this buffer. The position is set to zero, the limit is set to - * the capacity, and the mark is discarded. - */ - public final void clear() { - this.position = 0; - this.limit = capacity(); - this.mark = -1; - } - - /** - * Rewinds this buffer. The position is set to zero and the mark is - * discarded. - */ - public final void rewind() { - this.position = 0; - this.mark = -1; - } - - /** - * Sets this buffer's mark at its position. - */ - public final void mark() { - this.mark = this.position; - } - - /** - * Resets this buffer's position to the previously-marked position. - * - *

Invoking this method neither changes nor discards the mark's - * value.

- */ - public void reset() { - this.position = this.mark; - } -} \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Poolable.java b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Poolable.java new file mode 100644 index 0000000..708515f --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/bytes/ByteBuffer2Poolable.java @@ -0,0 +1,20 @@ +package dorkbox.util.bytes; + +import dorkbox.util.objectPool.PoolableObject; + +public class ByteBuffer2Poolable implements PoolableObject { + @Override + public ByteBuffer2 create() { + return new ByteBuffer2(8, -1); + } + + @Override + public void activate(ByteBuffer2 object) { + object.clear(); + } + + @Override + public void passivate(ByteBuffer2 object) { + } +} + diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java b/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java index 8666462..5d34209 100644 --- a/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java +++ b/Dorkbox-Util/src/dorkbox/util/bytes/LittleEndian.java @@ -2,6 +2,11 @@ package dorkbox.util.bytes; import java.nio.ByteBuffer; +/** + * This is intel/amd/arm arch! + * + * arm is technically bi-endian + */ public class LittleEndian { // the following are ALL in Little-Endian (big is to the right, first byte is least significant, unsigned bytes) @@ -24,10 +29,9 @@ public class LittleEndian { (b0 & 0xFF) << 0); } - public static final byte[] toBytes(char x) { - return new byte[] {(byte) (x >> 8), - (byte) (x >> 0) + return new byte[] {(byte) (x >> 0), + (byte) (x >> 8) }; } diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java deleted file mode 100644 index 3b6a9ff..0000000 --- a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtils.java +++ /dev/null @@ -1,541 +0,0 @@ -package dorkbox.util.bytes; - -public class OptimizeUtils { - - private static final OptimizeUtils instance = new OptimizeUtils(); - - public static OptimizeUtils get() { - return instance; - } - - // int - - /** - * FROM KRYO - * - * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - */ - public final int intLength (int value, boolean optimizePositive) { - if (!optimizePositive) { - value = value << 1 ^ value >> 31; - } - if (value >>> 7 == 0) { - return 1; - } - if (value >>> 14 == 0) { - return 2; - } - if (value >>> 21 == 0) { - return 3; - } - if (value >>> 28 == 0) { - return 4; - } - return 5; - } - - /** - * FROM KRYO - * - * look at buffer, and see if we can read the length of the int off of it. (from the reader index) - */ - public final boolean canReadInt (ByteBuffer2 buffer) { - int position = buffer.position(); - try { - int remaining = buffer.remaining(); - for (int offset = 0; offset < 32 && remaining > 0; offset += 7, remaining--) { - int b = buffer.getByte(); - if ((b & 0x80) == 0) { - return true; - } - } - return false; - } finally { - buffer.position(position); - } - } - - /** - * FROM KRYO - * - * look at buffer, and see if we can read the length of the int off of it. (from the reader index) - * - * @return 0 if we could not read anything, >0 for the number of bytes for the int on the buffer - */ - public boolean canReadInt (byte[] buffer) { - int length = buffer.length; - - if (length >= 5) { - return true; - } - int p = 0; - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == length) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == length) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == length) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == length) { - return false; - } - return true; - } - - /** - * FROM KRYO - * - * Reads an int from the buffer that was optimized. - */ - public final int readInt (ByteBuffer2 buffer, boolean optimizePositive) { - int b = buffer.getByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 28; - } - } - } - } - return optimizePositive ? result : result >>> 1 ^ -(result & 1); - } - - /** - * FROM KRYO - * - * Reads an int from the buffer that was optimized. - */ - public int readInt (byte[] buffer, boolean optimizePositive) { - int position = 0; - int b = buffer[position++]; - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 28; - } - } - } - } - return optimizePositive ? result : result >>> 1 ^ -(result & 1); - } - - - /** - * FROM KRYO - * - * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - * @return the number of bytes written. - */ - public final int writeInt (ByteBuffer2 buffer, int value, boolean optimizePositive) { - if (!optimizePositive) { - value = value << 1 ^ value >> 31; - } - if (value >>> 7 == 0) { - buffer.putByte((byte)value); - return 1; - } - if (value >>> 14 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7)); - return 2; - } - if (value >>> 21 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14)); - return 3; - } - if (value >>> 28 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21)); - return 4; - } - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28)); - return 5; - } - - /** - * FROM KRYO - * - * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - * @return the number of bytes written. - */ - public int writeInt (byte[] buffer, int value, boolean optimizePositive) { - int position = 0; - if (!optimizePositive) { - value = value << 1 ^ value >> 31; - } - if (value >>> 7 == 0) { - buffer[position++] = (byte)value; - return 1; - } - if (value >>> 14 == 0) { - buffer[position++] = (byte)(value & 0x7F | 0x80); - buffer[position++] = (byte)(value >>> 7); - return 2; - } - if (value >>> 21 == 0) { - buffer[position++] = (byte)(value & 0x7F | 0x80); - buffer[position++] = (byte)(value >>> 7 | 0x80); - buffer[position++] = (byte)(value >>> 14); - return 3; - } - if (value >>> 28 == 0) { - buffer[position++] = (byte)(value & 0x7F | 0x80); - buffer[position++] = (byte)(value >>> 7 | 0x80); - buffer[position++] = (byte)(value >>> 14 | 0x80); - buffer[position++] = (byte)(value >>> 21); - return 4; - } - buffer[position++] = (byte)(value & 0x7F | 0x80); - buffer[position++] = (byte)(value >>> 7 | 0x80); - buffer[position++] = (byte)(value >>> 14 | 0x80); - buffer[position++] = (byte)(value >>> 21 | 0x80); - buffer[position++] = (byte)(value >>> 28); - return 5; - } - - - // long - - /** - * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - */ - public final int longLength (long value, boolean optimizePositive) { - if (!optimizePositive) { - value = value << 1 ^ value >> 63; - } - if (value >>> 7 == 0) { - return 1; - } - if (value >>> 14 == 0) { - return 2; - } - if (value >>> 21 == 0) { - return 3; - } - if (value >>> 28 == 0) { - return 4; - } - if (value >>> 35 == 0) { - return 5; - } - if (value >>> 42 == 0) { - return 6; - } - if (value >>> 49 == 0) { - return 7; - } - if (value >>> 56 == 0) { - return 8; - } - return 9; - } - - /** - * FROM KRYO - * - * Reads a 1-9 byte long. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - */ - public final long readLong (ByteBuffer2 buffer, boolean optimizePositive) { - int b = buffer.getByte(); - long result = b & 0x7F; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (long)(b & 0x7F) << 28; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (long)(b & 0x7F) << 35; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (long)(b & 0x7F) << 42; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (long)(b & 0x7F) << 49; - if ((b & 0x80) != 0) { - b = buffer.getByte(); - result |= (long)b << 56; - } - } - } - } - } - } - } - } - if (!optimizePositive) { - result = result >>> 1 ^ -(result & 1); - } - return result; - } - - /** - * FROM KRYO - * - * Reads a 1-9 byte long. - * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value - */ - public long readLong (byte[] buffer, boolean optimizePositive) { - int position = 0; - int b = buffer[position++]; - long result = b & 0x7F; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (long)(b & 0x7F) << 28; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (long)(b & 0x7F) << 35; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (long)(b & 0x7F) << 42; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (long)(b & 0x7F) << 49; - if ((b & 0x80) != 0) { - b = buffer[position++]; - result |= (long)b << 56; - } - } - } - } - } - } - } - } - if (!optimizePositive) { - result = result >>> 1 ^ -(result & 1); - } - return result; - } - - /** - * FROM KRYO - * - * Writes a 1-9 byte long. - * - * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be - * inefficient (9 bytes). - * @return the number of bytes written. - */ - public final int writeLong (ByteBuffer2 buffer, long value, boolean optimizePositive) { - if (!optimizePositive) { - value = value << 1 ^ value >> 63; - } - if (value >>> 7 == 0) { - buffer.putByte((byte)value); - return 1; - } - if (value >>> 14 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7)); - return 2; - } - if (value >>> 21 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14)); - return 3; - } - if (value >>> 28 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21)); - return 4; - } - if (value >>> 35 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28)); - return 5; - } - if (value >>> 42 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28 | 0x80)); - buffer.putByte((byte)(value >>> 35)); - return 6; - } - if (value >>> 49 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28 | 0x80)); - buffer.putByte((byte)(value >>> 35 | 0x80)); - buffer.putByte((byte)(value >>> 42)); - return 7; - } - if (value >>> 56 == 0) { - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28 | 0x80)); - buffer.putByte((byte)(value >>> 35 | 0x80)); - buffer.putByte((byte)(value >>> 42 | 0x80)); - buffer.putByte((byte)(value >>> 49)); - return 8; - } - buffer.putByte((byte)(value & 0x7F | 0x80)); - buffer.putByte((byte)(value >>> 7 | 0x80)); - buffer.putByte((byte)(value >>> 14 | 0x80)); - buffer.putByte((byte)(value >>> 21 | 0x80)); - buffer.putByte((byte)(value >>> 28 | 0x80)); - buffer.putByte((byte)(value >>> 35 | 0x80)); - buffer.putByte((byte)(value >>> 42 | 0x80)); - buffer.putByte((byte)(value >>> 49 | 0x80)); - buffer.putByte((byte)(value >>> 56)); - return 9; - } - - /** - * FROM KRYO - * - * look at buffer, and see if we can read the length of the long off of it (from the reader index). - */ - public final boolean canReadLong (ByteBuffer2 buffer) { - int position = buffer.position(); - try { - int remaining = buffer.remaining(); - for (int offset = 0; offset < 64 && remaining > 0; offset += 7, remaining--) { - int b = buffer.getByte(); - if ((b & 0x80) == 0) { - return true; - } - } - return false; - } finally { - buffer.position(position); - } - } - - public boolean canReadLong (byte[] buffer) { - int limit = buffer.length; - - if (limit >= 9) { - return true; - } - int p = 0; - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - if ((buffer[p++] & 0x80) == 0) { - return true; - } - if (p == limit) { - return false; - } - return true; - } -} diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteArray.java b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteArray.java new file mode 100644 index 0000000..cc1d7e7 --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteArray.java @@ -0,0 +1,284 @@ +package dorkbox.util.bytes; + +public class OptimizeUtilsByteArray { + + private static final OptimizeUtilsByteArray instance = new OptimizeUtilsByteArray(); + + public static OptimizeUtilsByteArray get() { + return instance; + } + + private OptimizeUtilsByteArray() { + } + + // int + + /** + * FROM KRYO + * + * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}. + * + * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + */ + public final int intLength (int value, boolean optimizePositive) { + return ByteBuffer2.intLength(value, optimizePositive); + } + + /** + * FROM KRYO + * + * look at buffer, and see if we can read the length of the int off of it. (from the reader index) + * + * @return 0 if we could not read anything, >0 for the number of bytes for the int on the buffer + */ + public boolean canReadInt(byte[] buffer) { + int length = buffer.length; + + if (length >= 5) { + return true; + } + int p = 0; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == length) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == length) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == length) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == length) { + return false; + } + return true; + } + + /** + * FROM KRYO + * + * Reads an int from the buffer that was optimized. + */ + public int readInt (byte[] buffer, boolean optimizePositive) { + int position = 0; + int b = buffer[position++]; + int result = b & 0x7F; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 28; + } + } + } + } + return optimizePositive ? result : result >>> 1 ^ -(result & 1); + } + + /** + * FROM KRYO + * + * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number. + * + * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + * @return the number of bytes written. + */ + public int writeInt (byte[] buffer, int value, boolean optimizePositive) { + int position = 0; + if (!optimizePositive) { + value = value << 1 ^ value >> 31; + } + if (value >>> 7 == 0) { + buffer[position++] = (byte)value; + return 1; + } + if (value >>> 14 == 0) { + buffer[position++] = (byte)(value & 0x7F | 0x80); + buffer[position++] = (byte)(value >>> 7); + return 2; + } + if (value >>> 21 == 0) { + buffer[position++] = (byte)(value & 0x7F | 0x80); + buffer[position++] = (byte)(value >>> 7 | 0x80); + buffer[position++] = (byte)(value >>> 14); + return 3; + } + if (value >>> 28 == 0) { + buffer[position++] = (byte)(value & 0x7F | 0x80); + buffer[position++] = (byte)(value >>> 7 | 0x80); + buffer[position++] = (byte)(value >>> 14 | 0x80); + buffer[position++] = (byte)(value >>> 21); + return 4; + } + buffer[position++] = (byte)(value & 0x7F | 0x80); + buffer[position++] = (byte)(value >>> 7 | 0x80); + buffer[position++] = (byte)(value >>> 14 | 0x80); + buffer[position++] = (byte)(value >>> 21 | 0x80); + buffer[position++] = (byte)(value >>> 28); + return 5; + } + + + // long + + /** + * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}. + * + * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + */ + public final int longLength (long value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 63; + } + if (value >>> 7 == 0) { + return 1; + } + if (value >>> 14 == 0) { + return 2; + } + if (value >>> 21 == 0) { + return 3; + } + if (value >>> 28 == 0) { + return 4; + } + if (value >>> 35 == 0) { + return 5; + } + if (value >>> 42 == 0) { + return 6; + } + if (value >>> 49 == 0) { + return 7; + } + if (value >>> 56 == 0) { + return 8; + } + return 9; + } + + /** + * FROM KRYO + * + * Reads a 1-9 byte long. + * + * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + */ + public long readLong (byte[] buffer, boolean optimizePositive) { + int position = 0; + int b = buffer[position++]; + long result = b & 0x7F; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (long)(b & 0x7F) << 28; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (long)(b & 0x7F) << 35; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (long)(b & 0x7F) << 42; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (long)(b & 0x7F) << 49; + if ((b & 0x80) != 0) { + b = buffer[position++]; + result |= (long)b << 56; + } + } + } + } + } + } + } + } + if (!optimizePositive) { + result = result >>> 1 ^ -(result & 1); + } + return result; + } + + public boolean canReadLong (byte[] buffer) { + int limit = buffer.length; + + if (limit >= 9) { + return true; + } + int p = 0; + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + if ((buffer[p++] & 0x80) == 0) { + return true; + } + if (p == limit) { + return false; + } + return true; + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java index 6fc6a56..7a3e77d 100644 --- a/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java +++ b/Dorkbox-Util/src/dorkbox/util/bytes/OptimizeUtilsByteBuf.java @@ -17,14 +17,25 @@ public class OptimizeUtilsByteBuf { * * Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}. * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + * @param optimizePositive + * true if you want to optimize the number of bytes needed to write the length value */ - public final int intLength (int value, boolean optimizePositive) { - if (!optimizePositive) value = (value << 1) ^ (value >> 31); - if (value >>> 7 == 0) return 1; - if (value >>> 14 == 0) return 2; - if (value >>> 21 == 0) return 3; - if (value >>> 28 == 0) return 4; + public final int intLength(int value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 31; + } + if (value >>> 7 == 0) { + return 1; + } + if (value >>> 14 == 0) { + return 2; + } + if (value >>> 21 == 0) { + return 3; + } + if (value >>> 28 == 0) { + return 4; + } return 5; } @@ -35,7 +46,7 @@ public class OptimizeUtilsByteBuf { * * @return 0 if we could not read anything, >0 for the number of bytes for the int on the buffer */ - public final int canReadInt (ByteBuf buffer) { + public final int canReadInt(ByteBuf buffer) { int startIndex = buffer.readerIndex(); try { int remaining = buffer.readableBytes(); @@ -56,7 +67,7 @@ public class OptimizeUtilsByteBuf { * * Reads an int from the buffer that was optimized. */ - public final int readInt (ByteBuf buffer, boolean optimizePositive) { + public final int readInt(ByteBuf buffer, boolean optimizePositive) { int b = buffer.readByte(); int result = b & 0x7F; if ((b & 0x80) != 0) { @@ -75,70 +86,88 @@ public class OptimizeUtilsByteBuf { } } } - return optimizePositive ? result : ((result >>> 1) ^ -(result & 1)); + return optimizePositive ? result : result >>> 1 ^ -(result & 1); } - /** * FROM KRYO * * Writes the specified int to the buffer using 1 to 5 bytes, depending on the size of the number. * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + * @param optimizePositive + * true if you want to optimize the number of bytes needed to write the length value * @return the number of bytes written. */ - public final int writeInt (ByteBuf buffer, int value, boolean optimizePositive) { - if (!optimizePositive) value = (value << 1) ^ (value >> 31); + public final int writeInt(ByteBuf buffer, int value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 31; + } if (value >>> 7 == 0) { - buffer.writeByte((byte)value); + buffer.writeByte((byte) value); return 1; } if (value >>> 14 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7)); return 2; } if (value >>> 21 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14)); return 3; } if (value >>> 28 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21)); return 4; } - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28)); return 5; } - - - // long /** * Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}. * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + * @param optimizePositive + * true if you want to optimize the number of bytes needed to write the length value */ - public final int longLength (long value, boolean optimizePositive) { - if (!optimizePositive) value = (value << 1) ^ (value >> 63); - if (value >>> 7 == 0) return 1; - if (value >>> 14 == 0) return 2; - if (value >>> 21 == 0) return 3; - if (value >>> 28 == 0) return 4; - if (value >>> 35 == 0) return 5; - if (value >>> 42 == 0) return 6; - if (value >>> 49 == 0) return 7; - if (value >>> 56 == 0) return 8; + public final int longLength(long value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 63; + } + if (value >>> 7 == 0) { + return 1; + } + if (value >>> 14 == 0) { + return 2; + } + if (value >>> 21 == 0) { + return 3; + } + if (value >>> 28 == 0) { + return 4; + } + if (value >>> 35 == 0) { + return 5; + } + if (value >>> 42 == 0) { + return 6; + } + if (value >>> 49 == 0) { + return 7; + } + if (value >>> 56 == 0) { + return 8; + } return 9; } @@ -147,9 +176,10 @@ public class OptimizeUtilsByteBuf { * * Reads a 1-9 byte long. * - * @param optimizePositive true if you want to optimize the number of bytes needed to write the length value + * @param optimizePositive + * true if you want to optimize the number of bytes needed to write the length value */ - public final long readLong (ByteBuf buffer, boolean optimizePositive) { + public final long readLong(ByteBuf buffer, boolean optimizePositive) { int b = buffer.readByte(); long result = b & 0x7F; if ((b & 0x80) != 0) { @@ -163,19 +193,19 @@ public class OptimizeUtilsByteBuf { result |= (b & 0x7F) << 21; if ((b & 0x80) != 0) { b = buffer.readByte(); - result |= (long)(b & 0x7F) << 28; + result |= (long) (b & 0x7F) << 28; if ((b & 0x80) != 0) { b = buffer.readByte(); - result |= (long)(b & 0x7F) << 35; + result |= (long) (b & 0x7F) << 35; if ((b & 0x80) != 0) { b = buffer.readByte(); - result |= (long)(b & 0x7F) << 42; + result |= (long) (b & 0x7F) << 42; if ((b & 0x80) != 0) { b = buffer.readByte(); - result |= (long)(b & 0x7F) << 49; + result |= (long) (b & 0x7F) << 49; if ((b & 0x80) != 0) { b = buffer.readByte(); - result |= (long)b << 56; + result |= (long) b << 56; } } } @@ -184,91 +214,95 @@ public class OptimizeUtilsByteBuf { } } } - if (!optimizePositive) result = (result >>> 1) ^ -(result & 1); + if (!optimizePositive) { + result = result >>> 1 ^ -(result & 1); + } return result; } - /** * FROM KRYO * * Writes a 1-9 byte long. * - * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be - * inefficient (9 bytes). + * @param optimizePositive + * If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be + * inefficient (9 bytes). * @return the number of bytes written. */ - public final int writeLong (ByteBuf buffer, long value, boolean optimizePositive) { - if (!optimizePositive) value = (value << 1) ^ (value >> 63); + public final int writeLong(ByteBuf buffer, long value, boolean optimizePositive) { + if (!optimizePositive) { + value = value << 1 ^ value >> 63; + } if (value >>> 7 == 0) { - buffer.writeByte((byte)value); + buffer.writeByte((byte) value); return 1; } if (value >>> 14 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7)); return 2; } if (value >>> 21 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14)); return 3; } if (value >>> 28 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21)); return 4; } if (value >>> 35 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28)); return 5; } if (value >>> 42 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28 | 0x80)); - buffer.writeByte((byte)(value >>> 35)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28 | 0x80)); + buffer.writeByte((byte) (value >>> 35)); return 6; } if (value >>> 49 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28 | 0x80)); - buffer.writeByte((byte)(value >>> 35 | 0x80)); - buffer.writeByte((byte)(value >>> 42)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28 | 0x80)); + buffer.writeByte((byte) (value >>> 35 | 0x80)); + buffer.writeByte((byte) (value >>> 42)); return 7; } if (value >>> 56 == 0) { - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28 | 0x80)); - buffer.writeByte((byte)(value >>> 35 | 0x80)); - buffer.writeByte((byte)(value >>> 42 | 0x80)); - buffer.writeByte((byte)(value >>> 49)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28 | 0x80)); + buffer.writeByte((byte) (value >>> 35 | 0x80)); + buffer.writeByte((byte) (value >>> 42 | 0x80)); + buffer.writeByte((byte) (value >>> 49)); return 8; } - buffer.writeByte((byte)((value & 0x7F) | 0x80)); - buffer.writeByte((byte)(value >>> 7 | 0x80)); - buffer.writeByte((byte)(value >>> 14 | 0x80)); - buffer.writeByte((byte)(value >>> 21 | 0x80)); - buffer.writeByte((byte)(value >>> 28 | 0x80)); - buffer.writeByte((byte)(value >>> 35 | 0x80)); - buffer.writeByte((byte)(value >>> 42 | 0x80)); - buffer.writeByte((byte)(value >>> 49 | 0x80)); - buffer.writeByte((byte)(value >>> 56)); + buffer.writeByte((byte) (value & 0x7F | 0x80)); + buffer.writeByte((byte) (value >>> 7 | 0x80)); + buffer.writeByte((byte) (value >>> 14 | 0x80)); + buffer.writeByte((byte) (value >>> 21 | 0x80)); + buffer.writeByte((byte) (value >>> 28 | 0x80)); + buffer.writeByte((byte) (value >>> 35 | 0x80)); + buffer.writeByte((byte) (value >>> 42 | 0x80)); + buffer.writeByte((byte) (value >>> 49 | 0x80)); + buffer.writeByte((byte) (value >>> 56)); return 9; } @@ -279,7 +313,7 @@ public class OptimizeUtilsByteBuf { * * @return 0 if we could not read anything, >0 for the number of bytes for the long on the buffer */ - public final int canReadLong (ByteBuf buffer) { + public final int canReadLong(ByteBuf buffer) { int position = buffer.readerIndex(); try { int remaining = buffer.readableBytes(); diff --git a/Dorkbox-Util/src/dorkbox/util/input/Encoding.java b/Dorkbox-Util/src/dorkbox/util/input/Encoding.java new file mode 100644 index 0000000..47ff14f --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/input/Encoding.java @@ -0,0 +1,38 @@ +package dorkbox.util.input; + +import java.nio.charset.Charset; + +public class Encoding { + /** + * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding + * system property, then the default charset according to the JVM. + * + * @return The default encoding to use when none is specified. + */ + public static String get() { + // LC_CTYPE is usually in the form en_US.UTF-8 + String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); + if (envEncoding != null) { + return envEncoding; + } + return System.getProperty("input.encoding", Charset.defaultCharset().name()); + } + + /** + * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE + * environment variable may be of the format [language[_territory][.codeset][@modifier]] + * + * @param ctype The ctype to parse, may be null + * @return The encoding, if one was present, otherwise null + */ + private static String extractEncodingFromCtype(String ctype) { + if (ctype != null && ctype.indexOf('.') > 0) { + String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); + if (encodingAndModifier.indexOf('@') > 0) { + return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); + } + return encodingAndModifier; + } + return null; + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java b/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java index c50784c..98d2d85 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java +++ b/Dorkbox-Util/src/dorkbox/util/input/InputConsole.java @@ -1,14 +1,11 @@ package dorkbox.util.input; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.io.Reader; import java.net.URL; -import java.nio.charset.Charset; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.concurrent.CopyOnWriteArrayList; @@ -18,11 +15,14 @@ import org.fusesource.jansi.AnsiConsole; import org.slf4j.Logger; import dorkbox.util.OS; -import dorkbox.util.Sys; -import dorkbox.util.bytes.ByteBuffer2Fast; +import dorkbox.util.bytes.ByteBuffer2; +import dorkbox.util.bytes.ByteBuffer2Poolable; import dorkbox.util.input.posix.UnixTerminal; import dorkbox.util.input.unsupported.UnsupportedTerminal; import dorkbox.util.input.windows.WindowsTerminal; +import dorkbox.util.objectPool.ObjectPool; +import dorkbox.util.objectPool.ObjectPoolFactory; +import dorkbox.util.objectPool.ObjectPoolHolder; public class InputConsole { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InputConsole.class); @@ -108,30 +108,15 @@ public class InputConsole { private final Object inputLockSingle = new Object(); private final Object inputLockLine = new Object(); - private ThreadLocal threadBufferForRead = new ThreadLocal(); - private CopyOnWriteArrayList threadBuffersForRead = new CopyOnWriteArrayList(); - - ThreadLocal indexOfStringForReadChar = new ThreadLocal() { - @Override - protected Integer initialValue() { - return -1; - } - }; - + private final ObjectPool pool = ObjectPoolFactory.create(new ByteBuffer2Poolable()); + private ThreadLocal> threadBufferForRead = new ThreadLocal>(); + private CopyOnWriteArrayList> threadBuffersForRead = new CopyOnWriteArrayList>(); private volatile int readChar = -1; - - private final boolean unsupported; - private final Terminal terminal; - private Reader reader; - private final String encoding; - - private InputConsole() { Logger logger = InputConsole.logger; - boolean unsupported = false; String type = System.getProperty(TerminalType.TYPE, TerminalType.AUTO).toLowerCase(); if ("dumb".equals(System.getenv("TERM"))) { @@ -141,48 +126,42 @@ public class InputConsole { logger.debug("Creating terminal; type={}", type); - Terminal t; + + String encoding = Encoding.get(); + Terminal t; try { if (type.equals(TerminalType.UNIX)) { - t = new UnixTerminal(); + t = new UnixTerminal(encoding); } else if (type.equals(TerminalType.WIN) || type.equals(TerminalType.WINDOWS)) { t = new WindowsTerminal(); } else if (type.equals(TerminalType.NONE) || type.equals(TerminalType.OFF) || type.equals(TerminalType.FALSE)) { - t = new UnsupportedTerminal(); - unsupported = true; + t = new UnsupportedTerminal(encoding); } else { if (isIDEAutoDetect()) { logger.debug("Terminal is in UNSUPPORTED (best guess). Unable to support single key input. Only line input available."); - t = new UnsupportedTerminal(); - unsupported = true; + t = new UnsupportedTerminal(encoding); } else { if (OS.isWindows()) { t = new WindowsTerminal(); } else { - t = new UnixTerminal(); + t = new UnixTerminal(encoding); } } } } catch (Exception e) { logger.error("Failed to construct terminal, falling back to unsupported"); - t = new UnsupportedTerminal(); - unsupported = true; + t = new UnsupportedTerminal(encoding); } - InputStream in; - try { t.init(); - in = t.wrapInIfNeeded(System.in); } catch (Throwable e) { logger.error("Terminal initialization failed, falling back to unsupported"); - t = new UnsupportedTerminal(); - unsupported = true; - in = System.in; + t = new UnsupportedTerminal(encoding); try { t.init(); @@ -191,17 +170,10 @@ public class InputConsole { } } - this.encoding = this.encoding != null ? this.encoding : getEncoding(); - this.reader = new InputStreamReader(in, this.encoding); + t.setEchoEnabled(true); - if (unsupported) { - this.reader = new BufferedReader(this.reader); - } - - this.unsupported = unsupported; this.terminal = t; - - logger.debug("Created Terminal: {}", this.terminal); + logger.debug("Created Terminal: {} ({}x{})", this.terminal.getClass().getSimpleName(), t.getWidth(), t.getHeight()); } // called when the JVM is shutting down. @@ -237,9 +209,9 @@ public class InputConsole { /** return null if no data */ private final char[] readLine0() { if (this.threadBufferForRead.get() == null) { - ByteBuffer2Fast buffer = ByteBuffer2Fast.allocate(0); - this.threadBufferForRead.set(buffer); - this.threadBuffersForRead.add(buffer); + ObjectPoolHolder holder = this.pool.take(); + this.threadBufferForRead.set(holder); + this.threadBuffersForRead.add(holder); } synchronized (this.inputLockLine) { @@ -250,33 +222,28 @@ public class InputConsole { } } - ByteBuffer2Fast stringBuffer = this.threadBufferForRead.get(); - int len = stringBuffer.position(); + ObjectPoolHolder objectPoolHolder = this.threadBufferForRead.get(); + ByteBuffer2 buffer = objectPoolHolder.getValue(); + int len = buffer.position(); if (len == 0) { return emptyLine; } - char[] chars = new char[len/2]; // because 2 chars is 1 bytes - stringBuffer.getChars(0, len, chars, 0); + buffer.rewind(); + char[] readChars = buffer.readChars(len/2); // java always stores chars in 2 bytes // dump the chars in the buffer (safer for passwords, etc) - stringBuffer.clear(); - stringBuffer.putBytes(new byte[0]); + buffer.clearSecure(); + this.threadBuffersForRead.remove(objectPoolHolder); + this.pool.release(objectPoolHolder); this.threadBufferForRead.set(null); - this.threadBuffersForRead.remove(stringBuffer); // TODO: use object pool! - return chars; + return readChars; } /** return null if no data */ private final char[] readLinePassword0() { - if (this.threadBufferForRead.get() == null) { - ByteBuffer2Fast buffer = ByteBuffer2Fast.allocate(0); - this.threadBufferForRead.set(buffer); - this.threadBuffersForRead.add(buffer); - } - // don't bother in an IDE. it won't work. boolean echoEnabled = this.terminal.isEchoEnabled(); this.terminal.setEchoEnabled(false); @@ -288,51 +255,15 @@ public class InputConsole { /** return -1 if no data */ private final int read0() { - // if we are reading data (because we are in IDE mode), we want to return ALL - // the chars of the line! - - // so, readChar is REALLY the index at which we return letters (until the whole string is returned - if (this.unsupported) { - int readerCount = this.indexOfStringForReadChar.get(); - - if (readerCount == -1) { - // we have to wait for more data. - synchronized (this.inputLockLine) { - try { - this.inputLockLine.wait(); - } catch (InterruptedException e) { - return -1; - } - readerCount = 0; - this.indexOfStringForReadChar.set(0); - } + synchronized (this.inputLockSingle) { + try { + this.inputLockSingle.wait(); + } catch (InterruptedException e) { + return -1; } - - - // EACH thread will have it's own count! - ByteBuffer2Fast stringBuffer = this.threadBufferForRead.get(); - - if (readerCount == stringBuffer.position()) { - this.indexOfStringForReadChar.set(-1); - return '\n'; - } else { - this.indexOfStringForReadChar.set(readerCount+1); - } - - char c = stringBuffer.getChar(readerCount); - return c; - } - else { - // we can read like normal. - synchronized (this.inputLockSingle) { - try { - this.inputLockSingle.wait(); - } catch (InterruptedException e) { - return -1; - } - } - return this.readChar; } + + return this.readChar; } /** @@ -351,163 +282,98 @@ public class InputConsole { private final void run() { Logger logger2 = logger; - // if we are eclipse/etc, we MUST do this per line! (per character DOESN'T work.) - // char readers will get looped for the WHOLE string, so reading by char will work, - // it just waits until \n until it triggers - if (this.unsupported) { - BufferedReader reader = (BufferedReader) this.reader; - String line = null; - char[] readLine = null; + final boolean ansiEnabled = Ansi.isEnabled(); + Ansi ansi = Ansi.ansi(); + PrintStream out = AnsiConsole.out; - try { - while ((line = reader.readLine()) != null) { - readLine = line.toCharArray(); + int typedChar; + char asChar; - // notify everyone waiting for a line of text. - synchronized (this.inputLockSingle) { - if (readLine.length > 0) { - this.readChar = readLine[0]; - } else { - this.readChar = -1; - } - this.inputLockSingle.notifyAll(); - } - synchronized (this.inputLockLine) { - byte[] charToBytes = Sys.charToBytes(readLine); + // don't type ; in a bash shell, it quits everything + // \n is replaced by \r in unix terminal? + while ((typedChar = this.terminal.read()) != -1) { + asChar = (char) typedChar; - for (ByteBuffer2Fast buffer : this.threadBuffersForRead) { - buffer.clear(); - buffer.putBytes(charToBytes); - } - - this.inputLockLine.notifyAll(); - } - } - } catch (Exception ignored) { - ignored.printStackTrace(); + if (logger2.isTraceEnabled()) { + logger2.trace("READ: {} ({})", asChar, typedChar); } - } - else { - // from a 'regular' console - try { - final boolean ansiEnabled = Ansi.isEnabled(); - Ansi ansi = Ansi.ansi(); - PrintStream out = AnsiConsole.out; - int typedChar; + // notify everyone waiting for a character. + synchronized (this.inputLockSingle) { + if (this.terminal.wasSequence() && typedChar == '\n') { + // don't want to forward \n if it was a part of a sequence in the unsupported terminal + // the JIT will short-cut this out if we are not the unsupported terminal + } else { + this.readChar = typedChar; + this.inputLockSingle.notifyAll(); + } + } - // don't type ; in a bash shell, it quits everything - // \n is replaced by \r in unix terminal? - while ((typedChar = this.reader.read()) != -1) { - char asChar = (char) typedChar; + // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed. + if (typedChar == '\b') { + int position = 0; - if (logger2.isTraceEnabled()) { - logger2.trace("READ: {} ({})", asChar, typedChar); - } + // clear ourself + one extra. + if (ansiEnabled) { + for (ObjectPoolHolder objectPoolHolder : this.threadBuffersForRead) { + ByteBuffer2 buffer = objectPoolHolder.getValue(); + // size of the buffer BEFORE our backspace was typed + int length = buffer.position(); + int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes - // notify everyone waiting for a character. - synchronized (this.inputLockSingle) { - this.readChar = typedChar; - this.inputLockSingle.notifyAll(); - } + if (length > 1) { + char charAt = buffer.readChar(length-2); + amtToOverwrite += getPrintableCharacters(charAt); - // if we type a backspace key, swallow it + previous in READLINE. READCHAR will have it passed. - if (typedChar == 127) { - int position = 0; + // delete last item in our buffer + length -= 2; + buffer.setPosition(length); - // clear ourself + one extra. - if (ansiEnabled) { - for (ByteBuffer2Fast buffer : this.threadBuffersForRead) { - // size of the buffer BEFORE our backspace was typed - int length = buffer.position(); - int amtToOverwrite = 2 * 2; // backspace is always 2 chars (^?) * 2 because it's bytes - - if (length > 1) { - char charAt = buffer.getChar(length-2); - amtToOverwrite += getPrintableCharacters(charAt); - - // delete last item in our buffer - length -= 2; - buffer.position(length); - - // now figure out where the cursor is really at. - // this is more memory friendly than buf.toString.length - for (int i=0;i objectPoolHolder : this.threadBuffersForRead) { + ByteBuffer2 buffer = objectPoolHolder.getValue(); + buffer.writeChar(asChar); + } } } } - /** - * Get the default encoding. Will first look at the LC_CTYPE environment variable, then the input.encoding - * system property, then the default charset according to the JVM. - * - * @return The default encoding to use when none is specified. - */ - public static String getEncoding() { - // LC_CTYPE is usually in the form en_US.UTF-8 - String envEncoding = extractEncodingFromCtype(System.getenv("LC_CTYPE")); - if (envEncoding != null) { - return envEncoding; - } - return System.getProperty("input.encoding", Charset.defaultCharset().name()); - } - - /** - * Parses the LC_CTYPE value to extract the encoding according to the POSIX standard, which says that the LC_CTYPE - * environment variable may be of the format [language[_territory][.codeset][@modifier]] - * - * @param ctype The ctype to parse, may be null - * @return The encoding, if one was present, otherwise null - */ - static String extractEncodingFromCtype(String ctype) { - if (ctype != null && ctype.indexOf('.') > 0) { - String encodingAndModifier = ctype.substring(ctype.indexOf('.') + 1); - if (encodingAndModifier.indexOf('@') > 0) { - return encodingAndModifier.substring(0, encodingAndModifier.indexOf('@')); - } - return encodingAndModifier; - } - return null; - } - /** * try to guess if we are running inside an IDE */ diff --git a/Dorkbox-Util/src/dorkbox/util/input/Terminal.java b/Dorkbox-Util/src/dorkbox/util/input/Terminal.java index 3d01685..7c73d97 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/Terminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/Terminal.java @@ -1,7 +1,6 @@ package dorkbox.util.input; import java.io.IOException; -import java.io.InputStream; public abstract class Terminal { protected final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); @@ -60,7 +59,15 @@ public abstract class Terminal { public abstract int getWidth(); public abstract int getHeight(); - public InputStream wrapInIfNeeded(InputStream in) throws IOException { - return in; + /** + * @return a character from whatever underlying input method the terminal has available. + */ + public abstract int read(); + + /** + * Only needed for unsupported character input. + */ + public boolean wasSequence() { + return false; } } diff --git a/Dorkbox-Util/src/dorkbox/util/input/InputStreamReader.java b/Dorkbox-Util/src/dorkbox/util/input/posix/InputStreamReader.java similarity index 99% rename from Dorkbox-Util/src/dorkbox/util/input/InputStreamReader.java rename to Dorkbox-Util/src/dorkbox/util/input/posix/InputStreamReader.java index 09c35be..28a414f 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/InputStreamReader.java +++ b/Dorkbox-Util/src/dorkbox/util/input/posix/InputStreamReader.java @@ -6,7 +6,7 @@ * * http://www.opensource.org/licenses/bsd-license.php */ -package dorkbox.util.input; +package dorkbox.util.input.posix; import java.io.IOException; import java.io.InputStream; diff --git a/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java b/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java index 6d738b5..8ab0706 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/posix/UnixTerminal.java @@ -1,6 +1,7 @@ package dorkbox.util.input.posix; import java.io.IOException; +import java.io.Reader; import java.nio.ByteBuffer; import com.sun.jna.Native; @@ -18,10 +19,15 @@ public class UnixTerminal extends Terminal { private volatile TermiosStruct termInfoDefault = new TermiosStruct(); private volatile TermiosStruct termInfo = new TermiosStruct(); + private Reader reader; + private PosixTerminalControl term; private ByteBuffer windowSizeBuffer = ByteBuffer.allocate(8); - public UnixTerminal() throws Exception { + + public UnixTerminal(String encoding) throws Exception { + this.reader = new InputStreamReader(System.in, encoding); + this.term = (PosixTerminalControl) Native.loadLibrary("c", PosixTerminalControl.class); // save off the defaults @@ -54,7 +60,6 @@ public class UnixTerminal extends Terminal { // t->c_cc[VMIN] = 1; // t->c_cc[VTIME] = 0; - if (this.term.tcgetattr(0, this.termInfo) !=0) { throw new IOException("Failed to get terminal info"); } @@ -86,7 +91,7 @@ public class UnixTerminal extends Terminal { * used after calling this method. */ @Override - public void restore() throws IOException { + public final void restore() throws IOException { if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfoDefault) != 0) { throw new IOException("Can not reset terminal to defaults"); } @@ -96,7 +101,7 @@ public class UnixTerminal extends Terminal { * Returns number of columns in the terminal. */ @Override - public int getWidth() { + public final int getWidth() { if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { return DEFAULT_WIDTH; } @@ -109,7 +114,7 @@ public class UnixTerminal extends Terminal { * Returns number of rows in the terminal. */ @Override - public int getHeight() { + public final int getHeight() { if (this.term.ioctl(0, PosixTerminalControl.TIOCGWINSZ, this.windowSizeBuffer) != 0) { return DEFAULT_HEIGHT; } @@ -119,7 +124,7 @@ public class UnixTerminal extends Terminal { } @Override - public synchronized void setEchoEnabled(final boolean enabled) { + public final synchronized void setEchoEnabled(final boolean enabled) { // have to reget them, since flags change everything if (this.term.tcgetattr(0, this.termInfo) !=0) { this.logger.error("Failed to get terminal info"); @@ -139,29 +144,38 @@ public class UnixTerminal extends Terminal { super.setEchoEnabled(enabled); } - public void disableInterruptCharacter() { - // have to re-get them, since flags change everything - if (this.term.tcgetattr(0, this.termInfo) !=0) { - this.logger.error("Failed to get terminal info"); - } +// public final void disableInterruptCharacter() { +// // have to re-get them, since flags change everything +// if (this.term.tcgetattr(0, this.termInfo) !=0) { +// this.logger.error("Failed to get terminal info"); +// } +// +// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled +// +// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { +// this.logger.error("Can not set terminal flags"); +// } +// } +// +// public final void enableInterruptCharacter() { +// // have to re-get them, since flags change everything +// if (this.term.tcgetattr(0, this.termInfo) !=0) { +// this.logger.error("Failed to get terminal info"); +// } +// +// this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c +// +// if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { +// this.logger.error("Can not set terminal flags"); +// } +// } - this.termInfo.c_cc[PosixTerminalControl.VINTR] = 0; // interrupt disabled - - if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { - this.logger.error("Can not set terminal flags"); - } - } - - public void enableInterruptCharacter() { - // have to re-get them, since flags change everything - if (this.term.tcgetattr(0, this.termInfo) !=0) { - this.logger.error("Failed to get terminal info"); - } - - this.termInfo.c_cc[PosixTerminalControl.VINTR] = 3; // interrupt is ctrl-c - - if (this.term.tcsetattr(0, PosixTerminalControl.TCSANOW, this.termInfo) != 0) { - this.logger.error("Can not set terminal flags"); + @Override + public final int read() { + try { + return this.reader.read(); + } catch (IOException ignored) { + return -1; } } } diff --git a/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java b/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java index 54affae..41adfc3 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/unsupported/UnsupportedTerminal.java @@ -1,30 +1,91 @@ package dorkbox.util.input.unsupported; +import java.io.BufferedReader; import java.io.IOException; +import dorkbox.util.bytes.ByteBuffer2; import dorkbox.util.input.Terminal; +import dorkbox.util.input.posix.InputStreamReader; public class UnsupportedTerminal extends Terminal { - public UnsupportedTerminal() { -// setAnsiSupported(false); - setEchoEnabled(true); + + private final ByteBuffer2 buffer = new ByteBuffer2(8, -1); + + private BufferedReader reader; + private String readLine = null; + private char[] line; + + private ThreadLocal indexOfStringForReadChar = new ThreadLocal() { + @Override + protected Integer initialValue() { + return -1; + } + }; + + public UnsupportedTerminal(String encoding) { + this.reader = new BufferedReader(new InputStreamReader(System.in, encoding)); } @Override - public void init() throws IOException { + public final void init() throws IOException { } @Override - public void restore() { + public final void restore() { } @Override - public int getWidth() { + public final int getWidth() { return 0; } @Override - public int getHeight() { + public final int getHeight() { return 0; } + + @Override + public final int read() { + // if we are reading data (because we are in IDE mode), we want to return ALL + // the chars of the line! + + // so, readChar is REALLY the index at which we return letters (until the whole string is returned) + int readerCount = this.indexOfStringForReadChar.get(); + + if (readerCount == -1) { + + // we have to wait for more data. + try { + this.readLine = this.reader.readLine(); + } catch (IOException e) { + return -1; + } + + this.line = this.readLine.toCharArray(); + this.buffer.clear(); + for (char c : this.line) { + this.buffer.writeChar(c); + } + + readerCount = 0; + this.indexOfStringForReadChar.set(0); + } + + + // EACH thread will have it's own count! + if (readerCount == this.buffer.position()) { + this.indexOfStringForReadChar.set(-1); + return '\n'; + } else { + this.indexOfStringForReadChar.set(readerCount+2); // because 2 bytes per char in java + } + + char c = this.buffer.readChar(readerCount); + return c; + } + + @Override + public final boolean wasSequence() { + return this.line.length > 0; + } } \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/input/windows/ConsoleMode.java b/Dorkbox-Util/src/dorkbox/util/input/windows/ConsoleMode.java index 428ef17..bf47a53 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/windows/ConsoleMode.java +++ b/Dorkbox-Util/src/dorkbox/util/input/windows/ConsoleMode.java @@ -46,22 +46,8 @@ public enum ConsoleMode { * discarded by ReadFile or ReadConsole, even when this mode is enabled. */ ENABLE_MOUSE_INPUT(16), + ; - /** - * When enabled, text entered in a console window will be inserted at the - * current cursor location and all text following that location will not be - * overwritten. When disabled, all following text will be overwritten. An OR - * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS - * flag to enable this functionality. - */ - ENABLE_PROCESSED_OUTPUT(1), - - /** - * This flag enables the user to use the mouse to select and edit text. To - * enable this option, use the OR to combine this flag with - * ENABLE_EXTENDED_FLAGS. - */ - ENABLE_WRAP_AT_EOL_OUTPUT(2),; public final int code; diff --git a/Dorkbox-Util/src/dorkbox/util/input/windows/WindowsTerminal.java b/Dorkbox-Util/src/dorkbox/util/input/windows/WindowsTerminal.java index 4fece85..7b6f652 100644 --- a/Dorkbox-Util/src/dorkbox/util/input/windows/WindowsTerminal.java +++ b/Dorkbox-Util/src/dorkbox/util/input/windows/WindowsTerminal.java @@ -8,11 +8,11 @@ */ package dorkbox.util.input.windows; -import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.PrintStream; +import org.fusesource.jansi.internal.Kernel32.INPUT_RECORD; +import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD; import org.fusesource.jansi.internal.WindowsSupport; import dorkbox.util.input.Terminal; @@ -26,34 +26,26 @@ import dorkbox.util.input.Terminal; * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode to * disable character echoing. *

- *

- * By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt - * to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper - * around {@link FileDescriptor#in}, and if so, will bypass the character reading to - * directly invoke the readc() method in the JNI library. This is so the class - * can read special keys (like arrow keys) which are otherwise inaccessible via - * the {@link System#in} stream. Using JNI reading can be bypassed by setting - * the jline.WindowsTerminal.directConsole system property - * to false. - *

- * - * @author Marc Prud'hommeaux - * @author Jason Dillon - * @since 2.0 + * @since 2.0 (customized) */ -public class WindowsTerminal extends Terminal -{ - public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole"; - - private int originalMode; +public class WindowsTerminal extends Terminal { + private volatile int originalMode; + private final PrintStream out; public WindowsTerminal() { + this.out = System.out; } @Override - public void init() throws IOException { + public final void init() throws IOException { this.originalMode = WindowsSupport.getConsoleMode(); - WindowsSupport.setConsoleMode(this.originalMode & ~ConsoleMode.ENABLE_ECHO_INPUT.code); + + // Must set these four modes at the same time to make it work fine. + WindowsSupport.setConsoleMode(this.originalMode | + ConsoleMode.ENABLE_LINE_INPUT.code | + ConsoleMode.ENABLE_ECHO_INPUT.code | + ConsoleMode.ENABLE_PROCESSED_INPUT.code | + ConsoleMode.ENABLE_WINDOW_INPUT.code); } /** @@ -62,69 +54,71 @@ public class WindowsTerminal extends Terminal * used after calling this method. */ @Override - public void restore() throws IOException { + public final void restore() throws IOException { // restore the old console mode WindowsSupport.setConsoleMode(this.originalMode); } @Override - public int getWidth() { + public final int getWidth() { int w = WindowsSupport.getWindowsTerminalWidth(); return w < 1 ? DEFAULT_WIDTH : w; } @Override - public int getHeight() { + public final int getHeight() { int h = WindowsSupport.getWindowsTerminalHeight(); return h < 1 ? DEFAULT_HEIGHT : h; } @Override - public void setEchoEnabled(final boolean enabled) { - // Must set these four modes at the same time to make it work fine. - if (enabled) { - WindowsSupport.setConsoleMode(WindowsSupport.getConsoleMode() | - ConsoleMode.ENABLE_ECHO_INPUT.code | - ConsoleMode.ENABLE_LINE_INPUT.code | - ConsoleMode.ENABLE_PROCESSED_INPUT.code | - ConsoleMode.ENABLE_WINDOW_INPUT.code); + public final int read() { + int input = readInput(); + + if (isEchoEnabled()) { + char asChar = (char) input; + if (asChar == '\n') { + this.out.println(); + } else { + this.out.print(asChar); + } + // have to flush, otherwise we'll never see the chars on screen + this.out.flush(); } - else { - WindowsSupport.setConsoleMode(WindowsSupport.getConsoleMode() & - ~(ConsoleMode.ENABLE_LINE_INPUT.code | - ConsoleMode.ENABLE_ECHO_INPUT.code | - ConsoleMode.ENABLE_PROCESSED_INPUT.code | - ConsoleMode.ENABLE_WINDOW_INPUT.code)); - } - super.setEchoEnabled(enabled); + + return input; } + private final int readInput() { + // this HOOKS the input event, and prevents it from going to the console "proper" + try { + INPUT_RECORD[] events = null; + while (true) { + // we ALWAYS read until we have an event we care about! + events = WindowsSupport.readConsoleInput(1); - @Override - public InputStream wrapInIfNeeded(InputStream in) throws IOException { - if (isSystemIn(in)) { - return new InputStream() { - @Override - public int read() throws IOException { - return WindowsSupport.readByte(); + if (events != null) { + for (int i = 0; i < events.length; i++ ) { + KEY_EVENT_RECORD keyEvent = events[i].keyEvent; + //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); + if (keyEvent.keyDown) { + if (keyEvent.uchar > 0) { + char uchar = keyEvent.uchar; + if (uchar == '\r') { + // we purposefully swallow input after \r, and substitute it with \n + return '\n'; + } + + return uchar; + } + } + } } - }; - } else { - return in; - } - } - - private boolean isSystemIn(final InputStream in) throws IOException { - if (in == null) { - return false; - } - else if (in == System.in) { - return true; - } - else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) { - return true; + } + } catch (IOException e) { + this.logger.error("Windows console input error: ", e); } - return false; + return -1; } } diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/FastObjectPool.java b/Dorkbox-Util/src/dorkbox/util/objectPool/FastObjectPool.java index 0321c45..6dd1e6d 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/FastObjectPool.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/FastObjectPool.java @@ -104,10 +104,10 @@ class FastObjectPool implements ObjectPool { } @Override - public void release(ObjectPoolHolder object) throws InterruptedException { - this.lock.lockInterruptibly(); - + public void release(ObjectPoolHolder object) { try { + this.lock.lockInterruptibly(); + int localValue = this.releasePointer; //long index = ((localValue & mask) * INDEXSCALE ) + BASE; long index = ((localValue & this.mask)< implements ObjectPool { else { throw new IllegalArgumentException("Invalid reference passed"); } + } catch (InterruptedException e) { } finally { this.lock.unlock(); diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPool.java b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPool.java index 23a09d8..f7702b2 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPool.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPool.java @@ -9,5 +9,5 @@ public interface ObjectPool { /** * Return object to the pool */ - public void release(ObjectPoolHolder object) throws InterruptedException; + public void release(ObjectPoolHolder object); } diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java index b5b77f3..47a598a 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/ObjectPoolFactory.java @@ -2,11 +2,22 @@ package dorkbox.util.objectPool; import dorkbox.util.Sys; -public class ObjectPoolFactory { +public class ObjectPoolFactory { private ObjectPoolFactory() { } + /** + * Creates a pool with the max number of available processors as the pool size (padded by 2x as many). + */ + public static ObjectPool create(PoolableObject poolableObject) { + return create(poolableObject, Runtime.getRuntime().availableProcessors() * 2); + } + + + /** + * Creates a pool of the specified size + */ public static ObjectPool create(PoolableObject poolableObject, int size) { if (Sys.isAndroid) { // unfortunately, unsafe is not available in android diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/PoolableObject.java b/Dorkbox-Util/src/dorkbox/util/objectPool/PoolableObject.java index 6b0dfb4..13002c0 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/PoolableObject.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/PoolableObject.java @@ -10,10 +10,10 @@ public interface PoolableObject { /** * invoked on every instance that is borrowed from the pool */ - public void activate(T t); + public void activate(T object); /** * invoked on every instance that is returned to the pool */ - public void passivate(T t); + public void passivate(T object); } diff --git a/Dorkbox-Util/src/dorkbox/util/objectPool/SlowObjectPool.java b/Dorkbox-Util/src/dorkbox/util/objectPool/SlowObjectPool.java index 390cc96..0cc15d7 100644 --- a/Dorkbox-Util/src/dorkbox/util/objectPool/SlowObjectPool.java +++ b/Dorkbox-Util/src/dorkbox/util/objectPool/SlowObjectPool.java @@ -76,7 +76,7 @@ class SlowObjectPool implements ObjectPool { } @Override - public void release(ObjectPoolHolder object) throws InterruptedException { + public void release(ObjectPoolHolder object) { if (object.state.compareAndSet(USED, FREE)) { this.queue.offer(object); this.poolableObject.passivate(object.getValue()); diff --git a/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java index 5ac78c2..9b3d278 100644 --- a/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java +++ b/Dorkbox-Util/src/dorkbox/util/process/ShellProcessBuilder.java @@ -21,10 +21,6 @@ import dorkbox.util.OS; */ public class ShellProcessBuilder { - // TODO: see http://mark.koli.ch/2009/12/uac-prompt-from-java-createprocess-error740-the-requested-operation-requires-elevation.html - // for more information on copying files in windows with UAC protections. - // maybe we want to hook into our launcher executor so that we can "auto elevate" commands? - private String workingDirectory = null; private String executableName = null; private String executableDirectory = null; @@ -56,9 +52,9 @@ public class ShellProcessBuilder { } public ShellProcessBuilder(InputStream in, PrintStream out, PrintStream err) { - outputStream = out; - errorStream = err; - inputStream = in; + this.outputStream = out; + this.errorStream = err; + this.inputStream = in; } /** @@ -71,19 +67,19 @@ public class ShellProcessBuilder { } public final ShellProcessBuilder addArgument(String argument) { - arguments.add(argument); + this.arguments.add(argument); return this; } public final ShellProcessBuilder addArguments(String... paths) { for (String path : paths) { - arguments.add(path); + this.arguments.add(path); } return this; } public final ShellProcessBuilder addArguments(List paths) { - arguments.addAll(paths); + this.arguments.addAll(paths); return this; } @@ -106,39 +102,39 @@ public class ShellProcessBuilder { public void start() { // if no executable, then use the command shell - if (executableName == null) { + if (this.executableName == null) { if (OS.isWindows()) { // windows - executableName = "cmd"; - arguments.add(0, "/c"); + this.executableName = "cmd"; + this.arguments.add(0, "/c"); } else { // *nix - executableName = "/bin/bash"; - File file = new File(executableName); + this.executableName = "/bin/bash"; + File file = new File(this.executableName); if (!file.canExecute()) { - executableName = "/bin/sh"; + this.executableName = "/bin/sh"; } - arguments.add(0, "-c"); + this.arguments.add(0, "-c"); } - } else if (workingDirectory != null) { - if (!workingDirectory.endsWith("/") && !workingDirectory.endsWith("\\")) { - workingDirectory += File.separator; + } else if (this.workingDirectory != null) { + if (!this.workingDirectory.endsWith("/") && !this.workingDirectory.endsWith("\\")) { + this.workingDirectory += File.separator; } } - if (executableDirectory != null) { - if (!executableDirectory.endsWith("/") && !executableDirectory.endsWith("\\")) { - executableDirectory += File.separator; + if (this.executableDirectory != null) { + if (!this.executableDirectory.endsWith("/") && !this.executableDirectory.endsWith("\\")) { + this.executableDirectory += File.separator; } - executableName = executableDirectory + executableName; + this.executableName = this.executableDirectory + this.executableName; } List argumentsList = new ArrayList(); - argumentsList.add(executableName); + argumentsList.add(this.executableName); - for (String arg : arguments) { + for (String arg : this.arguments) { if (arg.contains(" ")) { // individual arguments MUST be in their own element in order to // be processed properly (this is how it works on the command line!) @@ -154,7 +150,7 @@ public class ShellProcessBuilder { // if we don't want output... TODO: i think we want to "exec" (this calls exec -c, which calls our program) // this code as well, since calling it directly won't work - boolean pipeToNull = errorStream == null || outputStream == null; + boolean pipeToNull = this.errorStream == null || this.outputStream == null; if (pipeToNull) { if (OS.isWindows()) { // >NUL on windows @@ -165,22 +161,22 @@ public class ShellProcessBuilder { } } - if (debugInfo) { - errorStream.print("Executing: "); + if (this.debugInfo) { + this.errorStream.print("Executing: "); Iterator iterator = argumentsList.iterator(); while (iterator.hasNext()) { String s = iterator.next(); - errorStream.print(s); + this.errorStream.print(s); if (iterator.hasNext()) { - errorStream.print(" "); + this.errorStream.print(" "); } } - errorStream.print(OS.LINE_SEPARATOR); + this.errorStream.print(OS.LINE_SEPARATOR); } ProcessBuilder processBuilder = new ProcessBuilder(argumentsList); - if (workingDirectory != null) { - processBuilder.directory(new File(workingDirectory)); + if (this.workingDirectory != null) { + processBuilder.directory(new File(this.workingDirectory)); } // combine these so output is properly piped to null. @@ -189,23 +185,23 @@ public class ShellProcessBuilder { } try { - process = processBuilder.start(); + this.process = processBuilder.start(); } catch (Exception ex) { - errorStream.println("There was a problem executing the program. Details:\n"); - ex.printStackTrace(errorStream); + this.errorStream.println("There was a problem executing the program. Details:\n"); + ex.printStackTrace(this.errorStream); - if (process != null) { + if (this.process != null) { try { - process.destroy(); - process = null; + this.process.destroy(); + this.process = null; } catch (Exception e) { - errorStream.println("Error destroying process: \n"); - e.printStackTrace(errorStream); + this.errorStream.println("Error destroying process: \n"); + e.printStackTrace(this.errorStream); } } } - if (process != null) { + if (this.process != null) { ProcessProxy writeToProcess_input; ProcessProxy readFromProcess_output; ProcessProxy readFromProcess_error; @@ -218,7 +214,7 @@ public class ShellProcessBuilder { // readers (read process -> write console) // have to keep the output buffers from filling in the target process. - readFromProcess_output = new ProcessProxy("Process Reader: " + executableName, process.getInputStream(), nullOutputStream); + readFromProcess_output = new ProcessProxy("Process Reader: " + this.executableName, this.process.getInputStream(), nullOutputStream); readFromProcess_error = null; } // we want to pipe our input/output from process to ourselves @@ -228,21 +224,21 @@ public class ShellProcessBuilder { * to the user's window. This is important or the spawned process could block. */ // readers (read process -> write console) - readFromProcess_output = new ProcessProxy("Process Reader: " + executableName, process.getInputStream(), outputStream); - if (errorStream != outputStream) { - readFromProcess_error = new ProcessProxy("Process Reader: " + executableName, process.getErrorStream(), errorStream); + readFromProcess_output = new ProcessProxy("Process Reader: " + this.executableName, this.process.getInputStream(), this.outputStream); + if (this.errorStream != this.outputStream) { + readFromProcess_error = new ProcessProxy("Process Reader: " + this.executableName, this.process.getErrorStream(), this.errorStream); } else { processBuilder.redirectErrorStream(true); readFromProcess_error = null; } } - if (inputStream != null) { + if (this.inputStream != null) { /** * Proxy System.in from the user's window to the spawned process */ // writer (read console -> write process) - writeToProcess_input = new ProcessProxy("Process Writer: " + executableName, inputStream, process.getOutputStream()); + writeToProcess_input = new ProcessProxy("Process Writer: " + this.executableName, this.inputStream, this.process.getOutputStream()); } else { writeToProcess_input = null; } @@ -254,10 +250,10 @@ public class ShellProcessBuilder { Thread hook = new Thread(new Runnable() { @Override public void run() { - if (debugInfo) { - errorStream.println("Terminating process: " + executableName); + if (ShellProcessBuilder.this.debugInfo) { + ShellProcessBuilder.this.errorStream.println("Terminating process: " + ShellProcessBuilder.this.executableName); } - process.destroy(); + ShellProcessBuilder.this.process.destroy(); } } ); @@ -273,10 +269,10 @@ public class ShellProcessBuilder { } try { - process.waitFor(); + this.process.waitFor(); @SuppressWarnings("unused") - int exitValue = process.exitValue(); + int exitValue = this.process.exitValue(); // wait for the READER threads to die (meaning their streams have closed/EOF'd) if (writeToProcess_input != null) { @@ -294,7 +290,7 @@ public class ShellProcessBuilder { // forcibly terminate the process when it's streams have closed. // this is for cleanup ONLY, not to actually do anything. - process.destroy(); + this.process.destroy(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/Dorkbox-Util/test/dorkbox/util/ByteBuffer2Test.java b/Dorkbox-Util/test/dorkbox/util/ByteBuffer2Test.java new file mode 100644 index 0000000..f4a5d91 --- /dev/null +++ b/Dorkbox-Util/test/dorkbox/util/ByteBuffer2Test.java @@ -0,0 +1,766 @@ +package dorkbox.util; + +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Random; + +import org.junit.Assert; +import org.junit.Test; + +import dorkbox.util.bytes.ByteBuffer2; + +public class ByteBuffer2Test { + + + static public void assertArrayEquals(Object object1, Object object2) { + Assert.assertEquals(arrayToList(object1), arrayToList(object2)); + } + + static public Object arrayToList(Object array) { + if (array == null || !array.getClass().isArray()) { + return array; + } + ArrayList list = new ArrayList(Array.getLength(array)); + for (int i = 0, n = Array.getLength(array); i < n; i++) { + list.add(arrayToList(Array.get(array, i))); + } + return list; + } + + @Test + public void testWriteBytes() { + ByteBuffer2 buffer = new ByteBuffer2(512); + buffer.writeBytes(new byte[] {11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26}); + buffer.writeBytes(new byte[] {31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46}); + buffer.writeByte(51); + buffer.writeBytes(new byte[] {52,53,54,55,56,57,58}); + buffer.writeByte(61); + buffer.writeByte(62); + buffer.writeByte(63); + buffer.writeByte(64); + buffer.writeByte(65); + + assertArrayEquals(new byte[] {11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,31,32,33,34,35,36,37,38,39,40,41,42, + 43,44,45,46,51,52,53,54,55,56,57,58,61,62,63,64,65}, buffer.toBytes()); + } + + @Test + public void testStrings() { + runStringTest(new ByteBuffer2(4096)); + runStringTest(new ByteBuffer2(897)); + + ByteBuffer2 write = new ByteBuffer2(21); + String value = "abcdef\u00E1\u00E9\u00ED\u00F3\u00FA\u1234"; + write.writeString(value); + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + assertArrayEquals(value, read.readString()); + + runStringTest(127); + runStringTest(256); + runStringTest(1024 * 1023); + runStringTest(1024 * 1024); + runStringTest(1024 * 1025); + runStringTest(1024 * 1026); + runStringTest(1024 * 1024 * 2); + } + + public void runStringTest(ByteBuffer2 write) { + String value1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\rabcdefghijklmnopqrstuvwxyz\n1234567890\t\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*"; + String value2 = "abcdef\u00E1\u00E9\u00ED\u00F3\u00FA\u1234"; + + write.writeString(""); + write.writeString("1"); + write.writeString("22"); + write.writeString("uno"); + write.writeString("dos"); + write.writeString("tres"); + write.writeString(null); + write.writeString(value1); + write.writeString(value2); + for (int i = 0; i < 127; i++) { + write.writeString(String.valueOf((char) i)); + } + for (int i = 0; i < 127; i++) { + write.writeString(String.valueOf((char) i) + "abc"); + } + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals("", read.readString()); + Assert.assertEquals("1", read.readString()); + Assert.assertEquals("22", read.readString()); + Assert.assertEquals("uno", read.readString()); + Assert.assertEquals("dos", read.readString()); + Assert.assertEquals("tres", read.readString()); + Assert.assertEquals(null, read.readString()); + Assert.assertEquals(value1, read.readString()); + Assert.assertEquals(value2, read.readString()); + for (int i = 0; i < 127; i++) { + Assert.assertEquals(String.valueOf((char) i), read.readString()); + } + for (int i = 0; i < 127; i++) { + Assert.assertEquals(String.valueOf((char) i) + "abc", read.readString()); + } + + read.rewind(); + + Assert.assertEquals("", read.readStringBuilder().toString()); + Assert.assertEquals("1", read.readStringBuilder().toString()); + Assert.assertEquals("22", read.readStringBuilder().toString()); + Assert.assertEquals("uno", read.readStringBuilder().toString()); + Assert.assertEquals("dos", read.readStringBuilder().toString()); + Assert.assertEquals("tres", read.readStringBuilder().toString()); + Assert.assertEquals(null, read.readStringBuilder()); + Assert.assertEquals(value1, read.readStringBuilder().toString()); + Assert.assertEquals(value2, read.readStringBuilder().toString()); + for (int i = 0; i < 127; i++) { + Assert.assertEquals(String.valueOf((char) i), read.readStringBuilder().toString()); + } + for (int i = 0; i < 127; i++) { + Assert.assertEquals(String.valueOf((char) i) + "abc", read.readStringBuilder().toString()); + } + } + + public void runStringTest(int length) { + ByteBuffer2 write = new ByteBuffer2(1024, -1); + + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < length; i++) { + buffer.append((char) i); + } + + String value = buffer.toString(); + write.writeString(value); + write.writeString(value); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(value, read.readString()); + Assert.assertEquals(value, read.readStringBuilder().toString()); + + write.clear(); + write.writeString(buffer); + write.writeString(buffer); + read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(value, read.readStringBuilder().toString()); + Assert.assertEquals(value, read.readString()); + + if (length <= 127) { + write.clear(); + write.writeAscii(value); + write.writeAscii(value); + read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(value, read.readStringBuilder().toString()); + Assert.assertEquals(value, read.readString()); + } + } + + @Test + public void testCanReadInt() { + ByteBuffer2 write = new ByteBuffer2(); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(false, read.canReadInt()); + + write = new ByteBuffer2(4); + write.writeInt(400, true); + + read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(true, read.canReadInt()); + read.setPosition(read.capacity()); + Assert.assertEquals(false, read.canReadInt()); + } + + @Test + public void testInts() { + runIntTest(new ByteBuffer2(4096)); + } + + private void runIntTest(ByteBuffer2 write) { + write.writeInt(0); + write.writeInt(63); + write.writeInt(64); + write.writeInt(127); + write.writeInt(128); + write.writeInt(8192); + write.writeInt(16384); + write.writeInt(2097151); + write.writeInt(1048575); + write.writeInt(134217727); + write.writeInt(268435455); + write.writeInt(134217728); + write.writeInt(268435456); + write.writeInt(-2097151); + write.writeInt(-1048575); + write.writeInt(-134217727); + write.writeInt(-268435455); + write.writeInt(-134217728); + write.writeInt(-268435456); + Assert.assertEquals(1, write.writeInt(0, true)); + Assert.assertEquals(1, write.writeInt(0, false)); + Assert.assertEquals(1, write.writeInt(63, true)); + Assert.assertEquals(1, write.writeInt(63, false)); + Assert.assertEquals(1, write.writeInt(64, true)); + Assert.assertEquals(2, write.writeInt(64, false)); + Assert.assertEquals(1, write.writeInt(127, true)); + Assert.assertEquals(2, write.writeInt(127, false)); + Assert.assertEquals(2, write.writeInt(128, true)); + Assert.assertEquals(2, write.writeInt(128, false)); + Assert.assertEquals(2, write.writeInt(8191, true)); + Assert.assertEquals(2, write.writeInt(8191, false)); + Assert.assertEquals(2, write.writeInt(8192, true)); + Assert.assertEquals(3, write.writeInt(8192, false)); + Assert.assertEquals(2, write.writeInt(16383, true)); + Assert.assertEquals(3, write.writeInt(16383, false)); + Assert.assertEquals(3, write.writeInt(16384, true)); + Assert.assertEquals(3, write.writeInt(16384, false)); + Assert.assertEquals(3, write.writeInt(2097151, true)); + Assert.assertEquals(4, write.writeInt(2097151, false)); + Assert.assertEquals(3, write.writeInt(1048575, true)); + Assert.assertEquals(3, write.writeInt(1048575, false)); + Assert.assertEquals(4, write.writeInt(134217727, true)); + Assert.assertEquals(4, write.writeInt(134217727, false)); + Assert.assertEquals(4, write.writeInt(268435455, true)); + Assert.assertEquals(5, write.writeInt(268435455, false)); + Assert.assertEquals(4, write.writeInt(134217728, true)); + Assert.assertEquals(5, write.writeInt(134217728, false)); + Assert.assertEquals(5, write.writeInt(268435456, true)); + Assert.assertEquals(5, write.writeInt(268435456, false)); + Assert.assertEquals(1, write.writeInt(-64, false)); + Assert.assertEquals(5, write.writeInt(-64, true)); + Assert.assertEquals(2, write.writeInt(-65, false)); + Assert.assertEquals(5, write.writeInt(-65, true)); + Assert.assertEquals(2, write.writeInt(-8192, false)); + Assert.assertEquals(5, write.writeInt(-8192, true)); + Assert.assertEquals(3, write.writeInt(-1048576, false)); + Assert.assertEquals(5, write.writeInt(-1048576, true)); + Assert.assertEquals(4, write.writeInt(-134217728, false)); + Assert.assertEquals(5, write.writeInt(-134217728, true)); + Assert.assertEquals(5, write.writeInt(-134217729, false)); + Assert.assertEquals(5, write.writeInt(-134217729, true)); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(0, read.readInt()); + Assert.assertEquals(63, read.readInt()); + Assert.assertEquals(64, read.readInt()); + Assert.assertEquals(127, read.readInt()); + Assert.assertEquals(128, read.readInt()); + Assert.assertEquals(8192, read.readInt()); + Assert.assertEquals(16384, read.readInt()); + Assert.assertEquals(2097151, read.readInt()); + Assert.assertEquals(1048575, read.readInt()); + Assert.assertEquals(134217727, read.readInt()); + Assert.assertEquals(268435455, read.readInt()); + Assert.assertEquals(134217728, read.readInt()); + Assert.assertEquals(268435456, read.readInt()); + Assert.assertEquals(-2097151, read.readInt()); + Assert.assertEquals(-1048575, read.readInt()); + Assert.assertEquals(-134217727, read.readInt()); + Assert.assertEquals(-268435455, read.readInt()); + Assert.assertEquals(-134217728, read.readInt()); + Assert.assertEquals(-268435456, read.readInt()); + Assert.assertEquals(true, read.canReadInt()); + Assert.assertEquals(true, read.canReadInt()); + Assert.assertEquals(true, read.canReadInt()); + Assert.assertEquals(0, read.readInt(true)); + Assert.assertEquals(0, read.readInt(false)); + Assert.assertEquals(63, read.readInt(true)); + Assert.assertEquals(63, read.readInt(false)); + Assert.assertEquals(64, read.readInt(true)); + Assert.assertEquals(64, read.readInt(false)); + Assert.assertEquals(127, read.readInt(true)); + Assert.assertEquals(127, read.readInt(false)); + Assert.assertEquals(128, read.readInt(true)); + Assert.assertEquals(128, read.readInt(false)); + Assert.assertEquals(8191, read.readInt(true)); + Assert.assertEquals(8191, read.readInt(false)); + Assert.assertEquals(8192, read.readInt(true)); + Assert.assertEquals(8192, read.readInt(false)); + Assert.assertEquals(16383, read.readInt(true)); + Assert.assertEquals(16383, read.readInt(false)); + Assert.assertEquals(16384, read.readInt(true)); + Assert.assertEquals(16384, read.readInt(false)); + Assert.assertEquals(2097151, read.readInt(true)); + Assert.assertEquals(2097151, read.readInt(false)); + Assert.assertEquals(1048575, read.readInt(true)); + Assert.assertEquals(1048575, read.readInt(false)); + Assert.assertEquals(134217727, read.readInt(true)); + Assert.assertEquals(134217727, read.readInt(false)); + Assert.assertEquals(268435455, read.readInt(true)); + Assert.assertEquals(268435455, read.readInt(false)); + Assert.assertEquals(134217728, read.readInt(true)); + Assert.assertEquals(134217728, read.readInt(false)); + Assert.assertEquals(268435456, read.readInt(true)); + Assert.assertEquals(268435456, read.readInt(false)); + Assert.assertEquals(-64, read.readInt(false)); + Assert.assertEquals(-64, read.readInt(true)); + Assert.assertEquals(-65, read.readInt(false)); + Assert.assertEquals(-65, read.readInt(true)); + Assert.assertEquals(-8192, read.readInt(false)); + Assert.assertEquals(-8192, read.readInt(true)); + Assert.assertEquals(-1048576, read.readInt(false)); + Assert.assertEquals(-1048576, read.readInt(true)); + Assert.assertEquals(-134217728, read.readInt(false)); + Assert.assertEquals(-134217728, read.readInt(true)); + Assert.assertEquals(-134217729, read.readInt(false)); + Assert.assertEquals(-134217729, read.readInt(true)); + Assert.assertEquals(false, read.canReadInt()); + + Random random = new Random(); + for (int i = 0; i < 10000; i++) { + int value = random.nextInt(); + write.clear(); + write.writeInt(value); + write.writeInt(value, true); + write.writeInt(value, false); + read.setBuffer(write.toBytes()); + Assert.assertEquals(value, read.readInt()); + Assert.assertEquals(value, read.readInt(true)); + Assert.assertEquals(value, read.readInt(false)); + } + } + + @Test + public void testLongs() { + runLongTest(new ByteBuffer2(4096)); + } + + private void runLongTest(ByteBuffer2 write) { + write.writeLong(0); + write.writeLong(63); + write.writeLong(64); + write.writeLong(127); + write.writeLong(128); + write.writeLong(8192); + write.writeLong(16384); + write.writeLong(2097151); + write.writeLong(1048575); + write.writeLong(134217727); + write.writeLong(268435455); + write.writeLong(134217728); + write.writeLong(268435456); + write.writeLong(-2097151); + write.writeLong(-1048575); + write.writeLong(-134217727); + write.writeLong(-268435455); + write.writeLong(-134217728); + write.writeLong(-268435456); + Assert.assertEquals(1, write.writeLong(0, true)); + Assert.assertEquals(1, write.writeLong(0, false)); + Assert.assertEquals(1, write.writeLong(63, true)); + Assert.assertEquals(1, write.writeLong(63, false)); + Assert.assertEquals(1, write.writeLong(64, true)); + Assert.assertEquals(2, write.writeLong(64, false)); + Assert.assertEquals(1, write.writeLong(127, true)); + Assert.assertEquals(2, write.writeLong(127, false)); + Assert.assertEquals(2, write.writeLong(128, true)); + Assert.assertEquals(2, write.writeLong(128, false)); + Assert.assertEquals(2, write.writeLong(8191, true)); + Assert.assertEquals(2, write.writeLong(8191, false)); + Assert.assertEquals(2, write.writeLong(8192, true)); + Assert.assertEquals(3, write.writeLong(8192, false)); + Assert.assertEquals(2, write.writeLong(16383, true)); + Assert.assertEquals(3, write.writeLong(16383, false)); + Assert.assertEquals(3, write.writeLong(16384, true)); + Assert.assertEquals(3, write.writeLong(16384, false)); + Assert.assertEquals(3, write.writeLong(2097151, true)); + Assert.assertEquals(4, write.writeLong(2097151, false)); + Assert.assertEquals(3, write.writeLong(1048575, true)); + Assert.assertEquals(3, write.writeLong(1048575, false)); + Assert.assertEquals(4, write.writeLong(134217727, true)); + Assert.assertEquals(4, write.writeLong(134217727, false)); + Assert.assertEquals(4, write.writeLong(268435455l, true)); + Assert.assertEquals(5, write.writeLong(268435455l, false)); + Assert.assertEquals(4, write.writeLong(134217728l, true)); + Assert.assertEquals(5, write.writeLong(134217728l, false)); + Assert.assertEquals(5, write.writeLong(268435456l, true)); + Assert.assertEquals(5, write.writeLong(268435456l, false)); + Assert.assertEquals(1, write.writeLong(-64, false)); + Assert.assertEquals(9, write.writeLong(-64, true)); + Assert.assertEquals(2, write.writeLong(-65, false)); + Assert.assertEquals(9, write.writeLong(-65, true)); + Assert.assertEquals(2, write.writeLong(-8192, false)); + Assert.assertEquals(9, write.writeLong(-8192, true)); + Assert.assertEquals(3, write.writeLong(-1048576, false)); + Assert.assertEquals(9, write.writeLong(-1048576, true)); + Assert.assertEquals(4, write.writeLong(-134217728, false)); + Assert.assertEquals(9, write.writeLong(-134217728, true)); + Assert.assertEquals(5, write.writeLong(-134217729, false)); + Assert.assertEquals(9, write.writeLong(-134217729, true)); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(0, read.readLong()); + Assert.assertEquals(63, read.readLong()); + Assert.assertEquals(64, read.readLong()); + Assert.assertEquals(127, read.readLong()); + Assert.assertEquals(128, read.readLong()); + Assert.assertEquals(8192, read.readLong()); + Assert.assertEquals(16384, read.readLong()); + Assert.assertEquals(2097151, read.readLong()); + Assert.assertEquals(1048575, read.readLong()); + Assert.assertEquals(134217727, read.readLong()); + Assert.assertEquals(268435455, read.readLong()); + Assert.assertEquals(134217728, read.readLong()); + Assert.assertEquals(268435456, read.readLong()); + Assert.assertEquals(-2097151, read.readLong()); + Assert.assertEquals(-1048575, read.readLong()); + Assert.assertEquals(-134217727, read.readLong()); + Assert.assertEquals(-268435455, read.readLong()); + Assert.assertEquals(-134217728, read.readLong()); + Assert.assertEquals(-268435456, read.readLong()); + Assert.assertEquals(0, read.readLong(true)); + Assert.assertEquals(0, read.readLong(false)); + Assert.assertEquals(63, read.readLong(true)); + Assert.assertEquals(63, read.readLong(false)); + Assert.assertEquals(64, read.readLong(true)); + Assert.assertEquals(64, read.readLong(false)); + Assert.assertEquals(127, read.readLong(true)); + Assert.assertEquals(127, read.readLong(false)); + Assert.assertEquals(128, read.readLong(true)); + Assert.assertEquals(128, read.readLong(false)); + Assert.assertEquals(8191, read.readLong(true)); + Assert.assertEquals(8191, read.readLong(false)); + Assert.assertEquals(8192, read.readLong(true)); + Assert.assertEquals(8192, read.readLong(false)); + Assert.assertEquals(16383, read.readLong(true)); + Assert.assertEquals(16383, read.readLong(false)); + Assert.assertEquals(16384, read.readLong(true)); + Assert.assertEquals(16384, read.readLong(false)); + Assert.assertEquals(2097151, read.readLong(true)); + Assert.assertEquals(2097151, read.readLong(false)); + Assert.assertEquals(1048575, read.readLong(true)); + Assert.assertEquals(1048575, read.readLong(false)); + Assert.assertEquals(134217727, read.readLong(true)); + Assert.assertEquals(134217727, read.readLong(false)); + Assert.assertEquals(268435455, read.readLong(true)); + Assert.assertEquals(268435455, read.readLong(false)); + Assert.assertEquals(134217728, read.readLong(true)); + Assert.assertEquals(134217728, read.readLong(false)); + Assert.assertEquals(268435456, read.readLong(true)); + Assert.assertEquals(268435456, read.readLong(false)); + Assert.assertEquals(-64, read.readLong(false)); + Assert.assertEquals(-64, read.readLong(true)); + Assert.assertEquals(-65, read.readLong(false)); + Assert.assertEquals(-65, read.readLong(true)); + Assert.assertEquals(-8192, read.readLong(false)); + Assert.assertEquals(-8192, read.readLong(true)); + Assert.assertEquals(-1048576, read.readLong(false)); + Assert.assertEquals(-1048576, read.readLong(true)); + Assert.assertEquals(-134217728, read.readLong(false)); + Assert.assertEquals(-134217728, read.readLong(true)); + Assert.assertEquals(-134217729, read.readLong(false)); + Assert.assertEquals(-134217729, read.readLong(true)); + + Random random = new Random(); + for (int i = 0; i < 10000; i++) { + long value = random.nextLong(); + write.clear(); + write.writeLong(value); + write.writeLong(value, true); + write.writeLong(value, false); + read.setBuffer(write.toBytes()); + Assert.assertEquals(value, read.readLong()); + Assert.assertEquals(value, read.readLong(true)); + Assert.assertEquals(value, read.readLong(false)); + } + } + + @Test + public void testShorts() { + runShortTest(new ByteBuffer2(4096)); + } + + private void runShortTest(ByteBuffer2 write) { + write.writeShort(0); + write.writeShort(63); + write.writeShort(64); + write.writeShort(127); + write.writeShort(128); + write.writeShort(8192); + write.writeShort(16384); + write.writeShort(32767); + write.writeShort(-63); + write.writeShort(-64); + write.writeShort(-127); + write.writeShort(-128); + write.writeShort(-8192); + write.writeShort(-16384); + write.writeShort(-32768); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(0, read.readShort()); + Assert.assertEquals(63, read.readShort()); + Assert.assertEquals(64, read.readShort()); + Assert.assertEquals(127, read.readShort()); + Assert.assertEquals(128, read.readShort()); + Assert.assertEquals(8192, read.readShort()); + Assert.assertEquals(16384, read.readShort()); + Assert.assertEquals(32767, read.readShort()); + Assert.assertEquals(-63, read.readShort()); + Assert.assertEquals(-64, read.readShort()); + Assert.assertEquals(-127, read.readShort()); + Assert.assertEquals(-128, read.readShort()); + Assert.assertEquals(-8192, read.readShort()); + Assert.assertEquals(-16384, read.readShort()); + Assert.assertEquals(-32768, read.readShort()); + } + + @Test + public void testFloats() { + runFloatTest(new ByteBuffer2(4096)); + } + + private void runFloatTest(ByteBuffer2 write) { + write.writeFloat(0); + write.writeFloat(63); + write.writeFloat(64); + write.writeFloat(127); + write.writeFloat(128); + write.writeFloat(8192); + write.writeFloat(16384); + write.writeFloat(32767); + write.writeFloat(-63); + write.writeFloat(-64); + write.writeFloat(-127); + write.writeFloat(-128); + write.writeFloat(-8192); + write.writeFloat(-16384); + write.writeFloat(-32768); + Assert.assertEquals(1, write.writeFloat(0, 1000, true)); + Assert.assertEquals(1, write.writeFloat(0, 1000, false)); + Assert.assertEquals(3, write.writeFloat(63, 1000, true)); + Assert.assertEquals(3, write.writeFloat(63, 1000, false)); + Assert.assertEquals(3, write.writeFloat(64, 1000, true)); + Assert.assertEquals(3, write.writeFloat(64, 1000, false)); + Assert.assertEquals(3, write.writeFloat(127, 1000, true)); + Assert.assertEquals(3, write.writeFloat(127, 1000, false)); + Assert.assertEquals(3, write.writeFloat(128, 1000, true)); + Assert.assertEquals(3, write.writeFloat(128, 1000, false)); + Assert.assertEquals(4, write.writeFloat(8191, 1000, true)); + Assert.assertEquals(4, write.writeFloat(8191, 1000, false)); + Assert.assertEquals(4, write.writeFloat(8192, 1000, true)); + Assert.assertEquals(4, write.writeFloat(8192, 1000, false)); + Assert.assertEquals(4, write.writeFloat(16383, 1000, true)); + Assert.assertEquals(4, write.writeFloat(16383, 1000, false)); + Assert.assertEquals(4, write.writeFloat(16384, 1000, true)); + Assert.assertEquals(4, write.writeFloat(16384, 1000, false)); + Assert.assertEquals(4, write.writeFloat(32767, 1000, true)); + Assert.assertEquals(4, write.writeFloat(32767, 1000, false)); + Assert.assertEquals(3, write.writeFloat(-64, 1000, false)); + Assert.assertEquals(5, write.writeFloat(-64, 1000, true)); + Assert.assertEquals(3, write.writeFloat(-65, 1000, false)); + Assert.assertEquals(5, write.writeFloat(-65, 1000, true)); + Assert.assertEquals(4, write.writeFloat(-8192, 1000, false)); + Assert.assertEquals(5, write.writeFloat(-8192, 1000, true)); + + float delta = 0.00000001f; + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(read.readFloat(), 0f, delta); + Assert.assertEquals(read.readFloat(), 63f, delta); + Assert.assertEquals(read.readFloat(), 64f, delta); + Assert.assertEquals(read.readFloat(), 127f, delta); + Assert.assertEquals(read.readFloat(), 128f, delta); + Assert.assertEquals(read.readFloat(), 8192f, delta); + Assert.assertEquals(read.readFloat(), 16384f, delta); + Assert.assertEquals(read.readFloat(), 32767f, delta); + Assert.assertEquals(read.readFloat(), -63f, delta); + Assert.assertEquals(read.readFloat(), -64f, delta); + Assert.assertEquals(read.readFloat(), -127f, delta); + Assert.assertEquals(read.readFloat(), -128f, delta); + Assert.assertEquals(read.readFloat(), -8192f, delta); + Assert.assertEquals(read.readFloat(), -16384f, delta); + Assert.assertEquals(read.readFloat(), -32768f, delta); + Assert.assertEquals(read.readFloat(1000, true), 0f, delta); + Assert.assertEquals(read.readFloat(1000, false), 0f, delta); + Assert.assertEquals(read.readFloat(1000, true), 63f, delta); + Assert.assertEquals(read.readFloat(1000, false), 63f, delta); + Assert.assertEquals(read.readFloat(1000, true), 64f, delta); + Assert.assertEquals(read.readFloat(1000, false), 64f, delta); + Assert.assertEquals(read.readFloat(1000, true), 127f, delta); + Assert.assertEquals(read.readFloat(1000, false), 127f, delta); + Assert.assertEquals(read.readFloat(1000, true), 128f, delta); + Assert.assertEquals(read.readFloat(1000, false), 128f, delta); + Assert.assertEquals(read.readFloat(1000, true), 8191f, delta); + Assert.assertEquals(read.readFloat(1000, false), 8191f, delta); + Assert.assertEquals(read.readFloat(1000, true), 8192f, delta); + Assert.assertEquals(read.readFloat(1000, false), 8192f, delta); + Assert.assertEquals(read.readFloat(1000, true), 16383f, delta); + Assert.assertEquals(read.readFloat(1000, false), 16383f, delta); + Assert.assertEquals(read.readFloat(1000, true), 16384f, delta); + Assert.assertEquals(read.readFloat(1000, false), 16384f, delta); + Assert.assertEquals(read.readFloat(1000, true), 32767f, delta); + Assert.assertEquals(read.readFloat(1000, false), 32767f, delta); + Assert.assertEquals(read.readFloat(1000, false), -64f, delta); + Assert.assertEquals(read.readFloat(1000, true), -64f, delta); + Assert.assertEquals(read.readFloat(1000, false), -65f, delta); + Assert.assertEquals(read.readFloat(1000, true), -65f, delta); + Assert.assertEquals(read.readFloat(1000, false), -8192f, delta); + Assert.assertEquals(read.readFloat(1000, true), -8192f, delta); + } + + @Test + public void testDoubles() { + runDoubleTest(new ByteBuffer2(4096)); + } + + private void runDoubleTest(ByteBuffer2 write) { + write.writeDouble(0); + write.writeDouble(63); + write.writeDouble(64); + write.writeDouble(127); + write.writeDouble(128); + write.writeDouble(8192); + write.writeDouble(16384); + write.writeDouble(32767); + write.writeDouble(-63); + write.writeDouble(-64); + write.writeDouble(-127); + write.writeDouble(-128); + write.writeDouble(-8192); + write.writeDouble(-16384); + write.writeDouble(-32768); + Assert.assertEquals(1, write.writeDouble(0, 1000, true)); + Assert.assertEquals(1, write.writeDouble(0, 1000, false)); + Assert.assertEquals(3, write.writeDouble(63, 1000, true)); + Assert.assertEquals(3, write.writeDouble(63, 1000, false)); + Assert.assertEquals(3, write.writeDouble(64, 1000, true)); + Assert.assertEquals(3, write.writeDouble(64, 1000, false)); + Assert.assertEquals(3, write.writeDouble(127, 1000, true)); + Assert.assertEquals(3, write.writeDouble(127, 1000, false)); + Assert.assertEquals(3, write.writeDouble(128, 1000, true)); + Assert.assertEquals(3, write.writeDouble(128, 1000, false)); + Assert.assertEquals(4, write.writeDouble(8191, 1000, true)); + Assert.assertEquals(4, write.writeDouble(8191, 1000, false)); + Assert.assertEquals(4, write.writeDouble(8192, 1000, true)); + Assert.assertEquals(4, write.writeDouble(8192, 1000, false)); + Assert.assertEquals(4, write.writeDouble(16383, 1000, true)); + Assert.assertEquals(4, write.writeDouble(16383, 1000, false)); + Assert.assertEquals(4, write.writeDouble(16384, 1000, true)); + Assert.assertEquals(4, write.writeDouble(16384, 1000, false)); + Assert.assertEquals(4, write.writeDouble(32767, 1000, true)); + Assert.assertEquals(4, write.writeDouble(32767, 1000, false)); + Assert.assertEquals(3, write.writeDouble(-64, 1000, false)); + Assert.assertEquals(9, write.writeDouble(-64, 1000, true)); + Assert.assertEquals(3, write.writeDouble(-65, 1000, false)); + Assert.assertEquals(9, write.writeDouble(-65, 1000, true)); + Assert.assertEquals(4, write.writeDouble(-8192, 1000, false)); + Assert.assertEquals(9, write.writeDouble(-8192, 1000, true)); + write.writeDouble(1.23456d); + + double delta = 0.00000001D; + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(read.readDouble(), 0d, delta); + Assert.assertEquals(read.readDouble(), 63d, delta); + Assert.assertEquals(read.readDouble(), 64d, delta); + Assert.assertEquals(read.readDouble(), 127d, delta); + Assert.assertEquals(read.readDouble(), 128d, delta); + Assert.assertEquals(read.readDouble(), 8192d, delta); + Assert.assertEquals(read.readDouble(), 16384d, delta); + Assert.assertEquals(read.readDouble(), 32767d, delta); + Assert.assertEquals(read.readDouble(), -63d, delta); + Assert.assertEquals(read.readDouble(), -64d, delta); + Assert.assertEquals(read.readDouble(), -127d, delta); + Assert.assertEquals(read.readDouble(), -128d, delta); + Assert.assertEquals(read.readDouble(), -8192d, delta); + Assert.assertEquals(read.readDouble(), -16384d, delta); + Assert.assertEquals(read.readDouble(), -32768d, delta); + Assert.assertEquals(read.readDouble(1000, true), 0d, delta); + Assert.assertEquals(read.readDouble(1000, false), 0d, delta); + Assert.assertEquals(read.readDouble(1000, true), 63d, delta); + Assert.assertEquals(read.readDouble(1000, false), 63d, delta); + Assert.assertEquals(read.readDouble(1000, true), 64d, delta); + Assert.assertEquals(read.readDouble(1000, false), 64d, delta); + Assert.assertEquals(read.readDouble(1000, true), 127d, delta); + Assert.assertEquals(read.readDouble(1000, false), 127d, delta); + Assert.assertEquals(read.readDouble(1000, true), 128d, delta); + Assert.assertEquals(read.readDouble(1000, false), 128d, delta); + Assert.assertEquals(read.readDouble(1000, true), 8191d, delta); + Assert.assertEquals(read.readDouble(1000, false), 8191d, delta); + Assert.assertEquals(read.readDouble(1000, true), 8192d, delta); + Assert.assertEquals(read.readDouble(1000, false), 8192d, delta); + Assert.assertEquals(read.readDouble(1000, true), 16383d, delta); + Assert.assertEquals(read.readDouble(1000, false), 16383d, delta); + Assert.assertEquals(read.readDouble(1000, true), 16384d, delta); + Assert.assertEquals(read.readDouble(1000, false), 16384d, delta); + Assert.assertEquals(read.readDouble(1000, true), 32767d, delta); + Assert.assertEquals(read.readDouble(1000, false), 32767d, delta); + Assert.assertEquals(read.readDouble(1000, false), -64d, delta); + Assert.assertEquals(read.readDouble(1000, true), -64d, delta); + Assert.assertEquals(read.readDouble(1000, false), -65d, delta); + Assert.assertEquals(read.readDouble(1000, true), -65d, delta); + Assert.assertEquals(read.readDouble(1000, false), -8192d, delta); + Assert.assertEquals(read.readDouble(1000, true), -8192d, delta); + Assert.assertEquals(1.23456d, read.readDouble(), delta); + } + + @Test + public void testBooleans() { + runBooleanTest(new ByteBuffer2(4096)); + } + + private void runBooleanTest(ByteBuffer2 write) { + for (int i = 0; i < 100; i++) { + write.writeBoolean(true); + write.writeBoolean(false); + } + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + for (int i = 0; i < 100; i++) { + Assert.assertEquals(true, read.readBoolean()); + Assert.assertEquals(false, read.readBoolean()); + } + } + + @Test + public void testChars() { + runCharTest(new ByteBuffer2(4096)); + } + + private void runCharTest(ByteBuffer2 write) { + write.writeChar((char) 0); + write.writeChar((char) 63); + write.writeChar((char) 64); + write.writeChar((char) 127); + write.writeChar((char) 128); + write.writeChar((char) 8192); + write.writeChar((char) 16384); + write.writeChar((char) 32767); + write.writeChar((char) 65535); + + ByteBuffer2 read = new ByteBuffer2(write.toBytes()); + Assert.assertEquals(0, read.readChar()); + Assert.assertEquals(63, read.readChar()); + Assert.assertEquals(64, read.readChar()); + Assert.assertEquals(127, read.readChar()); + Assert.assertEquals(128, read.readChar()); + Assert.assertEquals(8192, read.readChar()); + Assert.assertEquals(16384, read.readChar()); + Assert.assertEquals(32767, read.readChar()); + Assert.assertEquals(65535, read.readChar()); + } + + @Test + public void testInputWithOffset() throws Exception { + final byte[] buf = new byte[30]; + final ByteBuffer2 in = new ByteBuffer2(buf); + in.skip(20); + Assert.assertEquals(10, in.remaining()); + } + + @Test + public void testSmallBuffers() throws Exception { + ByteBuffer buf = ByteBuffer.allocate(1024); + + ByteBuffer2 testOutput = new ByteBuffer2(buf.array()); + testOutput.writeBytes(new byte[512]); + testOutput.writeBytes(new byte[512]); + + ByteBuffer2 testInputs = new ByteBuffer2(); + buf.flip(); + + testInputs.setBuffer(buf.array()); + byte[] toRead = new byte[512]; + testInputs.readBytes(toRead); + + testInputs.readBytes(toRead); + } +}