From 2d8e3a7ff5f415ba1ea76b2c705e5fa060efa14b Mon Sep 17 00:00:00 2001 From: DBotThePony <dbotthepony@yandex.ru> Date: Sun, 22 Jan 2023 20:29:48 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BC=D0=BC=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 3 ++ .../ru/dbotthepony/kstarbound/Starbound.kt | 5 +- .../io/json/builder/BuilderAdapter.kt | 9 +++- .../io/json/builder/FactoryAdapter.kt | 13 +++++- .../factory/ImmutableArrayMapTypeAdapter.kt | 37 +++++++++++++++ .../ImmutableCollectionAdapterFactory.kt | 42 +++++++++++++++++ .../json/factory/ImmutableListTypeAdapter.kt | 45 ++++++++++++++++++ .../json/factory/ImmutableMapTypeAdapter.kt | 35 ++++++++++++++ .../json/factory/ImmutableSetTypeAdapter.kt | 46 +++++++++++++++++++ .../io/json/util/LazyTypeProvider.kt | 4 +- 10 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableArrayMapTypeAdapter.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableListTypeAdapter.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableSetTypeAdapter.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 81a59540..dfa59cf5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -1,10 +1,13 @@ package ru.dbotthepony.kstarbound +import com.google.common.collect.ImmutableList +import com.google.gson.GsonBuilder import org.apache.logging.log4j.LogManager import org.lwjgl.Version import org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.io.* +import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.entities.ItemEntity import ru.dbotthepony.kstarbound.world.entities.PlayerEntity diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 4ab1c574..b4ad84b1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -46,6 +46,7 @@ import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter +import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kvector.vector.Color import java.io.* @@ -145,7 +146,9 @@ object Starbound { .setDateFormat(DateFormat.LONG) .setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) .setPrettyPrinting() - .registerTypeAdapter(Color::class.java, ColorTypeAdapter.nullSafe()) + .registerTypeAdapter(ColorTypeAdapter.nullSafe()) + + .registerTypeAdapterFactory(ImmutableCollectionAdapterFactory) // чтоб строки всегда intern'ились .registerTypeAdapter(NULLABLE_STRING_ADAPTER) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt index 6218f386..b099da1e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/BuilderAdapter.kt @@ -2,11 +2,14 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet +import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory import com.google.gson.internal.bind.JsonTreeReader import com.google.gson.internal.bind.TypeAdapters +import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter @@ -241,13 +244,17 @@ class BuilderAdapter<T : Any> private constructor( var mustBePresent: Boolean? = null } - class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) { + class Builder<T : Any>(val factory: () -> T, vararg fields: KMutableProperty1<T, *>) : TypeAdapterFactory { private val properties = ArrayList<WrappedProperty<T, *>>() private val flatProperties = ArrayList<WrappedProperty<T, *>>() private val ignoreKeys = ObjectArraySet<String>() var extraPropertiesAreFatal = false var logMisses: Boolean? = null + override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { + return null + } + /** * Являются ли "лишние" ключи в JSON структуре ошибкой. * diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index 456921ca..19a19fe4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -2,13 +2,16 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap +import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonParseException import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory import com.google.gson.internal.bind.JsonTreeReader import com.google.gson.internal.bind.TypeAdapters +import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter @@ -382,7 +385,7 @@ class FactoryAdapter<T : Any> private constructor( /** * Позволяет построить класс [FactoryAdapter] на основе заданных параметров */ - class Builder<T : Any>(val clazz: KClass<T>) { + class Builder<T : Any>(val clazz: KClass<T>) : TypeAdapterFactory { constructor(clazz: KClass<T>, vararg fields: KProperty1<T, *>) : this(clazz) { for (field in fields) { auto(field) @@ -391,6 +394,14 @@ class FactoryAdapter<T : Any> private constructor( private val types = ArrayList<PackedProperty<T, *>>() + override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { + if (type.rawType == clazz.java) { + return build() as TypeAdapter<T> + } + + return null + } + /** * Принимает ли класс *последним* аргументом JSON структуру * diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableArrayMapTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableArrayMapTypeAdapter.kt new file mode 100644 index 00000000..52a60081 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableArrayMapTypeAdapter.kt @@ -0,0 +1,37 @@ +package ru.dbotthepony.kstarbound.io.json.factory + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +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 + +class ImmutableArrayMapTypeAdapter<K, V>(val keyAdapter: TypeAdapter<K>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<K, V>>() { + override fun write(out: JsonWriter, value: ImmutableMap<K, V>) { + out.beginArray() + + for ((k, v) in value) { + keyAdapter.write(out, k) + elementAdapter.write(out, v) + } + + out.endArray() + } + + override fun read(reader: JsonReader): ImmutableMap<K, V> { + reader.beginArray() + + val builder = ImmutableMap.Builder<K, V>() + + while (reader.peek() != JsonToken.END_OBJECT) { + builder.put( + keyAdapter.read(reader) ?: throw JsonSyntaxException("Nulls as keys are not allowed, near ${reader.path}"), + elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls as values are not allowed, near ${reader.path}")) + } + + reader.endArray() + return builder.build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt new file mode 100644 index 00000000..4a716806 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt @@ -0,0 +1,42 @@ +package ru.dbotthepony.kstarbound.io.json.factory + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +import com.google.common.collect.ImmutableSet +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.internal.`$Gson$Types` +import com.google.gson.reflect.TypeToken + +@Suppress("unchecked_cast") +object ImmutableCollectionAdapterFactory : TypeAdapterFactory { + override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { + when (type.rawType) { + ImmutableList::class.java -> { + val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) + return ImmutableListTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter<T> + } + + ImmutableSet::class.java -> { + val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) + return ImmutableSetTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter<T> + } + + ImmutableMap::class.java -> { + val (elementType0, elementType1) = `$Gson$Types`.getMapKeyAndValueTypes(type.type, type.rawType) + + if (`$Gson$Types`.getRawType(elementType0) == String::class.java) { + return ImmutableMapTypeAdapter(gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T> + } + + return ImmutableArrayMapTypeAdapter( + gson.getAdapter(TypeToken.get(elementType0)), + gson.getAdapter(TypeToken.get(elementType1)) + ) as TypeAdapter<T> + } + + else -> return null + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableListTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableListTypeAdapter.kt new file mode 100644 index 00000000..0db51916 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableListTypeAdapter.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.kstarbound.io.json.factory + +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 + +class ImmutableListTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableList<E>>() { + override fun write(out: JsonWriter, value: ImmutableList<E>) { + if (value.size == 1) { + elementAdapter.write(out, value[0]) + return + } + + out.beginArray() + + for (v in value) { + elementAdapter.write(out, v) + } + + out.endArray() + } + + override fun read(reader: JsonReader): ImmutableList<E> { + if (reader.peek() != JsonToken.BEGIN_ARRAY) { + // не массив, возможно упрощение структуры "a": [value] -> "a": value + return ImmutableList.of(elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")) + } + + reader.beginArray() + + val builder = ImmutableList.Builder<E>() + + while (reader.peek() != JsonToken.END_ARRAY) { + val readObject = elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls") + builder.add(readObject) + } + + reader.endArray() + + return builder.build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt new file mode 100644 index 00000000..58bc816d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.kstarbound.io.json.factory + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableMap +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 + +class ImmutableMapTypeAdapter<V>(val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<String, V>>() { + override fun write(out: JsonWriter, value: ImmutableMap<String, V>) { + out.beginObject() + + for ((k, v) in value) { + out.name(k) + elementAdapter.write(out, v) + } + + out.endObject() + } + + override fun read(reader: JsonReader): ImmutableMap<String, V> { + reader.beginObject() + + val builder = ImmutableMap.Builder<String, V>() + + while (reader.peek() != JsonToken.END_OBJECT) { + builder.put(reader.nextName(), elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}")) + } + + reader.endObject() + return builder.build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableSetTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableSetTypeAdapter.kt new file mode 100644 index 00000000..1cd97dfb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableSetTypeAdapter.kt @@ -0,0 +1,46 @@ +package ru.dbotthepony.kstarbound.io.json.factory + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +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 + +class ImmutableSetTypeAdapter<E>(val elementAdapter: TypeAdapter<E>) : TypeAdapter<ImmutableSet<E>>() { + override fun write(out: JsonWriter, value: ImmutableSet<E>) { + if (value.size == 1) { + elementAdapter.write(out, value.first()) + return + } + + out.beginArray() + + for (v in value) { + elementAdapter.write(out, v) + } + + out.endArray() + } + + override fun read(reader: JsonReader): ImmutableSet<E> { + if (reader.peek() != JsonToken.BEGIN_ARRAY) { + // не массив, возможно упрощение структуры "a": [value] -> "a": value + return ImmutableSet.of(elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls")) + } + + reader.beginArray() + + val builder = ImmutableSet.Builder<E>() + + while (reader.peek() != JsonToken.END_ARRAY) { + val readObject = elementAdapter.read(reader) ?: throw JsonSyntaxException("List does not accept nulls") + builder.add(readObject) + } + + reader.endArray() + + return builder.build() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/LazyTypeProvider.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/LazyTypeProvider.kt index 53aab928..1b79210c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/LazyTypeProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/util/LazyTypeProvider.kt @@ -5,8 +5,8 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.Starbound -class LazyTypeProvider<T : Any?>(private val bound: Class<T>) : TypeAdapter<T>() { - private val resolved by lazy { Starbound.getTypeAdapter(bound) } +class LazyTypeProvider<T : Any?>(private val bound: Class<T>, private val resolver: (Class<T>) -> TypeAdapter<T> = Starbound::getTypeAdapter) : TypeAdapter<T>() { + private val resolved by lazy { resolver(bound) } override fun write(out: JsonWriter, value: T) { resolved.write(out, value)