Config delegation & reload improvements

- Added a class and an interface to make using list config data easier
- Made everything config-related reusable and reloadable because delegated properties don't update automatically when reassigned
- This also means that the original ConfigData instance should also properly update now when reloading, although now we return the delegate
This commit is contained in:
Norbi Peti 2023-06-18 20:16:12 +02:00
parent a26c16565f
commit 58729ab885
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
8 changed files with 61 additions and 50 deletions

View file

@ -30,7 +30,7 @@ class ComponentCommand : ICommand2MC() {
@Subcommand(helpText = ["Enable component", "Temporarily or permanently enables a component."]) @Subcommand(helpText = ["Enable component", "Temporarily or permanently enables a component."])
fun enable(sender: CommandSender, plugin: Plugin, component: String, @OptionalArg permanent: Boolean): Boolean { fun enable(sender: CommandSender, plugin: Plugin, component: String, @OptionalArg permanent: Boolean): Boolean {
if (plugin is ButtonPlugin) { if (plugin is ButtonPlugin) {
if (!plugin.justReload()) { if (!plugin.tryReloadConfig()) {
sender.sendMessage("${ChatColor.RED}Couldn't reload config, check console.") sender.sendMessage("${ChatColor.RED}Couldn't reload config, check console.")
return true return true
} }

View file

@ -16,10 +16,14 @@ import java.util.function.Consumer
@HasConfig(global = true) @HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() { abstract class ButtonPlugin : JavaPlugin() {
protected var iConfig = getIConfigInstance() protected val iConfig = IHaveConfig(::saveConfig, section)
private set
private var yaml: YamlConfiguration? = null private var yaml: YamlConfiguration? = null
/**
* May change if the config is reloaded
*/
private val section get() = config.getConfigurationSection("global") ?: config.createSection("global")
protected val data //TODO protected val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
@ -39,7 +43,7 @@ abstract class ButtonPlugin : JavaPlugin() {
*/ */
protected open fun pluginPreDisable() {} protected open fun pluginPreDisable() {}
override fun onEnable() { override fun onEnable() {
if (!reloadIConfig()) { if (!tryReloadConfig()) {
logger.warning("Please fix the issues and restart the server to load the plugin.") logger.warning("Please fix the issues and restart the server to load the plugin.")
return return
} }
@ -52,18 +56,6 @@ abstract class ButtonPlugin : JavaPlugin() {
IHaveConfig.pregenConfig(this, null) IHaveConfig.pregenConfig(this, null)
} }
private fun getIConfigInstance(): IHaveConfig {
return IHaveConfig(
::saveConfig,
this.config.getConfigurationSection("global") ?: this.config.createSection("global")
)
}
private fun reloadIConfig(): Boolean {
iConfig = getIConfigInstance()
return isConfigLoaded // If loading fails, getConfig() returns a temporary instance
}
override fun onDisable() { override fun onDisable() {
try { try {
pluginPreDisable() pluginPreDisable()
@ -81,14 +73,17 @@ abstract class ButtonPlugin : JavaPlugin() {
} }
fun tryReloadConfig(): Boolean { fun tryReloadConfig(): Boolean {
if (!justReload()) return false if (!saveAndReloadYaml()) return false
reloadIConfig() iConfig.reload(section)
componentStack.forEach(Consumer { c -> c.updateConfig() }) componentStack.forEach(Consumer { c -> c.updateConfig() })
return true return true
} }
fun justReload(): Boolean { /**
if (yaml != null && ConfigData.saveNow(config)) { * Returns whether the config was loaded successfully. Otherwise, an empty config is used.
*/
private fun saveAndReloadYaml(): Boolean {
if (isConfigLoaded && ConfigData.saveNow(config)) {
logger.warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.") logger.warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.")
return false return false
} }
@ -106,10 +101,8 @@ abstract class ButtonPlugin : JavaPlugin() {
e.printStackTrace() e.printStackTrace()
return false return false
} }
this.yaml = yaml
} else {
return false
} }
this.yaml = yaml
val res = getTextResource("configHelp.yml") ?: return true val res = getTextResource("configHelp.yml") ?: return true
val yc = YamlConfiguration.loadConfiguration(res) val yc = YamlConfiguration.loadConfiguration(res)
for ((key, value) in yc.getValues(true)) for ((key, value) in yc.getValues(true))
@ -121,7 +114,7 @@ abstract class ButtonPlugin : JavaPlugin() {
} }
override fun getConfig(): FileConfiguration { override fun getConfig(): FileConfiguration {
if (yaml == null) justReload() if (!isConfigLoaded) saveAndReloadYaml()
return yaml ?: YamlConfiguration() //Return a temporary instance return yaml ?: YamlConfiguration() //Return a temporary instance
} }

View file

@ -121,10 +121,11 @@ abstract class Component<TP : JavaPlugin> {
private val className: String get() = javaClass.simpleName private val className: String get() = javaClass.simpleName
internal fun updateConfig() { internal fun updateConfig() {
this.config = IHaveConfig(plugin::saveConfig, getConfigSection(plugin)) if (!this::config.isInitialized) this.config = IHaveConfig(plugin::saveConfig, getConfigSection())
else this.config.reload(getConfigSection())
} }
private fun getConfigSection(plugin: JavaPlugin): ConfigurationSection { private fun getConfigSection(): ConfigurationSection {
var compconf = plugin.config.getConfigurationSection("components") var compconf = plugin.config.getConfigurationSection("components")
if (compconf == null) compconf = plugin.config.createSection("components") if (compconf == null) compconf = plugin.config.createSection("components")
var configSect = compconf.getConfigurationSection(className) var configSect = compconf.getConfigurationSection(className)
@ -261,7 +262,7 @@ abstract class Component<TP : JavaPlugin> {
if (component.isEnabled == enabled) return //Don't do anything if (component.isEnabled == enabled) return //Don't do anything
if (enabled.also { component.isEnabled = it }) { if (enabled.also { component.isEnabled = it }) {
try { try {
component.getConfigSection(component.plugin) component.updateConfig()
component.enable() component.enable()
if (ButtonPlugin.configGenAllowed(component)) { if (ButtonPlugin.configGenAllowed(component)) {
IHaveConfig.pregenConfig(component, null) IHaveConfig.pregenConfig(component, null)

View file

@ -23,7 +23,7 @@ import java.util.function.Function
* @param T The type of the config value. May be nullable if the getter cannot always return a value * @param T The type of the config value. May be nullable if the getter cannot always return a value
*/ */
class ConfigData<T : Any?> internal constructor( class ConfigData<T : Any?> internal constructor(
val config: IHaveConfig?, var config: IHaveConfig?,
override val path: String, override val path: String,
private val primitiveDef: Any, private val primitiveDef: Any,
private val getter: Function<Any, T>, private val getter: Function<Any, T>,
@ -52,6 +52,10 @@ class ConfigData<T : Any?> internal constructor(
return getter.apply(convertPrimitiveType(freshValue)).also { value = it } return getter.apply(convertPrimitiveType(freshValue)).also { value = it }
} }
override fun reload() {
value = null
}
/** /**
* Converts a value to [T] from the representation returned by [Configuration.get]. * Converts a value to [T] from the representation returned by [Configuration.get].
*/ */
@ -72,9 +76,9 @@ class ConfigData<T : Any?> internal constructor(
} }
private fun setInternal(`val`: Any?) { private fun setInternal(`val`: Any?) {
if (config == null) return val conf = config ?: return
config.config.set(path, `val`) conf.config.set(path, `val`)
signalChange(config) signalChange(conf)
} }
/** /**

View file

@ -1,8 +1,9 @@
package buttondevteam.lib.architecture package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.ConfigDataDelegate import buttondevteam.lib.architecture.config.ConfigDataDelegate
import buttondevteam.lib.architecture.config.ConfigDataDelegate.Companion.delegate
import buttondevteam.lib.architecture.config.IConfigData import buttondevteam.lib.architecture.config.IConfigData
import buttondevteam.lib.architecture.config.ListConfigDataDelegate
import buttondevteam.lib.architecture.config.delegate
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
import java.util.function.Function import java.util.function.Function
@ -18,7 +19,7 @@ class IHaveConfig(
/** /**
* Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it. * Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it.
*/ */
val config: ConfigurationSection var config: ConfigurationSection
) { ) {
private val datamap = HashMap<String, IConfigData<*>>() private val datamap = HashMap<String, IConfigData<*>>()
@ -88,7 +89,7 @@ class IHaveConfig(
elementGetter: Function<Any?, T>? = null, elementGetter: Function<Any?, T>? = null,
elementSetter: Function<T, Any?>? = null, elementSetter: Function<T, Any?>? = null,
readOnly: Boolean = false readOnly: Boolean = false
): ConfigDataDelegate<ListConfigData<T>.List> { ): ListConfigDataDelegate<T> {
var data = datamap[path] var data = datamap[path]
if (data == null) datamap[path] = ListConfigData( if (data == null) datamap[path] = ListConfigData(
this, this,
@ -109,6 +110,11 @@ class IHaveConfig(
ConfigData.signalChange(this) ConfigData.signalChange(this)
} }
fun reload(section: ConfigurationSection) {
config = section
datamap.forEach { it.value.reload() }
}
companion object { companion object {
/** /**
* Generates the config YAML. * Generates the config YAML.

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.architecture package buttondevteam.lib.architecture
import buttondevteam.lib.architecture.config.IConfigData import buttondevteam.lib.architecture.config.IConfigData
import buttondevteam.lib.architecture.config.IListConfigData
import java.util.function.Function import java.util.function.Function
import java.util.function.Predicate import java.util.function.Predicate
import java.util.function.UnaryOperator import java.util.function.UnaryOperator
@ -12,19 +13,14 @@ class ListConfigData<T> internal constructor(
private val elementGetter: Function<Any?, T>, private val elementGetter: Function<Any?, T>,
private val elementSetter: Function<T, Any?>, private val elementSetter: Function<T, Any?>,
readOnly: Boolean readOnly: Boolean
) : IConfigData<ListConfigData<T>.List> { ) : IConfigData<ListConfigData<T>.List>, IListConfigData<T> {
val listConfig: ConfigData<List> = val listConfig: ConfigData<List> =
ConfigData(config, path, primitiveDef, { List((it as ArrayList<*>).toMutableList()) }, { it }, readOnly) ConfigData(config, path, primitiveDef, { List((it as ArrayList<*>).toMutableList()) }, { it }, readOnly)
override val path: String get() = listConfig.path override val path get() = listConfig.path
override fun get() = listConfig.get()
override fun get(): List { override fun set(value: List) = listConfig.set(value)
return listConfig.get() override fun reload() = listConfig.reload()
}
override fun set(value: List) {
listConfig.set(value)
}
inner class List(backingList: MutableList<Any?>) : MutableList<T> { inner class List(backingList: MutableList<Any?>) : MutableList<T> {
private val primitiveList = backingList private val primitiveList = backingList

View file

@ -1,12 +1,14 @@
package buttondevteam.lib.architecture.config package buttondevteam.lib.architecture.config
import buttondevteam.lib.architecture.ListConfigData
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
class ConfigDataDelegate<T>(val data: IConfigData<T>) : IConfigData<T> by data { open class ConfigDataDelegate<T>(private val data: IConfigData<T>) : IConfigData<T> by data {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = data.get() operator fun getValue(thisRef: Any?, property: KProperty<*>): T = data.get()
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = data.set(value) operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = data.set(value)
companion object {
fun <T> IConfigData<T>.delegate() = ConfigDataDelegate(this)
}
} }
class ListConfigDataDelegate<T>(data: IConfigData<ListConfigData<T>.List>) : ConfigDataDelegate<ListConfigData<T>.List>(data), IListConfigData<T>
fun <T> IConfigData<T>.delegate() = ConfigDataDelegate(this)
fun <T> IListConfigData<T>.delegate() = ListConfigDataDelegate(this)

View file

@ -1,5 +1,7 @@
package buttondevteam.lib.architecture.config package buttondevteam.lib.architecture.config
import buttondevteam.lib.architecture.ListConfigData
interface IConfigData<T> { interface IConfigData<T> {
/** /**
* Gets the value from the config using the getter specified for the config. If the config is not set, the default value is returned. * Gets the value from the config using the getter specified for the config. If the config is not set, the default value is returned.
@ -11,8 +13,15 @@ interface IConfigData<T> {
*/ */
fun set(value: T) fun set(value: T)
/**
* Reload the config from the file.
*/
fun reload()
/** /**
* The path to the config value. * The path to the config value.
*/ */
val path: String val path: String
} }
interface IListConfigData<T> : IConfigData<ListConfigData<T>.List>