💥
This commit is contained in:
parent
2805717ded
commit
3e5195cbc1
@ -6,6 +6,7 @@ import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
|
||||
private fun flattenJsonPrimitive(input: JsonPrimitive): Any {
|
||||
if (input.isNumber) {
|
||||
@ -32,8 +33,8 @@ private fun flattenJsonArray(input: JsonArray): ArrayList<Any> {
|
||||
return flattened
|
||||
}
|
||||
|
||||
private fun flattenJsonObject(input: JsonObject): Object2ObjectArrayMap<String, Any> {
|
||||
val flattened = Object2ObjectArrayMap<String, Any>()
|
||||
private fun flattenJsonObject(input: JsonObject): MutableMap<String, Any> {
|
||||
val flattened = Object2ObjectOpenHashMap<String, Any>()
|
||||
|
||||
for ((k, v) in input.entrySet()) {
|
||||
when (v) {
|
||||
@ -55,3 +56,7 @@ fun flattenJsonElement(input: JsonElement): Any? {
|
||||
else -> throw IllegalArgumentException("Unknown argument $input")
|
||||
}
|
||||
}
|
||||
|
||||
fun flattenJsonElement(input: JsonObject) = flattenJsonObject(input)
|
||||
fun flattenJsonElement(input: JsonArray) = flattenJsonArray(input)
|
||||
fun flattenJsonElement(input: JsonPrimitive) = flattenJsonPrimitive(input)
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
import ru.dbotthepony.kstarbound.io.json.INativeJsonHolder
|
||||
|
||||
/**
|
||||
* Базовый класс описания прототипа игрового объекта
|
||||
@ -12,10 +13,15 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
|
||||
* Если границы поля зависят от других полей, то проверка такого поля должна осуществляться уже при самой
|
||||
* сборке прототипа.
|
||||
*/
|
||||
abstract class RawPrototype<RAW : RawPrototype<RAW, ASSEMBLED>, ASSEMBLED : AssembledPrototype<ASSEMBLED, RAW>> {
|
||||
abstract class RawPrototype<RAW : RawPrototype<RAW, ASSEMBLED>, ASSEMBLED : AssembledPrototype<ASSEMBLED, RAW>> : INativeJsonHolder {
|
||||
val json = Object2ObjectArrayMap<String, Any>()
|
||||
fun enroll() = enrollMap(json)
|
||||
abstract fun assemble(directory: String = ""): ASSEMBLED
|
||||
|
||||
override fun acceptJson(json: MutableMap<String, Any>) {
|
||||
this.json.clear()
|
||||
this.json.putAll(json)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,9 +7,9 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.*
|
||||
import ru.dbotthepony.kstarbound.io.json.ConfigurableTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.BuilderAdapter
|
||||
import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.registerTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
import ru.dbotthepony.kvector.vector.Color
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -95,7 +95,7 @@ class ConfigurableProjectile : RawPrototype<ConfigurableProjectile, ConfiguredPr
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ADAPTER = ConfigurableTypeAdapter(
|
||||
val ADAPTER = BuilderAdapter.Builder(
|
||||
::ConfigurableProjectile,
|
||||
ConfigurableProjectile::projectileName,
|
||||
ConfigurableProjectile::physics,
|
||||
@ -118,16 +118,16 @@ class ConfigurableProjectile : RawPrototype<ConfigurableProjectile, ConfiguredPr
|
||||
ConfigurableProjectile::piercing,
|
||||
ConfigurableProjectile::speed,
|
||||
ConfigurableProjectile::power,
|
||||
)
|
||||
).build()
|
||||
|
||||
fun registerGson(gson: GsonBuilder) {
|
||||
gson.registerTypeAdapter(ConfigurableProjectile::class.java, ADAPTER)
|
||||
gson.registerTypeAdapter(ProjectilePhysics::class.java, CustomEnumTypeAdapter(ProjectilePhysics.values()).nullSafe())
|
||||
gson.registerTypeAdapter(ActionConfig::class.java, ActionConfig.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionProjectile::class.java, ActionProjectile.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionSound::class.java, ActionSound.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionLoop::class.java, ActionLoop.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionActions::class.java, ActionActions.ADAPTER)
|
||||
gson.registerTypeAdapter(ADAPTER)
|
||||
gson.registerTypeAdapter(CustomEnumTypeAdapter(ProjectilePhysics.values()).nullSafe())
|
||||
gson.registerTypeAdapter(ActionConfig.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionProjectile.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionSound.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionLoop.ADAPTER)
|
||||
gson.registerTypeAdapter(ActionActions.ADAPTER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,46 @@ package ru.dbotthepony.kstarbound.io.json
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.internal.bind.JsonTreeReader
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.defs.flattenJsonElement
|
||||
import ru.dbotthepony.kstarbound.util.NotNullVar
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
/**
|
||||
* Данный интерфейс имеет один единственный метод: [acceptJson]
|
||||
*
|
||||
* Используется в связке с [BuilderAdapter] для классов, которым необходимо хранить оригинальную JSON структуру
|
||||
*/
|
||||
interface IJsonHolder {
|
||||
/**
|
||||
* Выставляет [JsonObject], который является источником данных для данной структуры
|
||||
*/
|
||||
fun acceptJson(json: JsonObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* Для классов, которые хотят принимать Java'вские [Map] напрямую, как оригинальную JSON структуру
|
||||
*/
|
||||
interface INativeJsonHolder : IJsonHolder {
|
||||
override fun acceptJson(json: JsonObject) {
|
||||
acceptJson(flattenJsonElement(json))
|
||||
}
|
||||
|
||||
fun acceptJson(json: MutableMap<String, Any>)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun <T : Any> BuilderAdapter(factory: () -> T, vararg fields: KMutableProperty1<T, *>): BuilderAdapter<T> {
|
||||
val builder = BuilderAdapter.Builder(factory)
|
||||
@ -57,13 +84,22 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
}
|
||||
|
||||
override fun read(reader: JsonReader): T {
|
||||
@Suppress("name_shadowing")
|
||||
var reader = reader
|
||||
|
||||
val missing = ObjectArraySet<WrappedProperty<T, *>>()
|
||||
missing.addAll(properties.values)
|
||||
|
||||
reader.beginObject()
|
||||
|
||||
val instance = factory.invoke()
|
||||
|
||||
if (instance is IJsonHolder) {
|
||||
val obj = TypeAdapters.JSON_ELEMENT.read(reader)
|
||||
reader = JsonTreeReader(obj)
|
||||
instance.acceptJson(obj.asJsonObject)
|
||||
}
|
||||
|
||||
reader.beginObject()
|
||||
|
||||
while (reader.hasNext()) {
|
||||
val name = reader.nextName()
|
||||
|
||||
@ -247,6 +283,6 @@ class BuilderAdapter<T : Any> private constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
@ -1,129 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.io.json
|
||||
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.internal.bind.TypeAdapters
|
||||
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.LogManager
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.RawPrototype
|
||||
import ru.dbotthepony.kstarbound.defs.flattenJsonElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
|
||||
/**
|
||||
* Kotlin property aware adapter with arbitrary structure writer
|
||||
*/
|
||||
class ConfigurableTypeAdapter<T : RawPrototype<*, *>>(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>()
|
||||
|
||||
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: Array<KMutableProperty1<T, in Any?>> get() {
|
||||
val iterator = mappedFields.values.iterator()
|
||||
return Array(mappedFields.size) { iterator.next() }
|
||||
}
|
||||
|
||||
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 IllegalArgumentException("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()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read.toFloat())
|
||||
} else if (classifier.isSuperclassOf(Double::class)) {
|
||||
val read = reader.nextDouble()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read)
|
||||
} else if (classifier.isSuperclassOf(Int::class)) {
|
||||
val read = reader.nextInt()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read)
|
||||
} else if (classifier.isSuperclassOf(Long::class)) {
|
||||
val read = reader.nextLong()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read)
|
||||
} else if (classifier.isSuperclassOf(String::class)) {
|
||||
val read = reader.nextString()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read)
|
||||
} else if (classifier.isSuperclassOf(Boolean::class)) {
|
||||
val read = reader.nextBoolean()
|
||||
instance.json[name] = read
|
||||
field.set(instance, read)
|
||||
} else {
|
||||
val readElement = TypeAdapters.JSON_ELEMENT.read(reader)
|
||||
instance.json[name] = flattenJsonElement(readElement)
|
||||
field.set(instance, Starbound.gson.fromJson(readElement, 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 {
|
||||
instance.json[name] = flattenJsonElement(TypeAdapters.JSON_ELEMENT.read(reader))
|
||||
|
||||
if (!loggedMisses.contains(name)) {
|
||||
loggedMisses.add(name)
|
||||
LOGGER.warn("{} has no property for storing {}, this value will be visible to Lua scripts only", instance::class.qualifiedName, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
return instance
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user