package yuku.bintex;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class BintexReader implements Closeable {
private static final int[] SUPPORTED_TYPE_MAP = { // 1 = int; 2 = string; 3 = int[]; 4 = simple map
//.1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, // 0.
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1.
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2.
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3.
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4.
0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 5.
0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 6.
2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8.
4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // a.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // b.
3, 3, 0, 0, 3, 0, 0, 0, 3, 3, 0, 0, 3, 0, 0, 0, // c.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // d.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // e.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // f.
};
private InputStream is_;
private int pos_ = 0;
private static ThreadLocal<byte[]> buf_byte_ = new ThreadLocal<byte[]>() {
@Override protected byte[] initialValue() {
return new byte[2048];
}
};
private static ThreadLocal<char[]> buf_char_ = new ThreadLocal<char[]>() {
@Override protected char[] initialValue() {
return new char[1024];
}
};
public BintexReader(InputStream is) {
this.is_ = is;
}
/**
* Acts as if you were instantiating this object, only without memory allocations.
* Make sure that you have done with the current object before reusing this object.
* @return this object so that you can use ternary operator
*/
public BintexReader reuse(InputStream is) {
this.is_ = is;
this.pos_ = 0;
return this;
}
public String readShortString() throws IOException {
int len = is_.read();
pos_++;
if (len < 0) {
throw new EOFException();
} else if (len == 0) {
return "";
}
// max len = 255, maka buf pasti cukup
char[] _buf = buf_char_.get();
for (int i = 0; i < len; i++) {
_buf[i] = readCharWithoutIncreasingPos();
}
pos_ += len + len;
return new String(_buf, 0, len);
}
public String readLongString() throws IOException {
int len = readInt();
if (len == 0) {
return "";
}
char[] buf_char = buf_char_.get();
if (len > buf_char.length) {
buf_char = new char[len + 1024];
buf_char_.set(buf_char);
}
for (int i = 0; i < len; i++) {
buf_char[i] = readCharWithoutIncreasingPos();
}
pos_ += len + len;
return new String(buf_char, 0, len);
}
/**
* Read 8-bit or 16-bit string.
*
* The first byte determines:
* 0x01 = 8 bit short
* 0x02 = 16 bit short
* 0x11 = 8 bit long
* 0x12 = 16 bit long
*/
public String readAutoString() throws IOException {
int kind = readUint8();
int len = 0;
if (kind == 0x01 || kind == 0x02) {
len = readUint8();
} else if (kind == 0x11 || kind == 0x12) {
len = readInt();
}
char[] buf_char = buf_char_.get();
if (len > buf_char.length) {
buf_char = new char[len + 1024];
buf_char_.set(buf_char);
}
if (kind == 0x01 || kind == 0x11) {
for (int i = 0; i < len; i++) {
buf_char[i] = (char) is_.read();
}
pos_ += len;
return new String(buf_char, 0, len);
} else if (kind == 0x02 || kind == 0x12) {
for (int i = 0; i < len; i++) {
buf_char[i] = readCharWithoutIncreasingPos();
}
pos_ += len + len;
return new String(buf_char, 0, len);
} else {
return null;
}
}
public int readInt() throws IOException {
int res = (is_.read() << 24) | (is_.read() << 16) | (is_.read() << 8) | (is_.read());
pos_ += 4;
return res;
}
public char readChar() throws IOException {
char res = (char) ((is_.read() << 8) | (is_.read()));
pos_ += 2;
return res;
}
private char readCharWithoutIncreasingPos() throws IOException {
return (char) ((is_.read() << 8) | (is_.read()));
}
public int readUint8() throws IOException {
int res = is_.read();
pos_++;
return res;
}
public int readUint16() throws IOException {
int res = (is_.read() << 8) | (is_.read());
pos_ += 2;
return res;
}
public float readFloat() throws IOException {
int a = (is_.read() << 24) | (is_.read() << 16) | (is_.read() << 8) | (is_.read());
pos_ += 4;
return Float.intBitsToFloat(a);
}
public int readRaw(byte[] buf) throws IOException {
return readRaw(buf, 0, buf.length);
}
public int readRaw(byte[] buf, int off, int len) throws IOException {
int total = 0;
int _off = off;
int _len = len;
while (true) {
int read = is_.read(buf, _off, _len);
if (read < 0) {
if (total == 0) total = -1;
break;
}
total += read;
if (total >= len) {
break;
}
_off += read;
_len -= read;
}
pos_ += total;
return total;
}
public int readVarUint() throws IOException {
int first = is_.read();
if ((first & 0x80) == 0) { // 0xxxxxxx
pos_ += 1;
return first;
} else if ((first & 0xc0) == 0x80) { // 10xxxxxx
int next0 = is_.read();
pos_ += 2;
return ((first & 0x3f) << 8) | (next0);
} else if ((first & 0xe0) == 0xc0) { // 110xxxxx
int next1 = is_.read();
int next0 = is_.read();
pos_ += 3;
return ((first & 0x1f) << 16) | (next1 << 8) | (next0);
} else if ((first & 0xf0) == 0xe0) { // 1110xxxx
int next2 = is_.read();
int next1 = is_.read();
int next0 = is_.read();
pos_ += 4;
return ((first & 0x0f) << 24) | (next2 << 16) | (next1 << 8) | (next0);
} else if (first == 0xf0) { // 11110000
int next3 = is_.read();
int next2 = is_.read();
int next1 = is_.read();
int next0 = is_.read();
pos_ += 5;
return (next3 << 24) | (next2 << 16) | (next1 << 8) | (next0);
} else {
pos_ += 1;
throw new RuntimeException("unknown first byte in varuint: " + first);
}
}
public int readValueInt() throws IOException {
int t = is_.read();
pos_++;
return _readValueInt(t);
}
private int _readValueInt(int t) throws IOException {
switch (t) {
case 0x0e: // special value 0
return 0;
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07: // immediate 1-7
return t;
case 0x0f: // special value -1
return -1;
case 0x10:
case 0x11: {
int a = is_.read();
pos_++;
return t == 0x11? ~a: a;
}
case 0x20:
case 0x21: {
int a = (is_.read() << 8) | (is_.read());
pos_ += 2;
return t == 0x21? ~a: a;
}
case 0x30:
case 0x31: {
int a = (is_.read() << 16) | (is_.read() << 8) | (is_.read());
pos_ += 3;
return t == 0x31? ~a: a;
}
case 0x40:
case 0x41: {
int a = (is_.read() << 24) | (is_.read() << 16) | (is_.read() << 8) | (is_.read());
pos_ += 4;
return t == 0x41? ~a: a;
}
default: {
throw new IOException(String.format("value is not int: type=%02x", t));
}
}
}
public String readValueString() throws IOException {
int t = is_.read();
pos_++;
return _readValueString(t);
}
private String _readValueString(int t) throws IOException {
switch (t) {
case 0x0c: // null
return null;
case 0x0d:
return "";
case 0x51:
case 0x52:
case 0x53:
case 0x54:
case 0x55:
case 0x56:
case 0x57:
case 0x58:
case 0x59:
case 0x5a:
case 0x5b:
case 0x5c:
case 0x5d:
case 0x5e:
case 0x5f: { // 8-bit string with len 1-15
int len = t & 0x0f;
return _read8BitString(len);
}
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
case 0x66:
case 0x67:
case 0x68:
case 0x69:
case 0x6a:
case 0x6b:
case 0x6c:
case 0x6d:
case 0x6e:
case 0x6f: { // 16-bit string with len 1-15
int len = t & 0x0f;
return _read16BitString(len);
}
case 0x70: { // 8-bit string with len < 256
int len = is_.read();
pos_++;
return _read8BitString(len);
}
case 0x71: { // 16-bit string with len < 256
int len = is_.read();
pos_++;
return _read16BitString(len);
}
case 0x72: { // long 8-bit string
int len = readInt();
return _read8BitString(len);
}
case 0x73: { // long 16-bit string
int len = readInt();
return _read16BitString(len);
}
default:
throw new IOException(String.format("value is not string: type=%02x", t));
}
}
private String _read8BitString(int len) throws IOException {
byte[] buf1 = buf_byte_.get();
if (len > buf1.length) {
buf1 = new byte[len + 100];
buf_byte_.set(buf1);
}
is_.read(buf1, 0, len);
pos_ += len;
return new String(buf1, 0x00, 0, len);
}
private String _read16BitString(int len) throws IOException {
int bytes = len << 1;
char[] buf2 = buf_char_.get();
if (len > buf2.length) {
buf2 = new char[len + 100];
buf_char_.set(buf2);
}
for (int i = 0; i < len; i++) {
buf2[i] = readCharWithoutIncreasingPos();
}
pos_ += bytes;
return new String(buf2, 0, len);
}
public int[] readValueUint8Array() throws IOException {
int t = is_.read();
pos_++;
return _readValueUint8Array(t);
}
private int[] _readValueUint8Array(int t) throws IOException {
int len;
if (t == 0xc0) { // len < 256
len = is_.read();
pos_++;
} else if (t == 0xc8) {
len = readInt();
} else {
throw new IOException(String.format("value is not uint8 array: type=%02x", t));
}
byte[] buf1 = buf_byte_.get();
if (len > buf1.length) {
buf1 = new byte[len + 100];
buf_byte_.set(buf1);
}
is_.read(buf1, 0, len);
pos_ += len;
int[] res = new int[len];
for (int i = 0; i < len; i++) {
res[i] = buf1[i] & 0xff;
}
return res;
}
public int[] readValueUint16Array() throws IOException {
int t = is_.read();
pos_++;
return _readValueUint16Array(t);
}
private int[] _readValueUint16Array(int t) throws IOException {
int len;
if (t == 0xc1) { // len < 256
len = is_.read();
pos_++;
} else if (t == 0xc9) {
len = readInt();
} else {
throw new IOException(String.format("value is not uint16 array: type=%02x", t));
}
int[] res = new int[len];
byte[] buf = new byte[2];
for (int i = 0; i < len; i++) {
is_.read(buf, 0, 2);
res[i] = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
}
pos_ += len + len;
return res;
}
/** also returns correctly if the data is of type uint8 or uint16 array */
public int[] readValueIntArray() throws IOException {
int t = is_.read();
pos_++;
return _readValueIntArray(t);
}
private int[] _readValueIntArray(int t) throws IOException {
int len;
if (t == 0xc0 || t == 0xc8) {
return _readValueUint8Array(t);
} else if (t == 0xc1 || t == 0xc9) {
return _readValueUint16Array(t);
} else if (t == 0xc4) { // len < 256
len = is_.read();
pos_++;
} else if (t == 0xcc) {
len = readInt();
} else {
throw new IOException(String.format("value is not int array: type=%02x", t));
}
int[] res = new int[len];
byte[] buf = new byte[4];
for (int i = 0; i < len; i++) {
is_.read(buf, 0, 4);
res[i] = ((buf[0] & 0xff) << 24) | ((buf[1] & 0xff) << 16) | ((buf[2] & 0xff) << 8) | (buf[3] & 0xff);
}
pos_ += len << 2;
return res;
}
public ValueMap readValueSimpleMap() throws IOException {
int t = is_.read();
pos_++;
return _readValueSimpleMap(t);
}
private ValueMap _readValueSimpleMap(int t) throws IOException {
if (t == 0x90) {
return new ValueMap();
}
if (t != 0x91) {
throw new IOException(String.format("value is not simple map: type=%02x", t));
}
// t must be 0x91 by here
int size = is_.read();
pos_++;
ValueMap res = new ValueMap();
for (int i = 0; i < size; i++) {
int key_len = is_.read();
pos_++;
String k = _read8BitString(key_len);
Object v = readValue();
res.put(k, v);
}
return res;
}
public Object readValue() throws IOException {
int t = is_.read();
pos_++;
// ints
int type = SUPPORTED_TYPE_MAP[t];
if (type == 1) {
return _readValueInt(t);
} else if (type == 2) {
return _readValueString(t);
} else if (type == 3) {
return _readValueIntArray(t);
} else if (type == 4) {
return _readValueSimpleMap(t);
} else {
throw new IOException(String.format("value has unknown type: type=%02x", t));
}
}
public long skip(long n) throws IOException {
long res = is_.skip(n);
pos_ += (int) res;
return res;
}
public int getPos() {
return pos_;
}
@Override public void close() {
try {
is_.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}