KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt
2024-02-03 20:50:54 +07:00

414 lines
11 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.json
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readSignedVarLong
import ru.dbotthepony.kommons.io.readString
import ru.dbotthepony.kommons.io.readVarInt
import java.io.DataInputStream
import java.io.EOFException
import java.io.InputStream
import java.io.Reader
import java.util.LinkedList
/**
* Позволяет читать двоичный JSON прямиком в [JsonElement]
*/
fun DataInputStream.readJsonElement(): JsonElement {
return when (val id = read()) {
BinaryJsonReader.TYPE_NULL -> JsonNull.INSTANCE
BinaryJsonReader.TYPE_DOUBLE -> JsonPrimitive(readDouble())
BinaryJsonReader.TYPE_BOOLEAN -> InternedJsonElementAdapter.of(readBoolean())
BinaryJsonReader.TYPE_INT -> JsonPrimitive(readSignedVarLong())
BinaryJsonReader.TYPE_STRING -> JsonPrimitive(readBinaryString())
BinaryJsonReader.TYPE_ARRAY -> readJsonArray()
BinaryJsonReader.TYPE_OBJECT -> readJsonObject()
else -> throw JsonParseException("Unknown element type $id")
}
}
/**
* Позволяет читать двоичный JSON объект прямиком в [JsonObject]
*/
fun DataInputStream.readJsonObject(): JsonObject {
val values = readVarInt()
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 = try {
readBinaryString()
} catch(err: Throwable) {
throw JsonSyntaxException("Reading json object at $i", err)
}
try {
build.add(key, readJsonElement())
} catch(err: Throwable) {
throw JsonSyntaxException("Reading json object at $i with name $key", err)
}
}
return build
}
/**
* Позволяет читать двоичный JSON массив прямиком в [JsonArray]
*/
fun DataInputStream.readJsonArray(): JsonArray {
val values = readVarInt()
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(readJsonElement())
return build
}
class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreadable) {
constructor(stream: InputStream) : this(DataInputStream(stream))
private var needToReadNext = true
private fun popstack(check: NothingReader) {
val popped = stack.removeLast()
check(popped == check) { "$popped != $check" }
needToReadNext = true
}
private fun actuallyReadNext(expectingValidValue: Boolean) {
when (val id = stream.read()) {
TYPE_NULL -> stack.addLast(nullReader)
TYPE_DOUBLE -> stack.addLast(DoubleReader(stream.readDouble()))
TYPE_BOOLEAN -> stack.addLast(if (stream.readBoolean()) trueReader else falseReader)
TYPE_INT -> stack.addLast(LongReader(stream.readSignedVarLong()))
TYPE_STRING -> stack.addLast(StringReader(stream.readVarInt()))
TYPE_OBJECT -> stack.addLast(ObjectReader(stream.readVarInt()))
TYPE_ARRAY -> stack.addLast(ArrayReader(stream.readVarInt()))
-1 -> { if (expectingValidValue) throw EOFException("Unexpected end of stream while expecting binary json element") }
else -> throw IllegalArgumentException("Invalid type ID: $id")
}
}
private fun readNext() {
if (!needToReadNext)
return
needToReadNext = false
val last = stack.lastOrNull()
if (last is ObjectReader) {
if (last.readPairs == last.pairs) {
stack.removeLast()
stack.addLast(endObject)
return
}
if (last.readingName) {
last.readingName = false
stack.addLast(NameReader(stream.readBinaryString()))
} else {
last.readingName = true
last.readPairs++
actuallyReadNext(true)
}
} else if (last is ArrayReader) {
if (last.readValues == last.size) {
stack.removeLast()
stack.addLast(endArray)
return
}
last.readValues++
actuallyReadNext(true)
} else {
actuallyReadNext(false)
}
}
override fun peek(): JsonToken {
readNext()
return (stack.lastOrNull() ?: endReader).peek()
}
override fun hasNext(): Boolean {
return peek() != JsonToken.END_OBJECT && peek() != JsonToken.END_ARRAY
}
private abstract inner class NothingReader(val name: String, val token: JsonToken) {
open fun beginArray(): Unit = throw JsonSyntaxException("Expected BEGIN_ARRAY, got $name")
open fun endArray(): Unit = throw JsonSyntaxException("Expected END_ARRAY, got $name")
open fun beginObject(): Unit = throw JsonSyntaxException("Expected BEGIN_OBJECT, got $name")
open fun endObject(): Unit = throw JsonSyntaxException("Expected END_OBJECT, got $name")
open fun nextName(): String = throw JsonSyntaxException("Expected a name, got $name")
open fun nextString(): String = throw JsonSyntaxException("Expected a string, got $name")
open fun nextBoolean(): Boolean = throw JsonSyntaxException("Expected a boolean, got $name")
open fun nextNull(): Unit = throw JsonSyntaxException("Expected a null, got $name")
open fun nextDouble(): Double = throw JsonSyntaxException("Expected a double, got $name")
open fun nextLong(): Long = throw JsonSyntaxException("Expected a long, got $name")
open fun nextInt(): Int = throw JsonSyntaxException("Expected an int, got $name")
open fun skipValue() {
popstack(this)
}
open fun peek(): JsonToken = token
override fun toString(): String {
return "ReaderShard[$name, $token]"
}
}
private inner class NullReader : NothingReader("null", JsonToken.NULL) {
override fun nextNull() { popstack(this) }
}
private inner class TrueReader : NothingReader("boolean", JsonToken.BOOLEAN) {
override fun nextBoolean(): Boolean { popstack(this); return true }
}
private inner class FalseReader : NothingReader("boolean", JsonToken.BOOLEAN) {
override fun nextBoolean(): Boolean { popstack(this); return false }
}
private inner class EndReader : NothingReader("end_document", JsonToken.END_DOCUMENT) {
override fun skipValue() {
check(stack.size == 0)
}
}
private val nullReader = NullReader()
private val trueReader = TrueReader()
private val falseReader = FalseReader()
private val endReader = EndReader()
private inner class LongReader(val value: Long) : NothingReader("long", JsonToken.NUMBER) {
override fun nextLong(): Long {
popstack(this)
return value
}
override fun nextDouble() = nextLong().toDouble()
override fun nextInt() = nextLong().toInt()
override fun nextString() = nextLong().toString()
}
private inner class DoubleReader(val value: Double) : NothingReader("double", JsonToken.NUMBER) {
override fun nextDouble(): Double {
popstack(this)
return value
}
override fun nextInt() = nextDouble().toInt()
override fun nextLong() = nextDouble().toLong()
override fun nextString() = nextDouble().toString()
}
private inner class StringReader(val length: Int) : NothingReader("string", JsonToken.STRING) {
private val value by lazy(LazyThreadSafetyMode.NONE) { stream.readString(length) }
override fun skipValue() {
popstack(this)
stream.skipNBytes(length.toLong())
}
override fun nextString(): String {
popstack(this)
return value
}
override fun nextInt(): Int {
try {
return value.toInt().also { popstack(this) }
} catch(_: NumberFormatException) {
try {
return value.toDouble().toInt().also { popstack(this) }
} catch (_: NumberFormatException) {
}
}
throw JsonSyntaxException("Expected an int, got string near ${this@BinaryJsonReader.path}")
}
override fun nextLong(): Long {
try {
return value.toLong().also { popstack(this) }
} catch(_: NumberFormatException) {
try {
return value.toDouble().toLong().also { popstack(this) }
} catch (_: NumberFormatException) {
}
}
throw JsonSyntaxException("Expected an long, got string near ${this@BinaryJsonReader.path}")
}
override fun nextDouble(): Double {
try {
return value.toDouble().also { popstack(this) }
} catch (_: NumberFormatException) {
}
throw JsonSyntaxException("Expected an long, got string near ${this@BinaryJsonReader.path}")
}
}
private inner class NameReader(val value: String) : NothingReader("name", JsonToken.NAME) {
override fun nextName(): String {
popstack(this)
return value
}
}
private inner class ObjectReader(val pairs: Int) : NothingReader("object", JsonToken.BEGIN_OBJECT) {
var readingName = true
var readPairs = 0
var consumed = false
override fun beginObject() {
check(!consumed) { "Already called beginObject on this object" }
check(stack.last() === this)
consumed = true
needToReadNext = true
}
}
private inner class EndObject : NothingReader("end object", JsonToken.END_OBJECT) {
override fun endObject() {
popstack(this)
}
}
private val endObject = EndObject()
private inner class ArrayReader(val size: Int) : NothingReader("array", JsonToken.BEGIN_ARRAY) {
var readValues = 0
var consumed = false
override fun beginArray() {
check(!consumed) { "Already called beginArray on this array" }
check(stack.last() === this)
consumed = true
needToReadNext = true
}
}
private inner class EndArray : NothingReader("end array", JsonToken.END_ARRAY) {
override fun endArray() {
popstack(this)
}
}
override fun close() {
stack.clear()
stream.close()
}
override fun getPath(): String {
return "<binary json>"
}
private val endArray = EndArray()
private val stack = LinkedList<NothingReader>()
override fun nextDouble(): Double {
readNext()
return (stack.lastOrNull() ?: endReader).nextDouble()
}
override fun nextLong(): Long {
readNext()
return (stack.lastOrNull() ?: endReader).nextLong()
}
override fun nextInt(): Int {
readNext()
return (stack.lastOrNull() ?: endReader).nextInt()
}
override fun nextNull() {
readNext()
return (stack.lastOrNull() ?: endReader).nextNull()
}
override fun beginObject() {
readNext()
return (stack.lastOrNull() ?: endReader).beginObject()
}
override fun endObject() {
readNext()
return (stack.lastOrNull() ?: endReader).endObject()
}
override fun beginArray() {
readNext()
return (stack.lastOrNull() ?: endReader).beginArray()
}
override fun endArray() {
readNext()
return (stack.lastOrNull() ?: endReader).endArray()
}
override fun nextName(): String {
readNext()
return (stack.lastOrNull() ?: endReader).nextName()
}
override fun nextString(): String {
readNext()
return (stack.lastOrNull() ?: endReader).nextString()
}
override fun nextBoolean(): Boolean {
readNext()
return (stack.lastOrNull() ?: endReader).nextBoolean()
}
override fun skipValue() {
readNext()
return (stack.lastOrNull() ?: endReader).skipValue()
}
companion object {
const val TYPE_NULL = 0x01
const val TYPE_DOUBLE = 0x02
const val TYPE_BOOLEAN = 0x03
/**
* На самом деле, variable int
*/
const val TYPE_INT = 0x04
const val TYPE_STRING = 0x05
const val TYPE_ARRAY = 0x06
const val TYPE_OBJECT = 0x07
private val unreadable = object : Reader() {
override fun read(cbuf: CharArray, off: Int, len: Int): Int {
throw AssertionError()
}
override fun close() {
throw AssertionError()
}
}
}
}