diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index fe63bb6..03fcfb7 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f6a4583..cdafb20 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c92c679..b1addab 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,25 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '1.9.23' - id 'kr.entree.spigradle' version '2.4.3' - id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.23' + id 'org.jetbrains.kotlin.jvm' version '2.2.0' + id 'io.typst.spigradle' version '3.0.4' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0' id 'com.github.johnrengelman.shadow' version '8.1.1' id 'maven-publish' id 'signing' } group = 'io.typst' -version = '3.0.2' +version = '3.1.0' repositories { mavenCentral() + spigotmc() } dependencies { compileOnly spigot('1.16.5') - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' - testImplementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0' + implementation 'com.charleskorn.kaml:kaml-jvm:0.96.0' + testImplementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0' testImplementation 'org.jetbrains.kotlin:kotlin-test' } @@ -25,18 +27,11 @@ test { useJUnitPlatform() } -compileKotlin { - kotlinOptions.jvmTarget = '1.8' -} - -compileTestKotlin { - kotlinOptions.jvmTarget = '1.8' +kotlin { + jvmToolchain(17) } spigot { - debug { - buildVersion '1.16.5' - } apiVersion '1.16' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1121b15..cf54908 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/BukkitConfigSerializableSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/BukkitConfigSerializableSerializer.kt new file mode 100644 index 0000000..3567cf1 --- /dev/null +++ b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/BukkitConfigSerializableSerializer.kt @@ -0,0 +1,124 @@ +package io.typst.bukkit.kotlin.serialization + +import com.charleskorn.kaml.* +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* +import org.bukkit.configuration.serialization.ConfigurationSerializable +import org.bukkit.configuration.serialization.ConfigurationSerialization + +private val ROOT = YamlPath.root + +object BukkitConfigSerializableSerializer : KSerializer { + override val descriptor: SerialDescriptor = + buildClassSerialDescriptor("BukkitConfigurationSerializable") + + fun serialize(value: ConfigurationSerializable): Map { + val map: MutableMap = value.serialize() + val alias = ConfigurationSerialization.getAlias(value.javaClass) + return map + (ConfigurationSerialization.SERIALIZED_TYPE_KEY to alias) + } + + override fun serialize(encoder: Encoder, value: ConfigurationSerializable) { + val bukkitMap = serialize(value) + when (encoder) { + is JsonEncoder -> encoder.encodeJsonElement(mapToJson(bukkitMap)) + else -> { + val node: YamlNode = mapToYaml(bukkitMap) + encoder.encodeSerializableValue(YamlNode.serializer(), node) + } + } + } + + override fun deserialize(decoder: Decoder): ConfigurationSerializable { + val map: Map = when (decoder) { + is JsonDecoder -> { + val elem = decoder.decodeJsonElement() + (jsonToAny(elem) as Map) + } + + else -> { + val node: YamlNode = decoder.decodeSerializableValue(YamlNode.serializer()) + (yamlToAny(node) as Map) + } + } + return ConfigurationSerialization.deserializeObject(map) + ?: error("Failed to deserialize ConfigurationSerializable (missing '==' alias?)") + } + + // -------------------- JSON <-> Any -------------------- + private fun mapToJson(map: Map): JsonObject = buildJsonObject { + for ((k, v) in map) put(k, anyToJson(v)) + } + + private fun listToJson(list: List<*>): JsonArray = JsonArray(list.map { anyToJson(it) }) + + private fun anyToJson(v: Any?): JsonElement = when (v) { + null -> JsonNull + is JsonElement -> v + is String -> JsonPrimitive(v) + is Boolean -> JsonPrimitive(v) + is Number -> JsonPrimitive(v) + is Map<*, *> -> mapToJson(v as Map) + is List<*> -> listToJson(v) + is ConfigurationSerializable -> mapToJson(serialize(v)) + else -> JsonPrimitive(v.toString()) + } + + private fun jsonToAny(elem: JsonElement): Any? = when (elem) { + is JsonNull -> null + is JsonPrimitive -> when { + elem.isString -> elem.content + elem.booleanOrNull != null -> elem.boolean + elem.longOrNull != null -> if (elem.content.contains('.')) elem.double else elem.long + else -> elem.content + } + + is JsonObject -> elem.mapValues { jsonToAny(it.value) } + is JsonArray -> elem.map { jsonToAny(it) } + } + + // -------------------- YAML(KAML 0.96) <-> Any -------------------- + private fun mapToYaml(map: Map): YamlMap = + YamlMap(map.map { (k, v) -> YamlScalar(k, ROOT) to anyToYaml(v) }.toMap(), ROOT) + + private fun listToYaml(list: List<*>): YamlList = + YamlList(list.map { anyToYaml(it) }, ROOT) + + private fun anyToYaml(v: Any?): YamlNode = when (v) { + null -> YamlNull(ROOT) + is YamlNode -> v + is String -> YamlScalar(v, ROOT) + is Boolean -> YamlScalar(v.toString(), ROOT) // 스칼라는 문자열 컨텐트 + is Number -> YamlScalar(v.toString(), ROOT) + is Map<*, *> -> mapToYaml(v as Map) + is List<*> -> listToYaml(v) + is ConfigurationSerializable -> mapToYaml(serialize(v)) + else -> YamlScalar(v.toString(), ROOT) + } + + private fun yamlToAny(node: YamlNode): Any? = when (node) { + is YamlNull -> null + is YamlScalar -> parseYamlScalar(node.content) + is YamlList -> node.items.map { yamlToAny(it) } + is YamlMap -> node.entries + .mapKeys { (k, _) -> (yamlToAny(k) ?: "").toString() } + .mapValues { (_, v) -> yamlToAny(v) } + + is YamlTaggedNode -> { + yamlToAny(node.innerNode) + } + } + + private fun parseYamlScalar(s: String): Any { + val lower = s.lowercase() + if (lower == "true" || lower == "false") return lower == "true" + val num = s.replace("_", "") + num.toLongOrNull()?.let { return it } + num.toDoubleOrNull()?.let { return it } + return s + } +} diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigAnySerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigAnySerializer.kt deleted file mode 100644 index 695aba8..0000000 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigAnySerializer.kt +++ /dev/null @@ -1,95 +0,0 @@ -package io.typst.bukkit.kotlin.serialization - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.* -import org.bukkit.configuration.serialization.ConfigurationSerializable -import org.bukkit.configuration.serialization.ConfigurationSerialization - -@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) -internal object ConfigAnySerializer : KSerializer { - override val descriptor: SerialDescriptor = - buildSerialDescriptor("BukkitConfigAny", PolymorphicKind.OPEN) - - override fun deserialize(decoder: Decoder): Any? { - // TODO: use decodeStructure instead of decodeJsonElement - val jsonDecoder = decoder as? JsonDecoder ?: return null - val element = jsonDecoder.decodeJsonElement() - try { - return exactValue(element) - } catch (ex: SerializationException) { - throw SerializationException("Error while deserializing: $element", ex) - } - } - - private fun exactValue(x: JsonElement): Any? { - return when (x) { - is JsonPrimitive -> - if (x.isString) { - x.content - } else x.intOrNull ?: x.longOrNull ?: x.doubleOrNull ?: x.booleanOrNull - ?: throw SerializationException("Illegal input: $x") - - JsonNull -> null - is JsonArray -> x.map(::exactValue) - is JsonObject -> { - val map = x.map { (k, v) -> - k to exactValue(v) - }.toMap() - if (map.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { - ConfigurationSerialization.deserializeObject(map) - } else map - } - } - } - - override fun serialize(encoder: Encoder, value: Any?) { - if (value == null) { - encoder.encodeNull() - return - } - val roundedValue = roundValue(value) - val serializer = findSerializer(roundedValue) - encoder.encodeSerializableValue(serializer, roundedValue) - } - - private fun roundValue(x: Any): Any = - if (x is Number) { - if (x is Double || x is Float) { - x.toDouble() - } else x.toLong() - } else if (x is StringBuilder || x is StringBuffer) { - x.toString() - } else if (x is Collection<*>) { - x.toList() - } else if (x is Map<*, *>) { - LinkedHashMap(x) - } else x - - @Suppress("UNCHECKED_CAST") - private fun findSerializer(x: Any): KSerializer = - when (x) { - is Boolean -> Boolean.serializer() - is Double -> Double.serializer() - is Long -> Long.serializer() - is String -> String.serializer() - is Map<*, *> -> - if (x.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { - ConfigSerializableSerializer - } else MapSerializer(String.serializer(), ConfigAnySerializer) - - is Collection<*> -> ListSerializer(ConfigAnySerializer) - is ConfigurationSerializable -> ConfigSerializableSerializer - else -> throw IllegalArgumentException("Unknown type ${x.javaClass.name} $x") - } as KSerializer -} diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigSerializableSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigSerializableSerializer.kt deleted file mode 100644 index 0d050c0..0000000 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ConfigSerializableSerializer.kt +++ /dev/null @@ -1,35 +0,0 @@ -package io.typst.bukkit.kotlin.serialization - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.bukkit.configuration.serialization.ConfigurationSerializable -import org.bukkit.configuration.serialization.ConfigurationSerialization - -object ConfigSerializableSerializer : KSerializer { - private val mapSerializer: KSerializer> = - MapSerializer( - String.serializer(), - ConfigAnySerializer - ) - override val descriptor: SerialDescriptor - get() = mapSerializer.descriptor - - override fun serialize(encoder: Encoder, value: ConfigurationSerializable) { - val map = value.serialize() - val alias = ConfigurationSerialization.getAlias(value.javaClass) - return mapSerializer.serialize( - encoder, - map + (ConfigurationSerialization.SERIALIZED_TYPE_KEY to alias) - ) - } - - override fun deserialize(decoder: Decoder): ConfigurationSerializable { - val map = mapSerializer.deserialize(decoder) - return ConfigurationSerialization.deserializeObject(map) - ?: throw IllegalStateException(map.toString()) - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ItemStackSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ItemStackSerializer.kt index 943a9b0..6629caa 100644 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ItemStackSerializer.kt +++ b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/ItemStackSerializer.kt @@ -1,4 +1,3 @@ - package io.typst.bukkit.kotlin.serialization import kotlinx.serialization.KSerializer @@ -10,15 +9,14 @@ import org.bukkit.inventory.ItemStack typealias ItemStackSerializable = @Serializable(ItemStackSerializer::class) ItemStack -class ItemStackSerializer : KSerializer { - override val descriptor: SerialDescriptor - get() = ConfigSerializableSerializer.descriptor +object ItemStackSerializer : KSerializer { + override val descriptor: SerialDescriptor = BukkitConfigSerializableSerializer.descriptor - override fun serialize(encoder: Encoder, value: ItemStack) = - encoder.encodeSerializableValue(ConfigSerializableSerializer, value) + override fun serialize(encoder: Encoder, value: ItemStack) { + BukkitConfigSerializableSerializer.serialize(encoder, value) + } override fun deserialize(decoder: Decoder): ItemStack { - val serializable = decoder.decodeSerializableValue(ConfigSerializableSerializer) - return serializable as ItemStack + return BukkitConfigSerializableSerializer.deserialize(decoder) as ItemStack } } diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/LocationSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/LocationSerializer.kt deleted file mode 100644 index 3dd2486..0000000 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/LocationSerializer.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.typst.bukkit.kotlin.serialization - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.bukkit.Location - -@Deprecated("Use WorldLocation instead, this might causes issue with world manager plugins.") -typealias LocationSerializable = @Serializable(LocationSerializer::class) Location - -@Deprecated("Use WorldLocation instead, this might causes issue with world manager plugins.") -class LocationSerializer : KSerializer { - override val descriptor: SerialDescriptor - get() = ConfigSerializableSerializer.descriptor - - override fun deserialize(decoder: Decoder): Location { - val serializable = decoder.decodeSerializableValue(ConfigSerializableSerializer) - return serializable as Location - } - - override fun serialize(encoder: Encoder, value: Location) = - encoder.encodeSerializableValue(ConfigSerializableSerializer, value) -} diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/UUIDSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/UUIDSerializer.kt index 5636bae..14c10a5 100644 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/UUIDSerializer.kt +++ b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/UUIDSerializer.kt @@ -18,8 +18,7 @@ object UUIDSerializer : KSerializer { return UUID.fromString(decoder.decodeString()) } - override fun serialize(encoder: Encoder, value: UUID) { encoder.encodeString(value.toString()) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/VectorSerializer.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/VectorSerializer.kt index c6589a2..e054d5b 100644 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/VectorSerializer.kt +++ b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/VectorSerializer.kt @@ -2,6 +2,8 @@ package io.typst.bukkit.kotlin.serialization import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @@ -10,14 +12,18 @@ import org.bukkit.util.Vector typealias VectorSerializable = @Serializable(VectorSerializer::class) Vector class VectorSerializer : KSerializer { - override val descriptor: SerialDescriptor - get() = ConfigSerializableSerializer.descriptor + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("org.bukkit.util.Vector", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: Vector) = - encoder.encodeSerializableValue(ConfigSerializableSerializer, value) + encoder.encodeString("${value.x},${value.y},${value.z}") override fun deserialize(decoder: Decoder): Vector { - val serializable = decoder.decodeSerializableValue(ConfigSerializableSerializer) - return serializable as Vector + val pieces = decoder.decodeString().split(",") + return Vector( + pieces[0].toDouble(), + pieces[1].toDouble(), + pieces[2].toDouble() + ) } } diff --git a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/plugins.kt b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/plugins.kt index 4f79d70..9bdcf50 100644 --- a/src/main/kotlin/io/typst/bukkit/kotlin/serialization/plugins.kt +++ b/src/main/kotlin/io/typst/bukkit/kotlin/serialization/plugins.kt @@ -1,27 +1,44 @@ package io.typst.bukkit.kotlin.serialization +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlConfiguration +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import org.bukkit.plugin.java.JavaPlugin import java.io.File val JavaPlugin.configJsonFile: File get() = File(dataFolder, "config.json") +val JavaPlugin.configYamlFile: File get() = File(dataFolder, "config.yml") val bukkitPluginJson: Json by lazy { Json { encodeDefaults = true prettyPrint = true + ignoreUnknownKeys = true } } +val bukkitPluginYaml: Yaml by lazy { + Yaml(configuration = YamlConfiguration(strictMode = false)) +} -inline fun JavaPlugin.readConfigOrCreate(defaultValue: () -> A? = { null }): A { - val configFile = configJsonFile +inline fun JavaPlugin.readConfigOrCreate(defaultValue: () -> A? = { null }, jsonOrYaml: Boolean = true): A { + val configFile = if (jsonOrYaml) { + configJsonFile + } else configYamlFile + val serialFormat = if (jsonOrYaml) { + bukkitPluginJson + } else bukkitPluginYaml if (configFile.isFile) { - return bukkitPluginJson.decodeFromString(configJsonFile.readText()) + if (jsonOrYaml) { + return serialFormat.decodeFromString(configFile.readText()) + } else { + return serialFormat.decodeFromString(configFile.readText()) + } } else { configFile.parentFile.mkdirs() - val defValue = defaultValue() ?: bukkitPluginJson.decodeFromString("{}") - configFile.writeText(bukkitPluginJson.encodeToString(defValue)) + val defValue = defaultValue() ?: serialFormat.decodeFromString("{}") + configFile.writeText(serialFormat.encodeToString(defValue)) return defValue } }