KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BuilderAdapter.kt

153 lines
5.1 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.io.json
import com.google.common.collect.ImmutableList
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.isSuperclassOf
/**
* Kotlin property aware adapter.
*
* Создаёт пустые классы, а после наполняет их данными, что подходит для builder'ов с очень
* большим количеством возможных данных внутри.
*
* Подходит для игровых структур которые могут быть "разобраны" и пересобраны.
*/
class BuilderAdapter<T>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapter<T>() {
private val mappedFields = Object2ObjectArrayMap<String, KMutableProperty1<T, in Any?>>()
// потому что returnType медленный
private val mappedFieldsReturnTypes = Object2ObjectArrayMap<String, KType>()
private val loggedMisses = ObjectArraySet<String>()
private val ignoreProperties = ObjectArraySet<String>()
init {
for (field in fields) {
// потому что в котлине нет понятия KProperty который не имеет getter'а, только setter
require(mappedFields.put(field.name, field as KMutableProperty1<T, in Any?>) == null) { "${field.name} is defined twice" }
mappedFieldsReturnTypes[field.name] = field.returnType
}
}
val fields: List<KMutableProperty1<T, in Any?>> by lazy {
return@lazy ImmutableList.builder<KMutableProperty1<T, in Any?>>().let {
for (v in mappedFields.values.iterator()) {
it.add(v)
}
it.build()
}
}
fun ignoreProperty(vararg value: String): BuilderAdapter<T> {
ignoreProperties.addAll(value)
return this
}
var missingPropertiesAreFatal = true
var missingLogLevel = Level.ERROR
fun missingPropertiesAreFatal(flag: Boolean): BuilderAdapter<T> {
missingPropertiesAreFatal = flag
return this
}
fun missingLogLevel(level: Level): BuilderAdapter<T> {
missingLogLevel = level
return this
}
override fun write(writer: JsonWriter, value: T) {
TODO("Not yet implemented")
}
override fun read(reader: JsonReader): T? {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return null
}
reader.beginObject()
val instance = factory.invoke()!!
while (reader.hasNext()) {
val name = reader.nextName()
val field = mappedFields[name]
if (field != null) {
try {
val peek = reader.peek()
val expectedType = mappedFieldsReturnTypes[name]!!
if (!expectedType.isMarkedNullable && peek == JsonToken.NULL) {
throw NullPointerException("Property ${field.name} of ${instance::class.qualifiedName} does not accept nulls")
} else if (peek == JsonToken.NULL) {
field.set(instance, null)
reader.nextNull()
} else {
val classifier = expectedType.classifier
if (classifier is KClass<*>) {
if (classifier.isSuperclassOf(Float::class)) {
val read = reader.nextDouble()
field.set(instance, read.toFloat())
} else if (classifier.isSuperclassOf(Double::class)) {
val read = reader.nextDouble()
field.set(instance, read)
} else if (classifier.isSuperclassOf(Int::class)) {
val read = reader.nextInt()
field.set(instance, read)
} else if (classifier.isSuperclassOf(Long::class)) {
val read = reader.nextLong()
field.set(instance, read)
} else if (classifier.isSuperclassOf(String::class)) {
val read = reader.nextString()
field.set(instance, read)
} else if (classifier.isSuperclassOf(Boolean::class)) {
val read = reader.nextBoolean()
field.set(instance, read)
} else {
field.set(instance, Starbound.gson.fromJson(reader, classifier.java))
}
} else {
throw IllegalStateException("Expected ${field.name} classifier to be KClass, got $classifier")
}
}
} catch(err: Throwable) {
throw JsonSyntaxException(
"Reading property ${field.name} of ${instance::class.qualifiedName} near ${reader.path}",
err
)
}
} else if (!ignoreProperties.contains(name) && missingPropertiesAreFatal) {
throw JsonSyntaxException("Property $name is not present in ${instance::class.qualifiedName}")
} else {
if (!ignoreProperties.contains(name) && !loggedMisses.contains(name)) {
LOGGER.log(missingLogLevel, "{} has no property for storing {}", instance::class.qualifiedName, name)
loggedMisses.add(name)
}
reader.skipValue()
}
}
reader.endObject()
return instance
}
companion object {
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
}
}