diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/addon/RebarAddon.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/addon/RebarAddon.kt index b74955762..05aad638d 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/addon/RebarAddon.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/addon/RebarAddon.kt @@ -66,7 +66,7 @@ interface RebarAddon : Keyed { @ApiStatus.NonExtendable fun registerWithRebar() { if (!Bukkit.getPluginManager().isPluginEnabled("Rebar")) { - throw IllegalStateException("Rebar is not installed or not enabled") + throw IllegalStateException("Rebar is not installed or not enabled (if Rebar is installed, has it errored?)") } RebarRegistry.ADDONS.register(this) diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/Config.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/Config.kt index c2b40f78b..073a54325 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/Config.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/Config.kt @@ -27,4 +27,16 @@ class Config( fun save() { internalConfig.save(file) } + + override fun modifyException(exception: Exception): Exception = when (exception) { + is KeyNotFoundException -> KeyNotFoundException( + exception.key, + "Key '${exception.key}' not found in config file ${file.absolutePath}" + ) + + else -> RuntimeException( + "An error occurred while reading config file ${file.absolutePath} (CHECK THE SUB-EXCEPTION BEFORE REPORTING)", + exception + ) + } } \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/ConfigSection.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/ConfigSection.kt index 294482643..6ca113c9d 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/ConfigSection.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/ConfigSection.kt @@ -49,7 +49,7 @@ open class ConfigSection(val internalSection: ConfigurationSection) { } fun getSectionOrThrow(key: String): ConfigSection = - getSection(key) ?: throw KeyNotFoundException(internalSection.currentPath, key) + getSection(key) ?: throw modifyException(KeyNotFoundException(getKeyPath(key))) /** * Returns null if the key does not exist or if the value cannot be converted to the desired type. @@ -70,13 +70,19 @@ open class ConfigSection(val internalSection: ConfigurationSection) { */ fun getOrThrow(key: String, adapter: ConfigAdapter): T { val value = cache.getOrPut(key) { - val value = internalSection.get(key) ?: throw KeyNotFoundException(internalSection.currentPath, key) + val value = internalSection.get(key) ?: throw modifyException(KeyNotFoundException(getKeyPath(key))) try { adapter.convert(value) + } catch (e: KeyNotFoundException) { + val exception = modifyException(KeyNotFoundException("$key.${e.key.removePrefix("$key.")}")) + exception.stackTrace = e.stackTrace + throw exception } catch (e: Exception) { - throw IllegalArgumentException( - "Failed to convert value '$value' to type ${adapter.type} for key '$key' in section '${internalSection.currentPath}'", - e + throw modifyException( + IllegalArgumentException( + "Failed to convert value '$value' to type ${adapter.type} for key '${getKeyPath(key)}'", + e + ) ) } } @@ -84,7 +90,7 @@ open class ConfigSection(val internalSection: ConfigurationSection) { fun getClass(type: Type): Class<*> = when (type) { is Class<*> -> type is ParameterizedType -> getClass(type.rawType) - else -> throw IllegalArgumentException("Unsupported type: $type") + else -> throw modifyException(IllegalArgumentException("Unsupported type: $type")) } @Suppress("UNCHECKED_CAST") @@ -97,8 +103,8 @@ open class ConfigSection(val internalSection: ConfigurationSection) { cache.remove(key) } - fun createSection(key: String): ConfigSection - = ConfigSection(internalSection.createSection(key)).also { sectionCache[key] = it } + fun createSection(key: String): ConfigSection = + ConfigSection(internalSection.createSection(key)).also { sectionCache[key] = it } /** * 'Merges' [other] with this ConfigSection by copying all of its keys into this ConfigSection. @@ -120,9 +126,17 @@ open class ConfigSection(val internalSection: ConfigurationSection) { } } + private fun getKeyPath(key: String): String = + if (internalSection.currentPath.isNullOrEmpty()) key else "${internalSection.currentPath}.$key" + + /** + * This exists so that [Config] can add more context to exceptions thrown by this class without having to override every method. + * The default implementation is just `throw exception`. + */ + protected open fun modifyException(exception: Exception): Exception = exception + /** * Thrown when a key is not found. */ - class KeyNotFoundException(path: String?, key: String) : - Exception(if (!path.isNullOrEmpty()) "Config key not found: $path.$key" else "Config key not found: $key") + class KeyNotFoundException(val key: String, message: String = "Config key not found: $key") : RuntimeException(message) } \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigAdapter.kt index 7c57f66ac..264c4c3f3 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigAdapter.kt @@ -96,6 +96,7 @@ interface ConfigAdapter { @JvmField val WEIGHTED_SET = WeightedSetConfigAdapter @JvmField val CULLING_PRESET = CullingPresetConfigAdapter @JvmField val WAILA_DISPLAY = WailaDisplayConfigAdapter + @JvmField val CONFIG_SECTION = ConfigSectionConfigAdapter // @formatter:on } } diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigSectionConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigSectionConfigAdapter.kt new file mode 100644 index 000000000..c9356ee47 --- /dev/null +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigSectionConfigAdapter.kt @@ -0,0 +1,23 @@ +package io.github.pylonmc.rebar.config.adapter + +import io.github.pylonmc.rebar.config.ConfigSection +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.MemoryConfiguration + +object ConfigSectionConfigAdapter : ConfigAdapter { + + override val type = ConfigSection::class.java + + override fun convert(value: Any): ConfigSection { + val section = if (value is ConfigurationSection) { + value + } else { + val memoryConfig = MemoryConfiguration() + for ((key, value) in MapConfigAdapter.STRING_TO_ANY.convert(value)) { + memoryConfig.set(key, value) + } + memoryConfig + } + return ConfigSection(section) + } +} \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/CullingPresetConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/CullingPresetConfigAdapter.kt index f58489ff3..6599821e6 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/CullingPresetConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/CullingPresetConfigAdapter.kt @@ -7,17 +7,17 @@ object CullingPresetConfigAdapter : ConfigAdapter { override val type: Type = CullingPreset::class.java override fun convert(value: Any): CullingPreset { - val map = MapConfigAdapter.STRING_TO_ANY.convert(value) + val section = ConfigAdapter.CONFIG_SECTION.convert(value) return CullingPreset( - index = ConfigAdapter.INT.convert(map["index"] ?: throw IllegalArgumentException("Culling preset is missing 'index' field")), - id = ConfigAdapter.STRING.convert(map["id"] ?: throw IllegalArgumentException("Culling preset is missing 'id' field")), - material = ConfigAdapter.MATERIAL.convert(map["material"] ?: throw IllegalArgumentException("Culling preset is missing 'material' field")), - updateInterval = ConfigAdapter.INT.convert(map["update-interval"] ?: throw IllegalArgumentException("Culling preset is missing 'update-interval' field")), - hiddenInterval = map["hidden-interval"]?.let { ConfigAdapter.INT.convert(it) } ?: 1, - visibleInterval = map["visible-interval"]?.let { ConfigAdapter.INT.convert(it) } ?: 20, - alwaysShowRadius = map["always-show-radius"]?.let { ConfigAdapter.INT.convert(it) } ?: 16, - cullRadius = map["cull-radius"]?.let { ConfigAdapter.INT.convert(it) } ?: 64, - maxOccludingCount = map["max-occluding-count"]?.let { ConfigAdapter.INT.convert(it) } ?: 3 + index = section.getOrThrow("index", ConfigAdapter.INT), + id = section.getOrThrow("id", ConfigAdapter.STRING), + material = section.getOrThrow("material", ConfigAdapter.MATERIAL), + updateInterval = section.getOrThrow("update-interval", ConfigAdapter.INT), + hiddenInterval = section.get("hidden-interval", ConfigAdapter.INT, 1), + visibleInterval = section.get("visible-interval", ConfigAdapter.INT, 20), + alwaysShowRadius = section.get("always-show-radius", ConfigAdapter.INT, 16), + cullRadius = section.get("cull-radius", ConfigAdapter.INT, 64), + maxOccludingCount = section.get("max-occluding-count", ConfigAdapter.INT, 3) ) } } \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/RandomizedSoundConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/RandomizedSoundConfigAdapter.kt index ddc8eaec4..13207a095 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/RandomizedSoundConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/RandomizedSoundConfigAdapter.kt @@ -1,5 +1,6 @@ package io.github.pylonmc.rebar.config.adapter +import io.github.pylonmc.rebar.config.ConfigSection import io.github.pylonmc.rebar.util.RandomizedSound import net.kyori.adventure.key.Key import net.kyori.adventure.sound.Sound @@ -10,40 +11,36 @@ object RandomizedSoundConfigAdapter : ConfigAdapter { override val type: Type = RandomizedSound::class.java override fun convert(value: Any): RandomizedSound { - val map = MapConfigAdapter.STRING_TO_ANY.convert(value) + val section = ConfigAdapter.CONFIG_SECTION.convert(value) val keys = mutableListOf() - if (map.containsKey("sound")) { - keys.add(Key.key(map["sound"]!! as String)) - } else if (map.containsKey("sounds")) { - for (element in map["sounds"] as List<*>) { - keys.add(Key.key(element as String) ) - } - } else { - throw IllegalArgumentException("No 'sound' or 'sounds' field found in section: $value") + section.get("sound", ConfigAdapter.NAMESPACED_KEY)?.let(keys::add) + section.get("sounds", ConfigAdapter.LIST.from(ConfigAdapter.NAMESPACED_KEY))?.let(keys::addAll) + + if (keys.isEmpty()) { + section.get("sound", ConfigAdapter.STRING) // will report the error + throw AssertionError() } return RandomizedSound( keys, - ConfigAdapter.ENUM.from().convert(map["source"] ?: throw IllegalArgumentException("Sound is missing 'source' field")), - getRange(map, "volume"), - getRange(map, "pitch") + section.getOrThrow("source", ConfigAdapter.ENUM.from()), + getRange(section, "volume"), + getRange(section, "pitch") ) } - private fun getRange(section: Map, key: String): Pair { - val range = section[key] ?: throw IllegalArgumentException("Sound is missing '$key' field") - if (range is ConfigurationSection || range is Map<*, *>) { - val range = MapConfigAdapter.STRING_TO_ANY.convert(range) - return Pair( - ConfigAdapter.DOUBLE.convert(range["min"] ?: throw IllegalArgumentException("Sound is missing '$key.min' field")), - ConfigAdapter.DOUBLE.convert(range["max"] ?: throw IllegalArgumentException("Sound is missing '$key.max' field")) - ) - } else if (range is List<*>) { - return Pair(ConfigAdapter.DOUBLE.convert(range[0]!!), ConfigAdapter.DOUBLE.convert(range[1]!!)) - } else { - try { + private fun getRange(section: ConfigSection, key: String): Pair { + return when (val range = section.getOrThrow(key, ConfigAdapter.ANY)) { + is ConfigurationSection, is Map<*, *> -> { + val range = ConfigAdapter.CONFIG_SECTION.convert(range) + range.getOrThrow("min", ConfigAdapter.DOUBLE) to range.getOrThrow("max", ConfigAdapter.DOUBLE) + } + + is List<*> -> ConfigAdapter.DOUBLE.convert(range[0]!!) to ConfigAdapter.DOUBLE.convert(range[1]!!) + + else -> try { val value = range.toString().toDouble() - return Pair(value, value) + Pair(value, value) } catch (_: Throwable) { throw IllegalArgumentException("Sound '$key' field is not a valid number or range: $range") } diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/SoundConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/SoundConfigAdapter.kt index f9444abc9..3c22f5278 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/SoundConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/SoundConfigAdapter.kt @@ -1,19 +1,18 @@ package io.github.pylonmc.rebar.config.adapter -import net.kyori.adventure.key.Key -import net.kyori.adventure.sound.Sound +import net.kyori.adventure.sound.Sound import java.lang.reflect.Type object SoundConfigAdapter : ConfigAdapter { override val type: Type = Sound::class.java override fun convert(value: Any): Sound { - val map = MapConfigAdapter.STRING_TO_ANY.convert(value) + val section = ConfigAdapter.CONFIG_SECTION.convert(value) return Sound.sound( - Key.key(map["name"] as String), - ConfigAdapter.ENUM.from().convert(map["source"] ?: throw IllegalArgumentException("Sound is missing 'source' field")), - ConfigAdapter.FLOAT.convert(map["volume"] ?: throw IllegalArgumentException("Sound is missing 'volume' field")), - ConfigAdapter.FLOAT.convert(map["pitch"] ?: throw IllegalArgumentException("Sound is missing 'pitch' field")) + section.getOrThrow("name", ConfigAdapter.NAMESPACED_KEY), + section.getOrThrow("source", ConfigAdapter.ENUM.from()), + section.getOrThrow("volume", ConfigAdapter.FLOAT), + section.getOrThrow("pitch", ConfigAdapter.FLOAT) ) } } \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WailaDisplayConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WailaDisplayConfigAdapter.kt index 926557ace..b842f8ca4 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WailaDisplayConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WailaDisplayConfigAdapter.kt @@ -8,11 +8,12 @@ object WailaDisplayConfigAdapter : ConfigAdapter { override val type = WailaDisplay::class.java override fun convert(value: Any): WailaDisplay { - val map = MapConfigAdapter.STRING_TO_ANY.convert(value) - val text = Component.translatable(ConfigAdapter.STRING.convert(map["text"] ?: throw IllegalArgumentException("WailaDisplay is missing 'text' field"))) - val color = ConfigAdapter.ENUM.from().convert(map["color"] ?: throw IllegalArgumentException("WailaDisplay is missing 'color' field")) - val overlay = ConfigAdapter.ENUM.from().convert(map["overlay"] ?: throw IllegalArgumentException("WailaDisplay is missing 'overlay' field")) - val progress = ConfigAdapter.FLOAT.convert(map["progress"] ?: throw IllegalArgumentException("WailaDisplay is missing 'progress' field")) - return WailaDisplay(text, color, overlay, progress) + val section = ConfigAdapter.CONFIG_SECTION.convert(value) + return WailaDisplay( + text = Component.translatable(section.getOrThrow("text", ConfigAdapter.STRING)), + color = section.getOrThrow("color", ConfigAdapter.ENUM.from()), + overlay = section.getOrThrow("overlay", ConfigAdapter.ENUM.from()), + progress = section.getOrThrow("progress", ConfigAdapter.FLOAT) + ) } } \ No newline at end of file diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WeightedSetConfigAdapter.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WeightedSetConfigAdapter.kt index d8026811a..360af742b 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WeightedSetConfigAdapter.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/WeightedSetConfigAdapter.kt @@ -11,9 +11,9 @@ class WeightedSetConfigAdapter(private val elementAdapter: ConfigAdapter) override fun convert(value: Any): WeightedSet { return if (value is List<*>) { value.mapTo(WeightedSet()) { - val map = MapConfigAdapter.STRING_TO_ANY.convert(it!!) - val element = elementAdapter.convert(map["value"] ?: throw IllegalArgumentException("Missing 'value' key in weighted set element")) - val weight = ConfigAdapter.FLOAT.convert(map["weight"] ?: 1f) + val section = ConfigAdapter.CONFIG_SECTION.convert(it!!) + val element = section.getOrThrow("value", elementAdapter) + val weight = section.get("weight", ConfigAdapter.FLOAT, 1f) WeightedSet.Element(element, weight) } } else { diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/fluid/RebarFluid.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/fluid/RebarFluid.kt index b43309eff..561117093 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/fluid/RebarFluid.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/fluid/RebarFluid.kt @@ -60,7 +60,7 @@ open class RebarFluid( for (locale in addon.languages) { val translationKey = "${key.namespace}.fluid.${key.key}" if (!addon.translator.canTranslate(translationKey, locale)) { - Rebar.logger.warning("${key.namespace} is missing a translation key for fluid ${key.key} (locale: ${locale.displayName} | expected translation key: $translationKey") + Rebar.logger.warning("${key.namespace} is missing a translation key for fluid ${key.key} (locale: ${locale.displayName} | expected translation key: $translationKey)") } } } diff --git a/rebar/src/main/kotlin/io/github/pylonmc/rebar/item/RebarItem.kt b/rebar/src/main/kotlin/io/github/pylonmc/rebar/item/RebarItem.kt index 4452a488a..2b6689a60 100644 --- a/rebar/src/main/kotlin/io/github/pylonmc/rebar/item/RebarItem.kt +++ b/rebar/src/main/kotlin/io/github/pylonmc/rebar/item/RebarItem.kt @@ -95,7 +95,7 @@ open class RebarItem(val stack: ItemStack) : Keyed { ItemStackBuilder.nameKey( schema.key ) - }" + })" ) } }