package yuku.bintex;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
/**
* VALUE types:
* A, B etc are unsigned byte
*
* [value takes 1 byte]
* 01..07 = int 1..7
* 0c = string null
* 0d = string with zero length
* 0e = int 0
* 0f = int -1
*
* [value takes 2 bytes]
* 10 A = int A
* 11 A = int ~A (negate of A, not minus A)
*
* [value takes 3 bytes]
* 20 A[2] = positive 16-bit int
* 21 A[2] = negate of 16-bit int
*
* [value takes 4 bytes]
* 30 A[3] = positive 24-bit int
* 31 A[3] = negate of 24-bit int
*
* [value takes 5 bytes]
* 40 A[4] = positive 32-bit int
* 41 A[4] = negate of 32-bit int
*
* [variable length]
* 51..5f A[n] = 8-bit (all the characters are in range 0x0000-0x00ff) string with length n=1..15
* 61..6f A[n*2] = 16-bit string with length n=1..15
* 70 A B[A] = 8-bit string with length A
* 71 A B[A] = 16-bit string with length A
* 72 A[4] B[A] = 8-bit string with length A
* 73 A[4] B[A] = 16-bit string with length A
*
* [maps]
* 90 empty map
* 91 A {B C[B], value}[A] = simple map (no more than 255 entries with 8-bit string keys and values of type: int/string/int[]/simplemap)
* (not-yet-implemented) value A {B C[B], value}[A] = same as above with total size of map specified
*
* [arrays]
* c0 A B[A] = uint8 array with max 255 elements
* c1 A B[A] = uint16 array with max 255 elements
* c4 A B[A*4] = int array with max 255 elements
* c8 A[4] B[A] = uint8 array
* c9 A[4] B[A] = uint16 array
* cc A[4] B[A*4] = int array
*/
public class BintexWriter implements Closeable {
private OutputStream os_;
private int pos_ = 0;
public BintexWriter(OutputStream os) {
this.os_ = os;
}
/**
* 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 BintexWriter reuse(OutputStream os) {
this.os_ = os;
this.pos_ = 0;
return this;
}
public void writeShortString(String s) throws IOException {
int len = s.length();
if (len > 255) {
throw new IllegalArgumentException("string must not more than 255 chars. String is: " + s);
}
os_.write(len);
pos_ += 1;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
writeChar(c);
}
}
public void writeLongString(String s) throws IOException {
writeInt(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
writeChar(c);
}
}
/**
* Tulis pake 8-bit atau 16-bit
*
* byte pertama menentukan
* 0x01 = 8 bit short
* 0x02 = 16 bit short
* 0x11 = 8 bit long
* 0x12 = 16 bit long
*/
public void writeAutoString(String s) throws IOException {
// cek dulu apa semuanya 8 bit
boolean semua8bit = true;
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (c > 0xff) {
semua8bit = false;
break;
}
}
if (len <= 255 && semua8bit) writeUint8(0x01);
if (len > 255 && semua8bit) writeUint8(0x11);
if (len <= 255 && !semua8bit) writeUint8(0x02);
if (len > 255 && !semua8bit) writeUint8(0x12);
if (len <= 255) {
writeUint8(len);
} else {
writeInt(len);
}
if (semua8bit) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
writeUint8(c);
}
} else {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
writeChar(c);
}
}
}
public void writeInt(int a) throws IOException {
os_.write((a & 0xff000000) >> 24);
os_.write((a & 0x00ff0000) >> 16);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 4;
}
public void writeChar(char c) throws IOException {
os_.write((c & 0xff00) >> 8);
os_.write(c & 0x00ff);
pos_ += 2;
}
public void writeUint8(int a) throws IOException {
if (a < 0 || a > 255) {
throw new IllegalArgumentException("uint8 must be 0 to 255");
}
os_.write(a);
pos_ += 1;
}
public void writeUint16(int a) throws IOException {
if (a < 0 || a > 0xffff) {
throw new IllegalArgumentException("uint16 must be 0 to 65535");
}
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff) >> 0);
pos_ += 2;
}
public void writeFloat(float f) throws IOException {
int a = Float.floatToIntBits(f);
writeInt(a);
}
public void writeRaw(byte[] buf) throws IOException {
writeRaw(buf, 0, buf.length);
}
public void writeRaw(byte[] buf, int off, int len) throws IOException {
os_.write(buf, off, len);
pos_ += len;
}
/** Write a non-negative int using variable length encoding.
* 0-127 is 1 byte: 0xxxxxxx
* 128-16383 (0x3fff) is 2 bytes: 10xxxxxx + 1byte
* 16383-2097151 (0x1fffff) is 3 bytes: 110xxxxx + 2byte
* 2097152-268435455 (0x0fffffff) is 4 bytes: 1110xxxx + 3byte
* 268435456-2147483647 (0x7fffffff) is 5 bytes: 11110000 + 0xxxxxxx + 3byte
**/
public void writeVarUint(int a) throws IOException {
if (a < 0) {
throw new RuntimeException("uint must be non-negative");
}
if (a <= 0x7f) {
os_.write(a);
pos_ += 1;
} else if (a <= 0x3fff) {
os_.write(((a & 0x0000ff00) >> 8) | 0x80);
os_.write((a & 0x000000ff) >> 0);
pos_ += 2;
} else if (a <= 0x1fffff) {
os_.write(((a & 0x00ff0000) >> 16) | 0xc0);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 3;
} else if (a <= 0x0fffffff) {
os_.write(((a & 0xff000000) >> 24) | 0xe0);
os_.write((a & 0x00ff0000) >> 16);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 4;
} else {
os_.write(0xf0);
os_.write((a & 0xff000000) >> 24);
os_.write((a & 0x00ff0000) >> 16);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 5;
}
}
public void writeValueInt(int a) throws IOException {
if (a == 0) {
os_.write(0x0e);
pos_ += 1;
} else if (a >= 1 && a <= 7) {
os_.write(a);
pos_ += 1;
} else if (a == -1) {
os_.write(0x0f);
pos_ += 1;
} else if (a > 0) {
if (a < 256) {
os_.write(0x10);
os_.write(a);
pos_ += 2;
} else if (a < 65536) {
os_.write(0x20);
os_.write((a & 0xff00) >> 8);
os_.write(a & 0x00ff);
pos_ += 3;
} else if (a < 16777216) {
os_.write(0x30);
os_.write((a & 0xff0000) >> 16);
os_.write((a & 0x00ff00) >> 8);
os_.write(a & 0x0000ff);
pos_ += 4;
} else {
os_.write(0x40);
os_.write((a & 0xff000000) >> 24);
os_.write((a & 0x00ff0000) >> 16);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 5;
}
} else {
a = ~a;
if (a < 256) {
os_.write(0x11);
os_.write(a);
pos_ += 2;
} else if (a < 65536) {
os_.write(0x21);
os_.write((a & 0xff00) >> 8);
os_.write(a & 0x00ff);
pos_ += 3;
} else if (a < 16777216) {
os_.write(0x31);
os_.write((a & 0xff0000) >> 16);
os_.write((a & 0x00ff00) >> 8);
os_.write(a & 0x0000ff);
pos_ += 4;
} else {
os_.write(0x41);
os_.write((a & 0xff000000) >> 24);
os_.write((a & 0x00ff0000) >> 16);
os_.write((a & 0x0000ff00) >> 8);
os_.write((a & 0x000000ff));
pos_ += 5;
}
}
}
public void writeValueString(String s) throws IOException {
// special case: null and length-0 strings
if (s == null) {
os_.write(0x0c);
pos_ += 1;
} else if (s.length() == 0) {
os_.write(0x0d);
pos_ += 1;
} else {
// check if all characters are 0x0000-0x00ff
boolean all8bits = true;
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (c > 0xff) {
all8bits = false;
break;
}
}
// write header and length
if (len < 16) {
if (all8bits) {
os_.write(0x50 | len);
} else {
os_.write(0x60 | len);
}
pos_ += 1;
} else if (len < 256) {
if (all8bits) {
os_.write(0x70);
os_.write(len);
} else {
os_.write(0x71);
os_.write(len);
}
pos_ += 2;
} else {
if (all8bits) {
os_.write(0x72);
} else {
os_.write(0x73);
}
pos_ += 1;
writeInt(len);
}
// write characters
if (all8bits) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
os_.write(c);
}
pos_ += len;
} else {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
os_.write((c & 0xff00) >> 8);
os_.write(c & 0x00ff);
}
pos_ += len << 1;
}
}
}
public void writeValueUint8Array(int[] a) throws IOException {
int len = a.length;
if (len < 256) {
writeUint8(0xc0);
writeUint8(len);
} else {
writeUint8(0xc8);
writeInt(len);
}
for (int e: a) {
if (e > 255) {
throw new RuntimeException("element is larger than 255");
}
os_.write(e);
}
pos_ += len;
}
public void writeValueUint16Array(int[] a) throws IOException {
int len = a.length;
if (len < 256) {
writeUint8(0xc1);
writeUint8(len);
} else {
writeUint8(0xc9);
writeInt(len);
}
for (int e: a) {
if (e > 0xffff) {
throw new RuntimeException("element is larger than 0xffff");
}
os_.write((e & 0x0000ff00) >> 8);
os_.write((e & 0x000000ff) >> 0);
}
pos_ += len + len;
}
/** will use {@link #writeValueUint8Array(int[])} or {@link #writeValueUint16Array(int[])} if possible */
public void writeValueIntArray(int[] a) throws IOException {
{ // check for optimization
// possible boolean values for (allUint8, allUint16):
// true, true -> use write value uint8
// false, true -> use write value uint16
// false, false -> use this (write value int)
boolean allUint8 = true;
boolean allUint16 = true;
optimTest: {
for (int e: a) {
if (e < 0 || e > 0xffff) {
allUint8 = allUint16 = false;
break optimTest;
}
}
for (int e: a) { // now we know all values are between 0..0xffff
if (e > 255) {
allUint8 = false;
break optimTest;
}
}
}
if (allUint8) {
writeValueUint8Array(a);
return;
} else if (allUint16) {
writeValueUint16Array(a);
return;
}
}
int len = a.length;
if (len < 256) {
writeUint8(0xc4);
writeUint8(len);
} else {
writeUint8(0xcc);
writeInt(len);
}
for (int e: a) {
os_.write((e & 0xff000000) >> 24);
os_.write((e & 0x00ff0000) >> 16);
os_.write((e & 0x0000ff00) >> 8);
os_.write((e & 0x000000ff));
}
pos_ += len << 2;
}
public void writeValueSimpleMap(Map<String, Object> map) throws IOException {
int size = map.size();
if (size == 0) { // empty map
os_.write(0x90);
pos_ += 1;
return;
}
if (size > 255) {
throw new RuntimeException("entries of map max 255");
}
for (Map.Entry<String, Object> e: map.entrySet()) {
Object v = e.getValue();
if (v instanceof String) {
} else if (v instanceof Number) {
} else if (v instanceof int[]) {
} else if (v instanceof Map) {
} else {
throw new RuntimeException("map entry values must be string, or int, or int array, or simple map");
}
String k = e.getKey();
if (k == null || k.length() > 255) {
throw new RuntimeException("map entry keys must be string with length max 255");
}
for (int i = 0; i < k.length(); i++) {
char c = k.charAt(i);
if (c > 0xff) throw new RuntimeException("map entry keys must be string with 8-bit characters");
}
}
os_.write(0x91);
os_.write(size);
pos_ += 2;
for (Map.Entry<String, Object> e: map.entrySet()) {
String k = e.getKey();
os_.write(k.length());
pos_ += 1;
for (int i = 0; i < k.length(); i++) {
os_.write(k.charAt(i));
}
pos_ += k.length();
Object v = e.getValue();
if (v instanceof String) {
writeValueString((String) v);
} else if (v instanceof Number) {
writeValueInt(((Number) v).intValue());
} else if (v instanceof int[]) {
writeValueIntArray((int[]) v);
} else if (v instanceof Map) {
@SuppressWarnings("unchecked") Map<String, Object> v_map = (Map<String, Object>) v;
writeValueSimpleMap(v_map);
}
}
}
@Override public void close() throws IOException {
os_.close();
}
public int getPos() {
return pos_;
}
public OutputStream getOutputStream() {
return new OutputStream() {
@Override
public void write(int oneByte) throws IOException {
writeUint8(oneByte);
}
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
writeRaw(buffer, offset, count);
}
};
}
}