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

View file

@ -44,8 +44,9 @@ class ConfigData<T> internal constructor(
value = null
}
override fun get(): T? {
if (value != null) return value //Speed things up
override fun get(): T {
val cachedValue = value
if (cachedValue != null) return cachedValue //Speed things up
val config = config?.config
var `val`: Any?
if (config == null || !config.isSet(path)) {
@ -54,14 +55,14 @@ class ConfigData<T> internal constructor(
} else `val` = config.get(path) //config==null: testing
if (`val` == null) //If it's set to null explicitly
`val` = pdef
fun convert(_val: Any?, _pdef: Any?): Any? {
return if (_pdef is Number) //If we expect a number
if (_val is Number)
ChromaUtils.convertNumber(_val as Number?, _pdef.javaClass as Class<out Number?>)
else _pdef //If we didn't get a number, return default (which is a number)
else if (_val is List<*> && _pdef != null && _pdef.javaClass.isArray)
_val.toTypedArray()
else _val
fun convert(cval: Any?, cpdef: Any?): Any? {
return if (cpdef is Number) //If we expect a number
if (cval is Number)
ChromaUtils.convertNumber(cval, cpdef.javaClass)
else cpdef //If we didn't get a number, return default (which is a number)
else if (cval is List<*> && cpdef != null && cpdef.javaClass.isArray)
cval.toTypedArray()
else cval
}
return getter.apply(convert(`val`, pdef)).also { value = it }
}
@ -113,16 +114,19 @@ class ConfigData<T> internal constructor(
fun signalChange(config: IHaveConfig) {
val cc = config.config
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)) {
synchronized(saveTasks) {
saveTasks.put(
cc.root,
root,
SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, {
synchronized(
saveTasks
) {
saveTasks.remove(cc.getRoot())
sa!!.run()
synchronized(saveTasks) {
saveTasks.remove(root)
sa.run()
}
}, 100), sa)
)
@ -144,8 +148,8 @@ class ConfigData<T> internal constructor(
return false
}
fun <T> builder(config: IHaveConfig, path: String): ConfigDataBuilder<T> {
return ConfigDataBuilder(config, path)
fun <T> builder(config: IHaveConfig, path: String, primitiveDef: Any?, getter: Function<Any?, T>, setter: Function<T, Any?>): ConfigDataBuilder<T> {
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)
* @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,
def: T,
getter: Function<Any?, T>? = null,
setter: Function<T, Any?>? = null
): 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<*>) {
@ -77,7 +78,7 @@ class IHaveConfig(
setter: Function<T, Any?>? = null,
readOnly: Boolean = false
): 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)
* @return The data object that can be used to get or set the value
</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]
if (data == null) {
val defval = def.get()