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 private constructor( var mustBePresent: Boolean? = null } - class Builder(val factory: () -> T, vararg fields: KMutableProperty1) { + class Builder(val factory: () -> T, vararg fields: KMutableProperty1) : TypeAdapterFactory { private val properties = ArrayList>() private val flatProperties = ArrayList>() private val ignoreKeys = ObjectArraySet() var extraPropertiesAreFatal = false var logMisses: Boolean? = null + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + 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 private constructor( /** * Позволяет построить класс [FactoryAdapter] на основе заданных параметров */ - class Builder(val clazz: KClass) { + class Builder(val clazz: KClass) : TypeAdapterFactory { constructor(clazz: KClass, vararg fields: KProperty1) : this(clazz) { for (field in fields) { auto(field) @@ -391,6 +394,14 @@ class FactoryAdapter private constructor( private val types = ArrayList>() + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType == clazz.java) { + return build() as TypeAdapter + } + + 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(val keyAdapter: TypeAdapter, val elementAdapter: TypeAdapter) : TypeAdapter>() { + override fun write(out: JsonWriter, value: ImmutableMap) { + out.beginArray() + + for ((k, v) in value) { + keyAdapter.write(out, k) + elementAdapter.write(out, v) + } + + out.endArray() + } + + override fun read(reader: JsonReader): ImmutableMap { + reader.beginArray() + + val builder = ImmutableMap.Builder() + + 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 create(gson: Gson, type: TypeToken): TypeAdapter? { + when (type.rawType) { + ImmutableList::class.java -> { + val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) + return ImmutableListTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter + } + + ImmutableSet::class.java -> { + val elementType = `$Gson$Types`.getCollectionElementType(type.type, type.rawType) + return ImmutableSetTypeAdapter(gson.getAdapter(TypeToken.get(elementType))) as TypeAdapter + } + + 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 + } + + return ImmutableArrayMapTypeAdapter( + gson.getAdapter(TypeToken.get(elementType0)), + gson.getAdapter(TypeToken.get(elementType1)) + ) as TypeAdapter + } + + 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(val elementAdapter: TypeAdapter) : TypeAdapter>() { + override fun write(out: JsonWriter, value: ImmutableList) { + 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 { + 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() + + 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(val elementAdapter: TypeAdapter) : TypeAdapter>() { + override fun write(out: JsonWriter, value: ImmutableMap) { + out.beginObject() + + for ((k, v) in value) { + out.name(k) + elementAdapter.write(out, v) + } + + out.endObject() + } + + override fun read(reader: JsonReader): ImmutableMap { + reader.beginObject() + + val builder = ImmutableMap.Builder() + + 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(val elementAdapter: TypeAdapter) : TypeAdapter>() { + override fun write(out: JsonWriter, value: ImmutableSet) { + 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 { + 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() + + 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(private val bound: Class) : TypeAdapter() { - private val resolved by lazy { Starbound.getTypeAdapter(bound) } +class LazyTypeProvider(private val bound: Class, private val resolver: (Class) -> TypeAdapter = Starbound::getTypeAdapter) : TypeAdapter() { + private val resolved by lazy { resolver(bound) } override fun write(out: JsonWriter, value: T) { resolved.write(out, value)