Config improvements

- Added component wrapper to ensure null safety
- Plugin/config properties can no longer be null
This commit is contained in:
Norbi Peti 2023-04-03 17:18:49 +02:00
parent 8d8708d14b
commit 7b7ad18818
4 changed files with 68 additions and 58 deletions

View file

@ -0,0 +1,15 @@
package buttondevteam.lib.architecture
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.plugin.java.JavaPlugin
/**
* A wrapper for plugin components. This is used internally.
*/
class ButtonComponent<TP : JavaPlugin>(
val plugin: TP,
saveAction: Runnable,
config: ConfigurationSection
) {
val config = IHaveConfig(saveAction, config)
}

View file

@ -16,12 +16,12 @@ import java.util.stream.Collectors
* Configuration is based on class name * Configuration is based on class name
*/ */
@HasConfig(global = false) //Used for obtaining javadoc @HasConfig(global = false) //Used for obtaining javadoc
abstract class Component<TP : JavaPlugin?> { abstract class Component<TP : JavaPlugin> {
var isEnabled = false var isEnabled = false
private var wrapper: ButtonComponent<TP>? = null
var plugin: TP? = null val config get() = wrapper!!.config
val plugin get() = wrapper!!.plugin
val config = IHaveConfig(null)
private val data //TODO private val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
@ -32,11 +32,11 @@ abstract class Component<TP : JavaPlugin?> {
.orElse(true)) .orElse(true))
fun log(message: String) { fun log(message: String) {
plugin!!.logger.info("[$className] $message") plugin.logger.info("[$className] $message")
} }
fun logWarn(message: String) { fun logWarn(message: String) {
plugin!!.logger.warning("[$className] $message") plugin.logger.warning("[$className] $message")
} }
/** /**
@ -45,7 +45,7 @@ abstract class Component<TP : JavaPlugin?> {
* *
* @param plugin Plugin object * @param plugin Plugin object
*/ */
protected open fun register(plugin: JavaPlugin?) {} protected open fun register(plugin: JavaPlugin) {}
/** /**
* Unregisters the module, when called by the JavaPlugin class. * Unregisters the module, when called by the JavaPlugin class.
@ -54,7 +54,7 @@ abstract class Component<TP : JavaPlugin?> {
* *
* @param plugin Plugin object * @param plugin Plugin object
*/ */
protected open fun unregister(plugin: JavaPlugin?) {} protected open fun unregister(plugin: JavaPlugin) {}
/** /**
* Enables the module, when called by the JavaPlugin class. Call * Enables the module, when called by the JavaPlugin class. Call
@ -100,25 +100,19 @@ abstract class Component<TP : JavaPlugin?> {
* @param defaultProvider A mapping between config paths and config generators * @param defaultProvider A mapping between config paths and config generators
* @return A map containing configs * @return A map containing configs
*/ */
fun getConfigMap(key: String?, defaultProvider: Map<String, Consumer<IHaveConfig?>>): Map<String, IHaveConfig> { fun getConfigMap(key: String, defaultProvider: Map<String, Consumer<IHaveConfig>>): Map<String, IHaveConfig> {
val c: ConfigurationSection = config.config val c: ConfigurationSection = config.config
var cs = c.getConfigurationSection(key) var cs = c.getConfigurationSection(key)
if (cs == null) cs = c.createSection(key) if (cs == null) cs = c.createSection(key)
val res = cs!!.getValues(false).entries.stream() val res = cs.getValues(false).entries.stream()
.filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection } .filter { (_, value) -> value is ConfigurationSection }
.collect( .collect(Collectors.toMap(
Collectors.toMap<Map.Entry<String?, Any?>, String, IHaveConfig>( { it.key },
{ it.key }, { (_, value) -> IHaveConfig(plugin::saveConfig, value as ConfigurationSection) }
{ (_, value): Map.Entry<String?, Any?> -> ))
val conf = IHaveConfig { plugin!!.saveConfig() }
conf.reset(value as ConfigurationSection?)
conf
})
)
if (res.isEmpty()) { if (res.isEmpty()) {
for ((key1, value) in defaultProvider) { for ((key1, value) in defaultProvider) {
val conf = IHaveConfig { plugin!!.saveConfig() } val conf = IHaveConfig(plugin::saveConfig, cs.createSection(key1))
conf.reset(cs.createSection(key1))
value.accept(conf) value.accept(conf)
res[key1] = conf res[key1] = conf
} }
@ -159,7 +153,7 @@ abstract class Component<TP : JavaPlugin?> {
return registerUnregisterComponent(plugin, component, false) return registerUnregisterComponent(plugin, component, false)
} }
fun <T : JavaPlugin> registerUnregisterComponent( private fun <T : JavaPlugin> registerUnregisterComponent(
plugin: T, plugin: T,
component: Component<T>, component: Component<T>,
register: Boolean register: Boolean
@ -184,9 +178,7 @@ abstract class Component<TP : JavaPlugin?> {
) )
return false return false
} }
component.plugin = plugin // TODO: Perhaps construct a new object with these initialized val wrapper = ButtonComponent(plugin, { plugin.saveConfig() }, getConfigSection(plugin, component))
component.config.saveAction = Runnable { plugin.saveConfig() }
updateConfig(plugin, component)
component.register(plugin) component.register(plugin)
components[component.javaClass] = component components[component.javaClass] = component
if (plugin is ButtonPlugin) (plugin as ButtonPlugin).componentStack.push(component) if (plugin is ButtonPlugin) (plugin as ButtonPlugin).componentStack.push(component)
@ -250,7 +242,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 {
updateConfig(component.plugin!!, component) getConfigSection(component.plugin!!, component)
component.enable() component.enable()
if (ButtonPlugin.configGenAllowed(component)) { if (ButtonPlugin.configGenAllowed(component)) {
IHaveConfig.pregenConfig(component, null) IHaveConfig.pregenConfig(component, null)
@ -276,15 +268,13 @@ abstract class Component<TP : JavaPlugin?> {
} }
} }
@JvmStatic private fun getConfigSection(plugin: JavaPlugin, component: Component<*>): ConfigurationSection {
fun updateConfig(plugin: JavaPlugin, component: Component<*>) { var compconf = plugin.config.getConfigurationSection("components")
if (plugin.config != null) { //Production if (compconf == null) compconf = plugin.config.createSection("components")
var compconf = plugin.config.getConfigurationSection("components") var configSect = compconf.getConfigurationSection(component.className)
if (compconf == null) compconf = plugin.config.createSection("components") if (configSect == null) configSect = compconf.createSection(component.className)
var configSect = compconf!!.getConfigurationSection(component.className) return configSect
if (configSect == null) configSect = compconf.createSection(component.className) // TODO: Support tests (provide Bukkit configuration for tests)
component.config.reset(configSect)
} //Testing: it's already set
} }
/** /**

View file

@ -44,8 +44,9 @@ class ConfigData<T> internal constructor(
value = null value = null
} }
override fun get(): T? { override fun get(): T {
if (value != null) return value //Speed things up val cachedValue = value
if (cachedValue != null) return cachedValue //Speed things up
val config = config?.config val config = config?.config
var `val`: Any? var `val`: Any?
if (config == null || !config.isSet(path)) { if (config == null || !config.isSet(path)) {
@ -54,14 +55,14 @@ class ConfigData<T> internal constructor(
} else `val` = config.get(path) //config==null: testing } else `val` = config.get(path) //config==null: testing
if (`val` == null) //If it's set to null explicitly if (`val` == null) //If it's set to null explicitly
`val` = pdef `val` = pdef
fun convert(_val: Any?, _pdef: Any?): Any? { fun convert(cval: Any?, cpdef: Any?): Any? {
return if (_pdef is Number) //If we expect a number return if (cpdef is Number) //If we expect a number
if (_val is Number) if (cval is Number)
ChromaUtils.convertNumber(_val as Number?, _pdef.javaClass as Class<out Number?>) ChromaUtils.convertNumber(cval, cpdef.javaClass)
else _pdef //If we didn't get a number, return default (which is a number) else cpdef //If we didn't get a number, return default (which is a number)
else if (_val is List<*> && _pdef != null && _pdef.javaClass.isArray) else if (cval is List<*> && cpdef != null && cpdef.javaClass.isArray)
_val.toTypedArray() cval.toTypedArray()
else _val else cval
} }
return getter.apply(convert(`val`, pdef)).also { value = it } return getter.apply(convert(`val`, pdef)).also { value = it }
} }
@ -113,16 +114,19 @@ class ConfigData<T> internal constructor(
fun signalChange(config: IHaveConfig) { fun signalChange(config: IHaveConfig) {
val cc = config.config val cc = config.config
val sa = config.saveAction val sa = config.saveAction
val root = cc.root
if (root == null) {
MainPlugin.Instance.logger.warning("Attempted to save config with no root! Name: ${config.config.name}")
return
}
if (!saveTasks.containsKey(cc.root)) { if (!saveTasks.containsKey(cc.root)) {
synchronized(saveTasks) { synchronized(saveTasks) {
saveTasks.put( saveTasks.put(
cc.root, root,
SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, { SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, {
synchronized( synchronized(saveTasks) {
saveTasks saveTasks.remove(root)
) { sa.run()
saveTasks.remove(cc.getRoot())
sa!!.run()
} }
}, 100), sa) }, 100), sa)
) )
@ -144,8 +148,8 @@ class ConfigData<T> internal constructor(
return false return false
} }
fun <T> builder(config: IHaveConfig, path: String): ConfigDataBuilder<T> { fun <T> builder(config: IHaveConfig, path: String, primitiveDef: Any?, getter: Function<Any?, T>, setter: Function<T, Any?>): ConfigDataBuilder<T> {
return ConfigDataBuilder(config, path) return ConfigDataBuilder(config, path, primitiveDef, getter, setter)
} }
} }
} }

View file

@ -43,13 +43,14 @@ class IHaveConfig(
* @param T The type of this variable (can be any class) * @param T The type of this variable (can be any class)
* @return The data object that can be used to get or set the value * @return The data object that can be used to get or set the value
*/ */
fun <T> getConfig( @Suppress("UNCHECKED_CAST")
fun <T> getConfig( // TODO: Remove
path: String, path: String,
def: T, def: T,
getter: Function<Any?, T>? = null, getter: Function<Any?, T>? = null,
setter: Function<T, Any?>? = null setter: Function<T, Any?>? = null
): ConfigDataBuilder<T> { ): ConfigDataBuilder<T> {
return ConfigData.builder(this, path) return ConfigData.builder(this, path, if (setter != null) setter.apply(def) else def, getter ?: Function { it as T }, setter ?: Function { it })
} }
fun onConfigBuild(config: IConfigData<*>) { fun onConfigBuild(config: IConfigData<*>) {
@ -77,7 +78,7 @@ class IHaveConfig(
setter: Function<T, Any?>? = null, setter: Function<T, Any?>? = null,
readOnly: Boolean = false readOnly: Boolean = false
): ConfigData<T> { ): ConfigData<T> {
return getData(path, getter ?: Function { it as T }, setter ?: Function { it }, def) return getData(path, getter ?: Function { it as T }, setter ?: Function { it }, def, readOnly)
} }
/** /**
@ -112,7 +113,7 @@ class IHaveConfig(
* @param <T> The type of this variable (only use primitives or String) * @param <T> The type of this variable (only use primitives or String)
* @return The data object that can be used to get or set the value * @return The data object that can be used to get or set the value
</T> */ </T> */
fun <T> getData(path: String, def: Supplier<T>): ConfigData<T> { fun <T> getData(path: String, def: Supplier<T>): ConfigData<T> { // TODO: Remove
var data = datamap[path] var data = datamap[path]
if (data == null) { if (data == null) {
val defval = def.get() val defval = def.get()