From 482df409920db0ade27cedd1d8305cbdb1d185b8 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 23 Feb 2023 00:54:29 +0100 Subject: [PATCH] Convert config stuff and fix issues --- Chroma-Core/pom.xml | 1 + .../lib/architecture/ButtonPlugin.kt | 20 +- .../lib/architecture/Component.kt | 101 ++-- .../lib/architecture/ConfigData.kt | 466 ++++++++-------- .../lib/architecture/IHaveConfig.kt | 506 ++++++++++-------- 5 files changed, 576 insertions(+), 518 deletions(-) diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index b2a7863..1a3e21d 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -34,6 +34,7 @@ compile + process-sources compile diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt index 1e439e9..dafa73e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt @@ -6,8 +6,6 @@ import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.Component.Companion.updateConfig import buttondevteam.lib.chat.Command2MC import buttondevteam.lib.chat.ICommand2MC -import lombok.AccessLevel -import lombok.Getter import org.bukkit.configuration.InvalidConfigurationException import org.bukkit.configuration.file.FileConfiguration import org.bukkit.configuration.file.YamlConfiguration @@ -16,22 +14,19 @@ import java.io.File import java.io.IOException import java.util.* import java.util.function.Consumer -import java.util.function.Function @HasConfig(global = true) abstract class ButtonPlugin : JavaPlugin() { protected val iConfig = IHaveConfig { saveConfig() } private var yaml: CommentedConfiguration? = null - @Getter(AccessLevel.PROTECTED) - private val data //TODO + protected val data //TODO : IHaveConfig? = null /** * Used to unregister components in the right order - and to reload configs */ - @Getter - private val componentStack = Stack>() + val componentStack = Stack>() protected abstract fun pluginEnable() /** @@ -111,9 +106,12 @@ abstract class ButtonPlugin : JavaPlugin() { this.yaml = yaml val res = getTextResource("configHelp.yml") ?: return true val yc = YamlConfiguration.loadConfiguration(res) - for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(".generalDescriptionInsteadOfAConfig", ""), - *Arrays.stream(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) - .map { str: String -> "# " + str.trim { it <= ' ' } }.toArray { _Dummy_.__Array__() }) + for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace( + ".generalDescriptionInsteadOfAConfig", + "" + ), + *value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }.toTypedArray() + ) return true } @@ -148,7 +146,7 @@ abstract class ButtonPlugin : JavaPlugin() { val command2MC = Command2MC() fun configGenAllowed(obj: Any): Boolean { return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java)) - .map(Function { obj: ConfigOpts -> obj.disableConfigGen() }).orElse(false) + .map { it.disableConfigGen }.orElse(false) } } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt index 93c24c0..35c58c9 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt @@ -11,38 +11,34 @@ import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin import java.util.* import java.util.function.Consumer -import java.util.function.Function import java.util.stream.Collectors /** * Configuration is based on class name */ @HasConfig(global = false) //Used for obtaining javadoc - abstract class Component { - @Getter - private var enabled = false + var isEnabled = false - @Getter - private var plugin: TP = null + var plugin: TP? = null - @Getter - private val config = IHaveConfig(null) + val config = IHaveConfig(null) @Getter private val data //TODO : IHaveConfig? = null @JvmField - val shouldBeEnabled = config.getData("enabled", - Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map(Function { obj: ComponentMetadata -> obj.enabledByDefault() }).orElse(true)) + val shouldBeEnabled: ConfigData = config.getData("enabled", + Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map { it.enabledByDefault } + .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") } /** @@ -85,7 +81,7 @@ abstract class Component { fun registerCommand(command: ICommand2MC) { if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin) command.registerToComponent(this) - ButtonPlugin.getCommand2MC().registerCommand(command) + ButtonPlugin.command2MC.registerCommand(command) } /** @@ -107,18 +103,23 @@ abstract class Component { * @return A map containing configs */ fun getConfigMap(key: String?, defaultProvider: Map>): Map { - val c: ConfigurationSection = getConfig().getConfig() + 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 -> value is ConfigurationSection } - .collect(Collectors.toMap, String, IHaveConfig>(Function, String> { (key1, value) -> java.util.Map.Entry.key }, Function, IHaveConfig> { (_, value): Map.Entry -> - val conf = IHaveConfig { getPlugin().saveConfig() } - conf.reset(value as ConfigurationSection?) - conf - })) - if (res.size == 0) { + val res = cs!!.getValues(false).entries.stream() + .filter { (_, value): Map.Entry -> value is ConfigurationSection } + .collect( + Collectors.toMap, String, IHaveConfig>( + { it.key }, + { (_, value): Map.Entry -> + val conf = IHaveConfig { plugin!!.saveConfig() } + conf.reset(value as ConfigurationSection?) + conf + }) + ) + if (res.isEmpty()) { for ((key1, value) in defaultProvider) { - val conf = IHaveConfig { getPlugin().saveConfig() } + val conf = IHaveConfig { plugin!!.saveConfig() } conf.reset(cs.createSection(key1)) value.accept(conf) res[key1] = conf @@ -128,7 +129,7 @@ abstract class Component { } private val className: String - private get() = javaClass.simpleName + get() = javaClass.simpleName companion object { private val components = HashMap>, Component>() @@ -143,7 +144,7 @@ abstract class Component { * @return Whether the component is registered successfully (it may have failed to enable) */ @JvmStatic - fun registerComponent(plugin: T, component: Component): Boolean { + fun registerComponent(plugin: T, component: Component): Boolean { return registerUnregisterComponent(plugin, component, true) } @@ -156,29 +157,37 @@ abstract class Component { * @return Whether the component is unregistered successfully (it also got disabled) */ @JvmStatic - fun unregisterComponent(plugin: T, component: Component): Boolean { + fun unregisterComponent(plugin: T, component: Component): Boolean { return registerUnregisterComponent(plugin, component, false) } - fun registerUnregisterComponent(plugin: T, component: Component, register: Boolean): Boolean { + fun registerUnregisterComponent( + plugin: T, + component: Component, + register: Boolean + ): Boolean { return try { val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java) if (metaAnn != null) { - val dependencies: Array>> = metaAnn.depends() + val dependencies = metaAnn.depends for (dep in dependencies) { //TODO: Support dependencies at enable/disable as well - if (!components.containsKey(dep)) { - plugin!!.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName) + if (!components.containsKey(dep.java)) { + plugin.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName) return false } } } if (register) { if (components.containsKey(component.javaClass)) { - TBMCCoreAPI.SendException("Failed to register component " + component.className, IllegalArgumentException("The component is already registered!"), plugin) + TBMCCoreAPI.SendException( + "Failed to register component " + component.className, + IllegalArgumentException("The component is already registered!"), + plugin + ) return false } - component.plugin = plugin - component.config.saveAction = Runnable { plugin!!.saveConfig() } + component.plugin = plugin // TODO: Perhaps construct a new object with these initialized + component.config.saveAction = Runnable { plugin.saveConfig() } updateConfig(plugin, component) component.register(plugin) components[component.javaClass] = component @@ -188,7 +197,11 @@ abstract class Component { setComponentEnabled(component, true) true } catch (e: Exception) { - TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component) + TBMCCoreAPI.SendException( + "Failed to enable component " + component.className + "!", + e, + component + ) true } catch (e: NoClassDefFoundError) { TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component) @@ -197,14 +210,22 @@ abstract class Component { } } else { if (!components.containsKey(component.javaClass)) return true //Already unregistered - if (component.enabled) { + if (component.isEnabled) { try { setComponentEnabled(component, false) } catch (e: Exception) { - TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component) + TBMCCoreAPI.SendException( + "Failed to disable component " + component.className + "!", + e, + component + ) return false //If failed to disable, won't unregister either } catch (e: NoClassDefFoundError) { - TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component) + TBMCCoreAPI.SendException( + "Failed to disable component " + component.className + "!", + e, + component + ) return false } } @@ -228,10 +249,10 @@ abstract class Component { @Throws(UnregisteredComponentException::class) fun setComponentEnabled(component: Component<*>, enabled: Boolean) { if (!components.containsKey(component.javaClass)) throw UnregisteredComponentException(component) - if (component.enabled == enabled) return //Don't do anything - if (enabled.also { component.enabled = it }) { + if (component.isEnabled == enabled) return //Don't do anything + if (enabled.also { component.isEnabled = it }) { try { - updateConfig(component.getPlugin(), component) + updateConfig(component.plugin!!, component) component.enable() if (ButtonPlugin.configGenAllowed(component)) { IHaveConfig.pregenConfig(component, null) @@ -253,7 +274,7 @@ abstract class Component { } } else { component.disable() - ButtonPlugin.getCommand2MC().unregisterCommands(component) + ButtonPlugin.command2MC.unregisterCommands(component) } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt index 783e1dd..b9eb247 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -1,252 +1,260 @@ -package buttondevteam.lib.architecture; +package buttondevteam.lib.architecture -import buttondevteam.core.MainPlugin; -import buttondevteam.lib.ChromaUtils; -import lombok.*; -import org.bukkit.Bukkit; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.scheduler.BukkitTask; - -import java.lang.reflect.Array; -import java.util.HashMap; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Function; +import buttondevteam.core.MainPlugin +import buttondevteam.lib.ChromaUtils +import buttondevteam.lib.architecture.IHaveConfig.getConfig +import lombok.* +import org.bukkit.Bukkit +import org.bukkit.configuration.Configuration +import org.bukkit.scheduler.BukkitTask +import java.util.function.BiFunction +import java.util.function.Function /** - * Use the getter/setter constructor if {@link T} isn't a primitive type or String.
- * Use {@link Component#getConfig()} or {@link ButtonPlugin#getIConfig()} then {@link IHaveConfig#getData(String, Object)} to get an instance. + * Use the getter/setter constructor if [T] isn't a primitive type or String.

+ * Use [Component.getConfig] or [ButtonPlugin.getIConfig] then [IHaveConfig.getData] to get an instance. */ -public class ConfigData { - private static final HashMap saveTasks = new HashMap<>(); - /** - * May be null for testing - */ - private IHaveConfig config; - @Getter - @Setter(AccessLevel.PACKAGE) - private String path; - protected final T def; - private final Object primitiveDef; - /** - * The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)} - */ - private final Function getter; - /** - * The result should be a primitive type or string that can be retrieved correctly later - */ - private final Function setter; +open class ConfigData internal constructor( + config: IHaveConfig?, + path: String?, + def: T, + primitiveDef: Any?, + getter: Function?, + setter: Function? +) { + /** + * May be null for testing + */ + private val config: IHaveConfig? - /** - * The config value should not change outside this instance - */ - private T value; + @Getter + @Setter(AccessLevel.PACKAGE) + private val path: String? + protected val def: T? + private val primitiveDef: Any? - ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function getter, Function setter) { - if (def == null) { - if (primitiveDef == null) - throw new IllegalArgumentException("Either def or primitiveDef must be set."); - if (getter == null) - throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef."); - def = getter.apply(primitiveDef); - } else if (primitiveDef == null) - if (setter == null) - primitiveDef = def; - else - primitiveDef = setter.apply(def); - if ((getter == null) != (setter == null)) - throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive)."); - this.config = config; - this.path = path; - this.def = def; - this.primitiveDef = primitiveDef; - this.getter = getter; - this.setter = setter; - get(); //Generate config automatically - } + /** + * The parameter is of a primitive type as returned by [YamlConfiguration.get] + */ + private val getter: Function? - @Override - public String toString() { - return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}'; - } + /** + * The result should be a primitive type or string that can be retrieved correctly later + */ + private val setter: Function? - void reset() { - value = null; - } + /** + * The config value should not change outside this instance + */ + private var value: T? = null - @SuppressWarnings("unchecked") - public T get() { - if (value != null) return value; //Speed things up - var config = this.config.getConfig(); - Object val; - if (config == null || !config.isSet(path)) { //Call set() if config == null - val = primitiveDef; - if ((def == null || this instanceof ReadOnlyConfigData) && config != null) //In Discord's case def may be null - setInternal(primitiveDef); //If read-only then we still need to save the default value so it can be set - else - set(def); //Save default value - def is always set - } else - val = config.get(path); //config==null: testing - if (val == null) //If it's set to null explicitly - val = primitiveDef; - BiFunction convert = (_val, _def) -> { - if (_def instanceof Number) //If we expect a number - if (_val instanceof Number) - _val = ChromaUtils.convertNumber((Number) _val, - (Class) _def.getClass()); - else - _val = _def; //If we didn't get a number, return default (which is a number) - else if (_val instanceof List && _def != null && _def.getClass().isArray()) - _val = ((List) _val).toArray((T[]) Array.newInstance(_def.getClass().getComponentType(), 0)); - return _val; - }; - if (getter != null) { - val = convert.apply(val, primitiveDef); - T hmm = getter.apply(val); - if (hmm == null) hmm = def; //Set if the getter returned null - return hmm; - } - val = convert.apply(val, def); - return value = (T) val; //Always cache, if not cached yet - } + init { + var def: T? = def + var primitiveDef = primitiveDef + if (def == null) { + requireNotNull(primitiveDef) { "Either def or primitiveDef must be set." } + requireNotNull(getter) { "A getter and setter must be present when using primitiveDef." } + def = getter.apply(primitiveDef) + } else if (primitiveDef == null) primitiveDef = if (setter == null) def else setter.apply(def) + require(getter == null == (setter == null)) { "Both setters and getters must be present (or none if def is primitive)." } + this.config = config + this.path = path + this.def = def + this.primitiveDef = primitiveDef + this.getter = getter + this.setter = setter + get() //Generate config automatically + } - public void set(T value) { - if (this instanceof ReadOnlyConfigData) - return; //Safety for Discord channel/role data - Object val; - if (setter != null && value != null) - val = setter.apply(value); - else val = value; - if (config.getConfig() != null) - setInternal(val); - this.value = value; - } + override fun toString(): String { + return "ConfigData{path='$path', value=$value}" + } - private void setInternal(Object val) { - config.getConfig().set(path, val); - signalChange(config); - } + fun reset() { + value = null + } - static void signalChange(IHaveConfig config) { - var cc = config.getConfig(); - var sa = config.getSaveAction(); - if (!saveTasks.containsKey(cc.getRoot())) { - synchronized (saveTasks) { - saveTasks.put(cc.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> { - synchronized (saveTasks) { - saveTasks.remove(cc.getRoot()); - sa.run(); - } - }, 100), sa)); - } - } - } + fun get(): T? { + if (value != null) return value //Speed things up + val config = config!!.getConfig() + var `val`: Any? + if (config == null || !config.isSet(path)) { //Call set() if config == null + `val` = primitiveDef + if ((def == null || this is ReadOnlyConfigData<*>) && config != null) //In Discord's case def may be null + setInternal(primitiveDef) //If read-only then we still need to save the default value so it can be set + else set(def) //Save default value - def is always set + } else `val` = config.get(path) //config==null: testing + if (`val` == null) //If it's set to null explicitly + `val` = primitiveDef + val convert = BiFunction { _val: Any?, _def: Any? -> + if (_def is Number) //If we expect a number + _val = if (_val is Number) ChromaUtils.convertNumber( + _val as Number?, + _def.javaClass as Class + ) else _def //If we didn't get a number, return default (which is a number) + else if (_val is List<*> && _def != null && _def.javaClass.isArray) _val = (_val as List).toArray( + java.lang.reflect.Array.newInstance( + _def.javaClass.componentType, + 0 + ) as Array + ) + _val + } + if (getter != null) { + `val` = convert.apply(`val`, primitiveDef) + var hmm: T? = getter.apply(`val`) + if (hmm == null) hmm = def //Set if the getter returned null + return hmm + } + `val` = convert.apply(`val`, def) + return `val` as T?. also { + value = it //Always cache, if not cached yet + } + } - @AllArgsConstructor - private static class SaveTask { - BukkitTask task; - Runnable saveAction; - } + fun set(value: T?) { + if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data + val `val`: Any? + `val` = if (setter != null && value != null) setter.apply(value) else value + if (config!!.getConfig() != null) setInternal(`val`) + this.value = value + } - public static boolean saveNow(Configuration config) { - synchronized (saveTasks) { - SaveTask st = saveTasks.get(config); - if (st != null) { - st.task.cancel(); - saveTasks.remove(config); - st.saveAction.run(); - return true; - } - } - return false; - } + private fun setInternal(`val`: Any?) { + config!!.getConfig().set(path, `val`) + signalChange(config) + } - public static ConfigData.ConfigDataBuilder builder(IHaveConfig config, String path) { - return new ConfigDataBuilder(config, path); - } + @AllArgsConstructor + private class SaveTask { + var task: BukkitTask? = null + var saveAction: Runnable? = null + } - @RequiredArgsConstructor(access = AccessLevel.PACKAGE) - public static class ConfigDataBuilder { - private final IHaveConfig config; - private final String path; - private T def; - private Object primitiveDef; - private Function getter; - private Function setter; + @RequiredArgsConstructor(access = AccessLevel.PACKAGE) + class ConfigDataBuilder { + private val config: IHaveConfig? = null + private val path: String? = null + private var def: T? = null + private var primitiveDef: Any? = null + private var getter: Function? = null + private var setter: Function? = null - /** - * The default value to use, as used in code. If not a primitive type, use the {@link #getter(Function)} and {@link #setter(Function)} methods. - *
- * To set the value as it is stored, use {@link #primitiveDef(Object)}. - * - * @param def The default value - * @return This builder - */ - public ConfigDataBuilder def(T def) { - this.def = def; - return this; - } + /** + * The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods. + *

+ * To set the value as it is stored, use [.primitiveDef]. + * + * @param def The default value + * @return This builder + */ + fun def(def: T): ConfigDataBuilder { + this.def = def + return this + } - /** - * The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the {@link #getter(Function)} and {@link #setter(Function)} methods. - *
- * To set the value as used in the code, use {@link #def(Object)}. - * - * @param primitiveDef The default value - * @return This builder - */ - public ConfigDataBuilder primitiveDef(Object primitiveDef) { - this.primitiveDef = primitiveDef; - return this; - } + /** + * The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the [.getter] and [.setter] methods. + *

+ * To set the value as used in the code, use [.def]. + * + * @param primitiveDef The default value + * @return This builder + */ + fun primitiveDef(primitiveDef: Any?): ConfigDataBuilder { + this.primitiveDef = primitiveDef + return this + } - /** - * A function to use to obtain the runtime object from the yaml representation (usually string). - * The {@link #setter(Function)} must also be set. - * - * @param getter A function that receives the primitive type and returns the runtime type - * @return This builder - */ - public ConfigDataBuilder getter(Function getter) { - this.getter = getter; - return this; - } + /** + * A function to use to obtain the runtime object from the yaml representation (usually string). + * The [.setter] must also be set. + * + * @param getter A function that receives the primitive type and returns the runtime type + * @return This builder + */ + fun getter(getter: Function?): ConfigDataBuilder { + this.getter = getter + return this + } - /** - * A function to use to obtain the yaml representation (usually string) from the runtime object. - * The {@link #getter(Function)} must also be set. - * - * @param setter A function that receives the runtime type and returns the primitive type - * @return This builder - */ - public ConfigDataBuilder setter(Function setter) { - this.setter = setter; - return this; - } + /** + * A function to use to obtain the yaml representation (usually string) from the runtime object. + * The [.getter] must also be set. + * + * @param setter A function that receives the runtime type and returns the primitive type + * @return This builder + */ + fun setter(setter: Function?): ConfigDataBuilder { + this.setter = setter + return this + } - /** - * Builds a modifiable config representation. Use if you want to change the value in code. - * - * @return A ConfigData instance. - */ - public ConfigData build() { - ConfigData config = new ConfigData<>(this.config, path, def, primitiveDef, getter, setter); - this.config.onConfigBuild(config); - return config; - } + /** + * Builds a modifiable config representation. Use if you want to change the value *in code*. + * + * @return A ConfigData instance. + */ + fun build(): ConfigData { + val config = ConfigData(config, path, def, primitiveDef, getter, setter) + this.config!!.onConfigBuild(config) + return config + } - /** - * Builds a read-only config representation. Use if you only want the value to be changed in the config. - * - * @return A ReadOnlyConfigData instance. - */ - public ReadOnlyConfigData buildReadOnly() { - ReadOnlyConfigData config = new ReadOnlyConfigData<>(this.config, path, def, primitiveDef, getter, setter); - this.config.onConfigBuild(config); - return config; - } + /** + * Builds a read-only config representation. Use if you only want the value to be changed *in the config*. + * + * @return A ReadOnlyConfigData instance. + */ + fun buildReadOnly(): ReadOnlyConfigData { + val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter) + this.config!!.onConfigBuild(config) + return config + } - public String toString() {return "ConfigData.ConfigDataBuilder(config=" + this.config + ", path=" + this.path + ", def=" + this.def + ", primitiveDef=" + this.primitiveDef + ", getter=" + this.getter + ", setter=" + this.setter + ")";} - } -} + override fun toString(): String { + return "ConfigData.ConfigDataBuilder(config=" + config + ", path=" + path + ", def=" + def + ", primitiveDef=" + primitiveDef + ", getter=" + getter + ", setter=" + setter + ")" + } + } + + companion object { + private val saveTasks = HashMap() + fun signalChange(config: IHaveConfig?) { + val cc = config!!.getConfig() + val sa = config.saveAction + if (!saveTasks.containsKey(cc.getRoot())) { + synchronized(saveTasks) { + saveTasks.put( + cc.getRoot(), + SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, { + synchronized( + saveTasks + ) { + saveTasks.remove(cc.getRoot()) + sa!!.run() + } + }, 100), sa) + ) + } + } + } + + @JvmStatic + fun saveNow(config: Configuration): Boolean { + synchronized(saveTasks) { + val st = saveTasks[config] + if (st != null) { + st.task!!.cancel() + saveTasks.remove(config) + st.saveAction!!.run() + return true + } + } + return false + } + + fun builder(config: IHaveConfig?, path: String?): ConfigDataBuilder { + return ConfigDataBuilder(config, path) + } + } +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt index 6904000..e1e5467 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt @@ -1,256 +1,286 @@ -package buttondevteam.lib.architecture; +package buttondevteam.lib.architecture -import buttondevteam.core.MainPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import lombok.Getter; -import lombok.Setter; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.plugin.java.JavaPlugin; - -import javax.annotation.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; +import buttondevteam.core.MainPlugin +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.ConfigData.ConfigDataBuilder +import lombok.Getter +import org.bukkit.Bukkit +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.plugin.java.JavaPlugin +import java.lang.reflect.InvocationTargetException +import java.util.* +import java.util.function.Function +import java.util.function.Predicate +import java.util.function.Supplier +import java.util.stream.Collectors /** * A config system + * May be used in testing. + * + * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null. */ -public final class IHaveConfig { - private final HashMap> datamap = new HashMap<>(); - /** - * Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it. - */ - @Getter - private ConfigurationSection config; - @Getter - @Setter - private Runnable saveAction; +class IHaveConfig(var saveAction: Runnable?) { // TODO: Make non-nullable after adding component builder + private val datamap = HashMap>() - /** - * May be used in testing. - * - * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null. - */ - public IHaveConfig(Runnable saveAction) { - this.saveAction = saveAction; - } + /** + * Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it. + */ + @Getter + private var config: ConfigurationSection? = null - /** - * Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well. - * - * @param path The dot-separated path relative to this config instance - * @param The runtime type of the config value - * @return A ConfigData builder to set how to obtain the value - */ - public ConfigData.ConfigDataBuilder getConfig(String path) { - return ConfigData.builder(this, path); - } + /** + * Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well. + * + * @param path The dot-separated path relative to this config instance + * @param The runtime type of the config value + * @return A ConfigData builder to set how to obtain the value + */ + fun getConfig(path: String?): ConfigDataBuilder { + return ConfigData.builder(this, path) + } - void onConfigBuild(ConfigData config) { - datamap.put(config.getPath(), config); - } + fun onConfigBuild(config: ConfigData<*>) { + datamap[config.path] = config + } - /** - * This method overload should only be used with primitives or String. - * - * @param path The path in config to use - * @param def The value to use by default - * @param The type of this variable (only use primitives or String) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ConfigData getData(String path, T def) { - ConfigData data = datamap.get(path); - if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null)); - return (ConfigData) data; - } + /** + * This method overload should only be used with primitives or String. + * + * @param path The path in config to use + * @param def The value to use by default + * @param The type of this variable (only use primitives or String) + * @return The data object that can be used to get or set the value + */ + fun getData(path: String, def: T): ConfigData { + var data = datamap[path] + if (data == null) datamap[path] = ConfigData(this, path, def, def, null, null).also { data = it } + @Suppress("UNCHECKED_CAST") + return data as ConfigData + } - /** - * This method overload may be used with any class. - * - * @param path The path in config to use - * @param def The value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ConfigData getData(String path, T def, Function getter, Function setter) { - ConfigData data = datamap.get(path); - if (data == null) - datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter)); - return (ConfigData) data; - } + /** + * This method overload may be used with any class. + * + * @param path The path in config to use + * @param def The value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param The type of this variable (can be any class) + * @return The data object that can be used to get or set the value + */ + fun getData(path: String, def: T, getter: Function?, setter: Function): ConfigData { + var data = datamap[path] + if (data == null) datamap[path] = + ConfigData(this, path, def, setter.apply(def), getter, setter).also { data = it } + @Suppress("UNCHECKED_CAST") + return data as ConfigData + } - /** - * This method overload may be used with any class. The given default value will be run through the getter. - * - * @param path The path in config to use - * @param primitiveDef The primitive value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ConfigData getDataPrimDef(String path, Object primitiveDef, Function getter, Function setter) { - ConfigData data = datamap.get(path); - if (data == null) - datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter)); - return (ConfigData) data; - } + /** + * This method overload may be used with any class. The given default value will be run through the getter. + * + * @param path The path in config to use + * @param primitiveDef The **primitive** value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param The type of this variable (can be any class) + * @return The data object that can be used to get or set the value + */ + fun getDataPrimDef( + path: String, + primitiveDef: Any?, + getter: Function, + setter: Function? + ): ConfigData { + var data = datamap[path] + if (data == null) datamap[path] = + ConfigData(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter).also { data = it } + @Suppress("UNCHECKED_CAST") + return data as ConfigData + } - /** - * This method overload may be used with any class. The given default value will be run through the getter. - * - * @param path The path in config to use - * @param primitiveDef The primitive value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ReadOnlyConfigData getReadOnlyDataPrimDef(String path, Object primitiveDef, Function getter, Function setter) { - ConfigData data = datamap.get(path); - if (data == null) - datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter)); - return (ReadOnlyConfigData) data; - } + /** + * This method overload may be used with any class. The given default value will be run through the getter. + * + * @param path The path in config to use + * @param primitiveDef The **primitive** value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param The type of this variable (can be any class) + * @return The data object that can be used to get or set the value + */ + fun getReadOnlyDataPrimDef( + path: String, + primitiveDef: Any?, + getter: Function, + setter: Function? + ): ReadOnlyConfigData { + var data = datamap[path] + if (data == null) datamap[path] = + ReadOnlyConfigData(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter).also { data = it } + @Suppress("UNCHECKED_CAST") + return data as ReadOnlyConfigData + } - /** - * This method overload should only be used with primitves or String. - * - * @param path The path in config to use - * @param def The value to use by default - * @param The type of this variable (only use primitives or String) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ConfigData getData(String path, Supplier def) { - ConfigData data = datamap.get(path); - if (data == null) { - val defval = def.get(); - datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null)); - } - return (ConfigData) data; - } + /** + * This method overload should only be used with primitves or String. + * + * @param path The path in config to use + * @param def The value to use by default + * @param The type of this variable (only use primitives or String) + * @return The data object that can be used to get or set the value + */ + fun getData(path: String, def: Supplier): ConfigData { + var data = datamap[path] + if (data == null) { + val defval = def.get() + datamap[path] = ConfigData(this, path, defval, defval, null, null).also { data = it } + } + @Suppress("UNCHECKED_CAST") + return data as ConfigData + } - /** - * This method overload may be used with any class. - * - * @param path The path in config to use - * @param def The value to use by default - * @param getter A function that converts a primitive representation to the correct value - * @param setter A function that converts a value to a primitive representation - * @param The type of this variable (can be any class) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ConfigData getData(String path, Supplier def, Function getter, Function setter) { - ConfigData data = datamap.get(path); - if (data == null) { - val defval = def.get(); - datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter)); - } - return (ConfigData) data; - } + /** + * This method overload may be used with any class. + * + * @param path The path in config to use + * @param def The value to use by default + * @param getter A function that converts a primitive representation to the correct value + * @param setter A function that converts a value to a primitive representation + * @param The type of this variable (can be any class) + * @return The data object that can be used to get or set the value + */ + fun getData( + path: String, + def: Supplier, + getter: Function?, + setter: Function + ): ConfigData { + var data = datamap[path] + if (data == null) { + val defval = def.get() + datamap[path] = ConfigData(this, path, defval, setter.apply(defval), getter, setter).also { data = it } + } + @Suppress("UNCHECKED_CAST") + return data as ConfigData + } - /** - * This method overload should only be used with primitves or String. - * - * @param path The path in config to use - * @param The type of this variable (only use primitives or String) - * @return The data object that can be used to get or set the value - */ - @SuppressWarnings("unchecked") - public ListConfigData getListData(String path) { - ConfigData data = datamap.get(path); - if (data == null) - datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List())); - return (ListConfigData) data; - } + /** + * This method overload should only be used with primitves or String. + * + * @param path The path in config to use + * @param The type of this variable (only use primitives or String) + * @return The data object that can be used to get or set the value + */ + fun getListData(path: String): ListConfigData { + var data = datamap[path] + if (data == null) datamap[path] = ListConfigData(this, path, ListConfigData.List()).also { data = it } + @Suppress("UNCHECKED_CAST") + return data as ListConfigData + } - /** - * Schedules a save operation. Use after changing the ConfigurationSection directly. - */ - public void signalChange() { - ConfigData.signalChange(this); - } + /** + * Schedules a save operation. Use after changing the ConfigurationSection directly. + */ + fun signalChange() { + ConfigData.signalChange(this) + } - /** - * Clears all caches and loads everything from yaml. - */ - public void reset(ConfigurationSection config) { - this.config = config; - datamap.forEach((path, data) -> data.reset()); - } + /** + * Clears all caches and loads everything from yaml. + */ + fun reset(config: ConfigurationSection?) { + this.config = config + datamap.forEach { (path: String?, data: ConfigData<*>) -> data.reset() } + } - /** - * Generates the config YAML. - * - * @param obj The object which has config methods - * @param configMap The result from {@link Component#getConfigMap(String, Map)}. May be null. - */ - public static void pregenConfig(Object obj, @Nullable Map configMap) { - val ms = obj.getClass().getDeclaredMethods(); - for (val m : ms) { - if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue; - final String mName; - { - var name = m.getName(); - var ind = name.lastIndexOf('$'); - if (ind == -1) mName = name; - else mName = name.substring(ind + 1); - } - try { - m.setAccessible(true); - List> configList; - if (m.getParameterCount() == 0) { - configList = Collections.singletonList((ConfigData) m.invoke(obj)); - } else if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == IHaveConfig.class) { - if (configMap == null) continue; //Hope it will get called with the param later - configList = configMap.entrySet().stream().map(kv -> - { - try { - return (ConfigData) m.invoke(obj, kv.getValue()); - } catch (IllegalAccessException | InvocationTargetException e) { - String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!"; - if (obj instanceof Component) - TBMCCoreAPI.SendException(msg, e, (Component) obj); - else if (obj instanceof JavaPlugin) - TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj); - else - TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - } else { - if (TBMCCoreAPI.IsTestServer()) - MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes())); - continue; - } - for (val c : configList) { - if (c.getPath().length() == 0) - c.setPath(mName); - else if (!c.getPath().equals(mName)) - MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName); - c.get(); //Saves the default value if needed - also checks validity - } - } catch (Exception e) { - String msg = "Failed to pregenerate " + mName + " for " + obj + "!"; - if (obj instanceof Component) - TBMCCoreAPI.SendException(msg, e, (Component) obj); - else if (obj instanceof JavaPlugin) - TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj); - else - TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning); - } - } - } -} + companion object { + /** + * Generates the config YAML. + * + * @param obj The object which has config methods + * @param configMap The result from [Component.getConfigMap]. May be null. + */ + fun pregenConfig(obj: Any, configMap: Map?) { + val ms = obj.javaClass.declaredMethods + for (m in ms) { + if (m.returnType.name != ConfigData::class.java.name) continue + val mName: String + run { + val name = m.name + val ind = name.lastIndexOf('$') + mName = if (ind == -1) name else name.substring(ind + 1) + } + try { + m.isAccessible = true + var configList: List> + configList = if (m.parameterCount == 0) { + listOf(m.invoke(obj) as ConfigData<*>) + } else if (m.parameterCount == 1 && m.parameterTypes[0] == IHaveConfig::class.java) { + if (configMap == null) continue //Hope it will get called with the param later + configMap.entries.stream().map { (key, value): Map.Entry -> + try { + return@map m.invoke(obj, value) as ConfigData<*> + } catch (e: IllegalAccessException) { + val msg = "Failed to pregenerate $mName for $obj using config $key!" + if (obj is Component<*>) TBMCCoreAPI.SendException( + msg, + e, + obj + ) else if (obj is JavaPlugin) TBMCCoreAPI.SendException( + msg, + e, + obj + ) else TBMCCoreAPI.SendException(msg, e, false) { msg: String? -> + Bukkit.getLogger().warning(msg) + } + return@map null + } catch (e: InvocationTargetException) { + val msg = "Failed to pregenerate $mName for $obj using config $key!" + if (obj is Component<*>) TBMCCoreAPI.SendException( + msg, + e, + obj + ) else if (obj is JavaPlugin) TBMCCoreAPI.SendException( + msg, + e, + obj + ) else TBMCCoreAPI.SendException(msg, e, false) { msg: String? -> + Bukkit.getLogger().warning(msg) + } + return@map null + } + } + .filter(Predicate> { obj: ConfigData? -> Objects.nonNull(obj) }) + .collect(Collectors.toList()) + } else { + if (TBMCCoreAPI.IsTestServer()) MainPlugin.Instance.logger.warning( + "Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString( + m.parameterTypes + ) + ) + continue + } + for (c in configList) { + if (c.path.length == 0) c.setPath(mName) else if (c.path != mName) MainPlugin.Instance.logger.warning( + "Config name does not match: " + c.path + " instead of " + mName + ) + c.get() //Saves the default value if needed - also checks validity + } + } catch (e: Exception) { + val msg = "Failed to pregenerate $mName for $obj!" + if (obj is Component<*>) TBMCCoreAPI.SendException( + msg, + e, + obj + ) else if (obj is JavaPlugin) TBMCCoreAPI.SendException(msg, e, obj) else TBMCCoreAPI.SendException( + msg, + e, + false + ) { msg: String? -> Bukkit.getLogger().warning(msg) } + } + } + } + } +} \ No newline at end of file