From e01afbefe7162e4acf11e45ff015c94b63d31033 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Thu, 9 Feb 2023 19:30:52 +0700
Subject: [PATCH] =?UTF-8?q?=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=BD=D0=B8?=
 =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=D0=B5=20=D0=B0=D0=B4=D0=B0?=
 =?UTF-8?q?=D0=BF=D1=82=D0=B5=D1=80=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../ru/dbotthepony/kstarbound/Starbound.kt    | 19 ++----
 .../io/json/InternedJsonElementAdapter.kt     | 63 +++++++++++++++++++
 .../ImmutableCollectionAdapterFactory.kt      |  5 +-
 .../json/factory/ImmutableMapTypeAdapter.kt   | 20 +++++-
 4 files changed, 88 insertions(+), 19 deletions(-)
 create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt

diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
index 984b0561..eaf9505b 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
@@ -42,6 +42,8 @@ import ru.dbotthepony.kstarbound.io.*
 import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter
 import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
 import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter
+import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter
+import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter
 import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
 import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
 import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
@@ -105,25 +107,14 @@ class Starbound : ISBFileLocator {
 		setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
 		setPrettyPrinting()
 
-		// чтоб строки всегда intern'ились
-		registerTypeAdapter(object : TypeAdapter<String>() {
-			override fun write(out: JsonWriter, value: String?) {
-				if (value == null)
-					out.nullValue()
-				else
-					out.value(value)
-			}
-
-			override fun read(`in`: JsonReader): String? {
-				return stringInterner.intern(TypeAdapters.STRING.read(`in`) ?: return null)
-			}
-		})
+		registerTypeAdapter(InternedStringAdapter(stringInterner))
+		registerTypeAdapter(InternedJsonElementAdapter(stringInterner))
 
 		// Обработчик @JsonImplementation
 		registerTypeAdapterFactory(JsonImplementationTypeFactory)
 
 		// ImmutableList, ImmutableSet, ImmutableMap
-		registerTypeAdapterFactory(ImmutableCollectionAdapterFactory)
+		registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(stringInterner))
 
 		// ArrayList
 		registerTypeAdapterFactory(ArrayListAdapterFactory)
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt
new file mode 100644
index 00000000..362c006e
--- /dev/null
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/InternedJsonElementAdapter.kt
@@ -0,0 +1,63 @@
+package ru.dbotthepony.kstarbound.io.json
+
+import com.google.common.collect.Interner
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonNull
+import com.google.gson.JsonObject
+import com.google.gson.JsonPrimitive
+import com.google.gson.TypeAdapter
+import com.google.gson.internal.LazilyParsedNumber
+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
+
+class InternedJsonElementAdapter(val stringInterner: Interner<String>) : TypeAdapter<JsonElement>() {
+	private val _true = JsonPrimitive(true)
+	private val _false = JsonPrimitive(false)
+
+	override fun write(out: JsonWriter, value: JsonElement?) {
+		return TypeAdapters.JSON_ELEMENT.write(out, value)
+	}
+
+	override fun read(`in`: JsonReader): JsonElement? {
+		return when (val p = `in`.peek()) {
+			JsonToken.STRING -> JsonPrimitive(stringInterner.intern(`in`.nextString()))
+			JsonToken.NUMBER -> JsonPrimitive(LazilyParsedNumber(`in`.nextString()))
+			JsonToken.BOOLEAN -> if (`in`.nextBoolean()) _true else _false
+			JsonToken.NULL -> JsonNull.INSTANCE
+			JsonToken.BEGIN_ARRAY -> {
+				val output = JsonArray()
+				`in`.beginArray()
+				while (`in`.hasNext()) { output.add(read(`in`)) }
+				`in`.endArray()
+				output
+			}
+			JsonToken.BEGIN_OBJECT -> {
+				val output = JsonObject()
+				`in`.beginObject()
+				while (`in`.hasNext()) { output.add(stringInterner.intern(`in`.nextName()), read(`in`)) }
+				`in`.endObject()
+				output
+			}
+			else -> throw IllegalArgumentException(p.toString())
+		}
+	}
+}
+
+class InternedStringAdapter(val stringInterner: Interner<String>) : TypeAdapter<String>() {
+	override fun write(out: JsonWriter, value: String?) {
+		out.value(value)
+	}
+
+	override fun read(`in`: JsonReader): String? {
+		if (`in`.peek() == JsonToken.NULL)
+			return null
+
+		if (`in`.peek() == JsonToken.BOOLEAN)
+			return `in`.nextBoolean().toString()
+
+		return stringInterner.intern(`in`.nextString())
+	}
+}
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
index 2173200d..28beb9e0 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableCollectionAdapterFactory.kt
@@ -3,6 +3,7 @@ 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.common.collect.Interner
 import com.google.gson.Gson
 import com.google.gson.TypeAdapter
 import com.google.gson.TypeAdapterFactory
@@ -10,7 +11,7 @@ import com.google.gson.internal.`$Gson$Types`
 import com.google.gson.reflect.TypeToken
 
 @Suppress("unchecked_cast")
-object ImmutableCollectionAdapterFactory : TypeAdapterFactory {
+class ImmutableCollectionAdapterFactory(val stringInterner: Interner<String> = Interner { it }) : TypeAdapterFactory {
 	override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
 		when (type.rawType) {
 			ImmutableList::class.java -> {
@@ -27,7 +28,7 @@ object ImmutableCollectionAdapterFactory : TypeAdapterFactory {
 				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 ImmutableMapTypeAdapter(stringInterner, gson.getAdapter(TypeToken.get(elementType1))) as TypeAdapter<T>
 				}
 
 				return ImmutableArrayMapTypeAdapter(
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
index 6614a6a7..e6b02f7b 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/factory/ImmutableMapTypeAdapter.kt
@@ -2,13 +2,14 @@ package ru.dbotthepony.kstarbound.io.json.factory
 
 import com.google.common.collect.ImmutableList
 import com.google.common.collect.ImmutableMap
+import com.google.common.collect.Interner
 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>>() {
+class ImmutableMapTypeAdapter<V>(val stringInterner: Interner<String>, val elementAdapter: TypeAdapter<V>) : TypeAdapter<ImmutableMap<String, V>>() {
 	override fun write(out: JsonWriter, value: ImmutableMap<String, V>?) {
 		if (value == null) {
 			out.nullValue()
@@ -29,12 +30,25 @@ class ImmutableMapTypeAdapter<V>(val elementAdapter: TypeAdapter<V>) : TypeAdapt
 		if (reader.peek() == JsonToken.NULL)
 			return null
 
+		if (reader.peek() == JsonToken.BEGIN_ARRAY) {
+			val builder = ImmutableMap.Builder<String, V>()
+
+			while (reader.peek() !== JsonToken.END_ARRAY) {
+				builder.put(
+					stringInterner.intern(reader.nextString()),
+					elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}")
+				)
+			}
+
+			return builder.build()
+		}
+
 		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}"))
+		while (reader.peek() !== JsonToken.END_OBJECT) {
+			builder.put(stringInterner.intern(reader.nextName()), elementAdapter.read(reader) ?: throw JsonSyntaxException("Nulls are not allowed, near ${reader.path}"))
 		}
 
 		reader.endObject()