diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFlattener.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFlattener.kt index df9efb3b..7486fd14 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFlattener.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonFlattener.kt @@ -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 { return flattened } -private fun flattenJsonObject(input: JsonObject): Object2ObjectArrayMap { - val flattened = Object2ObjectArrayMap() +private fun flattenJsonObject(input: JsonObject): MutableMap { + val flattened = Object2ObjectOpenHashMap() 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) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt index 1417bf1b..0ed74859 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/RawPrototype.kt @@ -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, ASSEMBLED : AssembledPrototype> { +abstract class RawPrototype, ASSEMBLED : AssembledPrototype> : INativeJsonHolder { val json = Object2ObjectArrayMap() fun enroll() = enrollMap(json) abstract fun assemble(directory: String = ""): ASSEMBLED + + override fun acceptJson(json: MutableMap) { + this.json.clear() + this.json.putAll(json) + } } /** diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt index c7c9f208..5ecc68ff 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt @@ -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) +} + @Suppress("FunctionName") fun BuilderAdapter(factory: () -> T, vararg fields: KMutableProperty1): BuilderAdapter { val builder = BuilderAdapter.Builder(factory) @@ -57,13 +84,22 @@ class BuilderAdapter private constructor( } override fun read(reader: JsonReader): T { + @Suppress("name_shadowing") + var reader = reader + val missing = ObjectArraySet>() 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 private constructor( } companion object { - private val LOGGER = LogManager.getLogger(ConfigurableTypeAdapter::class.java) + private val LOGGER = LogManager.getLogger() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ConfigurableTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ConfigurableTypeAdapter.kt deleted file mode 100644 index 874b0650..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ConfigurableTypeAdapter.kt +++ /dev/null @@ -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>(val factory: () -> T, vararg fields: KMutableProperty1) : TypeAdapter() { - private val mappedFields = Object2ObjectArrayMap>() - // потому что returnType медленный - private val mappedFieldsReturnTypes = Object2ObjectArrayMap() - private val loggedMisses = ObjectArraySet() - - init { - for (field in fields) { - // потому что в котлине нет понятия KProperty который не имеет getter'а, только setter - require(mappedFields.put(field.name, field as KMutableProperty1) == null) { "${field.name} is defined twice" } - mappedFieldsReturnTypes[field.name] = field.returnType - } - } - - val fields: Array> 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) - } -} \ No newline at end of file