414 lines
11 KiB
Kotlin
414 lines
11 KiB
Kotlin
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()
|
||
}
|
||
}
|
||
}
|
||
}
|