Improve binary json
This commit is contained in:
parent
7c2d446563
commit
1e2611fd2e
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.mc.otm.core.util
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
@ -8,16 +9,186 @@ import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import net.minecraft.nbt.NbtAccounter
|
||||
import ru.dbotthepony.mc.otm.core.collect.stream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.function.Predicate
|
||||
|
||||
private const val TYPE_NULL = 0x01
|
||||
private const val TYPE_DOUBLE = 0x02
|
||||
private const val TYPE_BOOLEAN = 0x03
|
||||
private const val TYPE_INT = 0x04
|
||||
private const val TYPE_STRING = 0x05
|
||||
private const val TYPE_ARRAY = 0x06
|
||||
private const val TYPE_OBJECT = 0x07
|
||||
private enum class BinaryElementType(private val predicate: Predicate<JsonElement>) : Predicate<JsonElement> by predicate {
|
||||
NULL({ it is JsonNull }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return JsonNull.INSTANCE
|
||||
}
|
||||
},
|
||||
|
||||
DOUBLE({ it is JsonPrimitive && it.isNumber && (it.asNumber is Double || it.asNumber is Float) }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
stream.writeDouble(element.asDouble)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
accounter.accountBytes(8L)
|
||||
return JsonPrimitive(stream.readDouble())
|
||||
}
|
||||
},
|
||||
|
||||
BOOLEAN({ it is JsonPrimitive && it.isBoolean }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
stream.write(if (element.asBoolean) 1 else 0)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
accounter.accountBytes(1L)
|
||||
return JsonPrimitive(stream.read() > 0)
|
||||
}
|
||||
},
|
||||
|
||||
INT({ it is JsonPrimitive && it.isNumber }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
val it = element.asNumber
|
||||
|
||||
if (it is Int || it is Long || it is Short || it is Byte)
|
||||
stream.writeVarLongLE(element.asLong)
|
||||
else
|
||||
throw IllegalArgumentException("Unknown number type: ${it::class.qualifiedName}")
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return JsonPrimitive(stream.readVarLongLE(accounter))
|
||||
}
|
||||
},
|
||||
|
||||
STRING({ it is JsonPrimitive && it.isString }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
stream.writeBinaryString(element.asString)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return JsonPrimitive(stream.readBinaryString(accounter))
|
||||
}
|
||||
},
|
||||
|
||||
ARRAY({ it is JsonArray }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
writeArray(stream, element, OutputStream::writeBinaryJson)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return readArray(stream, accounter, InputStream::readBinaryJson)
|
||||
}
|
||||
},
|
||||
|
||||
OBJECT({ it is JsonObject }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
element as JsonObject
|
||||
stream.writeVarIntLE(element.size())
|
||||
|
||||
for ((k, v) in element.entrySet()) {
|
||||
stream.writeBinaryString(k)
|
||||
stream.writeBinaryJson(v)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
val count = stream.readVarIntLE(accounter)
|
||||
|
||||
if (count == 0) return JsonObject()
|
||||
if (count < 0) throw JsonSyntaxException("Tried to read json object with $count elements in it")
|
||||
|
||||
val build = JsonObject()
|
||||
|
||||
for (i in 0 until count) {
|
||||
val key = try {
|
||||
stream.readBinaryString(accounter)
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading json object at $i", err)
|
||||
}
|
||||
|
||||
try {
|
||||
build.add(key, stream.readBinaryJson(accounter))
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading json object at $i with name $key", err)
|
||||
}
|
||||
}
|
||||
|
||||
return build
|
||||
}
|
||||
},
|
||||
|
||||
DOUBLE_ARRAY({ it is JsonArray && it.stream().allMatch { DOUBLE.test(it) } }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
writeArray(stream, element, DOUBLE::write)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return readArray(stream, accounter, DOUBLE::read)
|
||||
}
|
||||
},
|
||||
|
||||
INT_ARRAY({ it is JsonArray && it.stream().allMatch { INT.test(it) } }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
writeArray(stream, element, INT::write)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return readArray(stream, accounter, INT::read)
|
||||
}
|
||||
},
|
||||
|
||||
BOOLEAN_ARRAY({ it is JsonArray && it.stream().allMatch { BOOLEAN.test(it) } }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
writeArray(stream, element, BOOLEAN::write)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return readArray(stream, accounter, BOOLEAN::read)
|
||||
}
|
||||
},
|
||||
|
||||
NULL_ARRAY({ it is JsonArray && it.stream().allMatch { NULL.test(it) } }) {
|
||||
override fun write(stream: OutputStream, element: JsonElement) {
|
||||
writeArray(stream, element, NULL::write)
|
||||
}
|
||||
|
||||
override fun read(stream: InputStream, accounter: NbtAccounter): JsonElement {
|
||||
return readArray(stream, accounter, NULL::read)
|
||||
}
|
||||
};
|
||||
|
||||
protected fun writeArray(stream: OutputStream, element: JsonElement, writer: (OutputStream, JsonElement) -> Unit) {
|
||||
element as JsonArray
|
||||
stream.writeVarIntLE(element.size())
|
||||
|
||||
for (v in element) {
|
||||
writer.invoke(stream, v)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun readArray(stream: InputStream, accounter: NbtAccounter, reader: (InputStream, NbtAccounter) -> JsonElement): JsonArray {
|
||||
val count = stream.readVarIntLE(accounter)
|
||||
|
||||
if (count == 0) return JsonArray()
|
||||
if (count < 0) throw JsonSyntaxException("Tried to read json array with $count elements in it")
|
||||
|
||||
return JsonArray(count).also {
|
||||
for (i in 0 until count) it.add(reader.invoke(stream, accounter))
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun write(stream: OutputStream, element: JsonElement)
|
||||
abstract fun read(stream: InputStream, accounter: NbtAccounter): JsonElement
|
||||
|
||||
companion object {
|
||||
val cached: ImmutableList<BinaryElementType> = ImmutableList.of(
|
||||
NULL, DOUBLE, BOOLEAN, INT, STRING, OBJECT,
|
||||
DOUBLE_ARRAY, INT_ARRAY, BOOLEAN_ARRAY, ARRAY)
|
||||
|
||||
val ordered: ImmutableList<BinaryElementType> = ImmutableList.copyOf(values())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes binary json to stream in Starbound Object Notation format
|
||||
@ -25,48 +196,15 @@ private const val TYPE_OBJECT = 0x07
|
||||
* just copy pasted this code from my another project because i was lazy
|
||||
*/
|
||||
fun OutputStream.writeBinaryJson(element: JsonElement) {
|
||||
if (element is JsonObject) {
|
||||
write(TYPE_OBJECT)
|
||||
writeVarIntLE(element.size())
|
||||
|
||||
for ((k, v) in element.entrySet()) {
|
||||
writeBinaryString(k)
|
||||
writeBinaryJson(v)
|
||||
for (writer in BinaryElementType.cached) {
|
||||
if (writer.test(element)) {
|
||||
write(writer.ordinal + 1)
|
||||
writer.write(this, element)
|
||||
return
|
||||
}
|
||||
} else if (element is JsonArray) {
|
||||
write(TYPE_ARRAY)
|
||||
writeVarIntLE(element.size())
|
||||
|
||||
for (v in element) {
|
||||
writeBinaryJson(v)
|
||||
}
|
||||
} else if (element is JsonPrimitive) {
|
||||
if (element.isNumber) {
|
||||
val num = element.asNumber
|
||||
|
||||
if (num is Int || num is Long || num is Short || num is Byte) {
|
||||
write(TYPE_INT)
|
||||
writeVarLongLE(num.toLong())
|
||||
} else if (num is Float || num is Double) {
|
||||
write(TYPE_DOUBLE)
|
||||
writeDouble(num.toDouble())
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown number type: ${num::class.qualifiedName}")
|
||||
}
|
||||
} else if (element.isString) {
|
||||
write(TYPE_STRING)
|
||||
writeBinaryString(element.asString)
|
||||
} else if (element.isBoolean) {
|
||||
write(TYPE_BOOLEAN)
|
||||
write(if (element.asBoolean) 1 else 0)
|
||||
} else {
|
||||
write(TYPE_NULL)
|
||||
}
|
||||
} else if (element is JsonNull) {
|
||||
write(TYPE_NULL)
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown element type: ${element::class.qualifiedName}")
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Unknown element type: ${element::class.qualifiedName}")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,54 +214,7 @@ fun OutputStream.writeBinaryJson(element: JsonElement) {
|
||||
*/
|
||||
fun InputStream.readBinaryJson(sizeLimit: NbtAccounter = NbtAccounter(1L shl 18 /* 256 KiB */)): JsonElement {
|
||||
sizeLimit.accountBytes(1L)
|
||||
|
||||
return when (val id = read()) {
|
||||
TYPE_NULL -> JsonNull.INSTANCE
|
||||
TYPE_DOUBLE -> {
|
||||
sizeLimit.accountBytes(8L)
|
||||
JsonPrimitive(readDouble())
|
||||
}
|
||||
TYPE_BOOLEAN -> {
|
||||
sizeLimit.accountBytes(1L)
|
||||
JsonPrimitive(read() > 1)
|
||||
}
|
||||
TYPE_INT -> JsonPrimitive(readVarLongLE(sizeLimit))
|
||||
TYPE_STRING -> JsonPrimitive(readBinaryString(sizeLimit))
|
||||
TYPE_ARRAY -> {
|
||||
val values = readVarIntLE(sizeLimit)
|
||||
|
||||
if (values == 0) return JsonArray()
|
||||
if (values < 0) throw JsonSyntaxException("Tried to read json array with $values elements in it")
|
||||
|
||||
val build = JsonArray(values)
|
||||
for (i in 0 until values) build.add(readBinaryJson(sizeLimit))
|
||||
return build
|
||||
}
|
||||
TYPE_OBJECT -> {
|
||||
val values = readVarIntLE(sizeLimit)
|
||||
if (values == 0) return JsonObject()
|
||||
if (values < 0) throw JsonSyntaxException("Tried to read json object with $values elements in it")
|
||||
|
||||
val build = JsonObject()
|
||||
|
||||
for (i in 0 until values) {
|
||||
val key: String
|
||||
|
||||
try {
|
||||
key = readBinaryString(sizeLimit)
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading json object at $i", err)
|
||||
}
|
||||
|
||||
try {
|
||||
build.add(key, readBinaryJson(sizeLimit))
|
||||
} catch(err: Throwable) {
|
||||
throw JsonSyntaxException("Reading json object at $i with name $key", err)
|
||||
}
|
||||
}
|
||||
|
||||
return build
|
||||
}
|
||||
else -> throw JsonParseException("Unknown element type $id")
|
||||
}
|
||||
val id = read() - 1
|
||||
val reader = BinaryElementType.ordered.getOrNull(id) ?: throw JsonParseException("Unknown element type ${id + 1}")
|
||||
return reader.read(this, sizeLimit)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user