From 529fef1c5fdf3202e6316031712d7256d39d2d8c Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 26 Jun 2023 23:03:19 +0200 Subject: [PATCH] Fix config lateinit errors and fix testing --- .../java/buttondevteam/core/MainPlugin.kt | 9 ++- .../java/buttondevteam/core/TestPrepare.kt | 65 +++++-------------- .../java/buttondevteam/lib/ChromaUtils.kt | 11 ++++ .../lib/architecture/ButtonPlugin.kt | 9 ++- .../lib/architecture/Component.kt | 17 +++-- .../lib/architecture/ConfigData.kt | 26 ++++---- .../lib/architecture/IHaveConfig.kt | 17 +++-- .../lib/architecture/ListConfigData.kt | 6 +- 8 files changed, 81 insertions(+), 79 deletions(-) diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt index c397b5b..1859a00 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt @@ -28,10 +28,13 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.command.ConsoleCommandSender import org.bukkit.entity.Player +import org.bukkit.plugin.PluginDescriptionFile +import org.bukkit.plugin.java.JavaPluginLoader +import java.io.File import java.util.* import java.util.function.Supplier -class MainPlugin : ButtonPlugin() { +class MainPlugin : ButtonPlugin { private var economy: Economy? = null /** @@ -62,6 +65,10 @@ class MainPlugin : ButtonPlugin() { */ // TODO: Combine the channel access test with command permissions (with a generic implementation) val externalPlayerPermissionGroup get() = iConfig.getData("externalPlayerPermissionGroup", "default") + constructor() : super() + constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File) : super(loader, description, dataFolder, file) + + public override fun pluginEnable() { instance = this val pdf = description diff --git a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.kt b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.kt index 947a6c1..5223e23 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.kt @@ -1,50 +1,21 @@ -package buttondevteam.core; +package buttondevteam.core -import buttondevteam.core.component.channel.Channel; -import buttondevteam.core.component.channel.ChannelComponent; -import buttondevteam.lib.ChromaUtils; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.TBMCChatAPI; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitScheduler; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import be.seeseemelk.mockbukkit.MockBukkit +import buttondevteam.core.component.channel.Channel +import buttondevteam.core.component.channel.ChannelComponent +import buttondevteam.lib.ChromaUtils.isTest +import buttondevteam.lib.architecture.Component.Companion.registerComponent +import buttondevteam.lib.chat.Color +import buttondevteam.lib.chat.TBMCChatAPI.registerChatChannel +import org.bukkit.ChatColor -import java.util.Collection; -import java.util.Collections; -import java.util.logging.Logger; - -public class TestPrepare { - - public static void PrepareServer() { - ChromaUtils.setTest(true); //Needs to be in a separate class because of the potential lack of Mockito - Bukkit.setServer(Mockito.mock(Server.class, new Answer() { - - @Override - public Object answer(InvocationOnMock invocation) { - if (returns(invocation, String.class)) - return "test"; - if (returns(invocation, Logger.class)) - return Logger.getAnonymousLogger(); - if (returns(invocation, PluginManager.class)) - return Mockito.mock(PluginManager.class); - if (returns(invocation, Collection.class)) - return Collections.EMPTY_LIST; - if (returns(invocation, BukkitScheduler.class)) - return Mockito.mock(BukkitScheduler.class); - return null; - } - - boolean returns(InvocationOnMock invocation, Class cl) { - return cl.isAssignableFrom(invocation.getMethod().getReturnType()); - } - })); - Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent()); - TBMCChatAPI.registerChatChannel(Channel.globalChat = new Channel("${ChatColor.WHITE}g${ChatColor.WHITE}", Color.White, "g", null)); - } +@Deprecated("Use MockBukkit") +object TestPrepare { + @JvmStatic + fun prepareServer() { + isTest = true //Needs to be in a separate class because of the potential lack of Mockito + MockBukkit.mock() + registerComponent(MockBukkit.load(MainPlugin::class.java), ChannelComponent()) + registerChatChannel(Channel("${ChatColor.WHITE}g${ChatColor.WHITE}", Color.White, "g", null).also { Channel.globalChat = it }) + } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt index 7d2e5ef..a80510e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt @@ -70,6 +70,17 @@ object ChromaUtils { } else what.get() } + /** + * Log a warning message if the plugin is initialized. If not, just print a regular message. + */ + fun logWarn(message: String) { + if (MainPlugin.isInitialized) { + MainPlugin.instance.logger.warning(message) + } else { + println(message) + } + } + /** * Returns true while unit testing. */ 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 5fccf79..97cfdfc 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt @@ -8,14 +8,16 @@ import buttondevteam.lib.chat.ICommand2MC import org.bukkit.configuration.InvalidConfigurationException import org.bukkit.configuration.file.FileConfiguration import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.plugin.PluginDescriptionFile import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.plugin.java.JavaPluginLoader import java.io.File import java.io.IOException import java.util.* import java.util.function.Consumer @HasConfig(global = true) -abstract class ButtonPlugin : JavaPlugin() { +abstract class ButtonPlugin : JavaPlugin { protected val iConfig = IHaveConfig(::saveConfig, section) private var yaml: YamlConfiguration? = null @@ -31,6 +33,11 @@ abstract class ButtonPlugin : JavaPlugin() { * Used to unregister components in the right order - and to reload configs */ val componentStack = Stack>() + + // Support testing with the protected constructor (MockBukkit) + constructor() : super() + protected constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File) : super(loader, description, dataFolder, file) + protected abstract fun pluginEnable() /** 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 bda7290..13cfb58 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt @@ -19,7 +19,7 @@ import java.util.stream.Collectors abstract class Component { var isEnabled = false - lateinit var config: IHaveConfig + var config: IHaveConfig = IHaveConfig({ logWarn("Attempted to save config with null section!") }, null) private set lateinit var plugin: TP private set @@ -100,7 +100,11 @@ abstract class Component { * @return A map containing configs */ fun getConfigMap(key: String, defaultProvider: Map>): Map { - val c: ConfigurationSection = config.config + val c: ConfigurationSection? = config.config + if (c == null) { + logWarn("Config section is null when getting config map") + return defaultProvider.mapValues { kv -> IHaveConfig(plugin::saveConfig, null).also { kv.value.accept(it) } } + } val cs = c.getConfigurationSection(key) ?: c.createSection(key) val res = cs.getValues(false).entries.stream() .filter { (_, value) -> value is ConfigurationSection } @@ -109,11 +113,7 @@ abstract class Component { { (_, value) -> IHaveConfig(plugin::saveConfig, value as ConfigurationSection) } )) if (res.isEmpty()) { - for ((key1, value) in defaultProvider) { - val conf = IHaveConfig(plugin::saveConfig, cs.createSection(key1)) - value.accept(conf) - res[key1] = conf - } + defaultProvider.mapValuesTo(res) { kv -> IHaveConfig(plugin::saveConfig, cs.createSection(kv.key)).also { kv.value.accept(it) } } } return res } @@ -121,8 +121,7 @@ abstract class Component { private val className: String get() = javaClass.simpleName internal fun updateConfig() { - if (!this::config.isInitialized) this.config = IHaveConfig(plugin::saveConfig, getConfigSection()) - else this.config.reload(getConfigSection()) + this.config.reload(getConfigSection(), plugin::saveConfig) } private fun getConfigSection(): ConfigurationSection { 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 a677e79..e67f4b1 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -14,16 +14,16 @@ import java.util.function.Function * Use [Component.config] or [ButtonPlugin.iConfig] then [IHaveConfig.getData] to get an instance. * * **Note:** The instance can become outdated if the config is reloaded. - * @param config May be null for testing + * @param config The config object to use for the whole file * @param path The path to the config value - * @param primitiveDef The default value, as stored in the config. Non-nullable as it needs to be saved to the config + * @param primitiveDef The default value, as stored in the config. Non-nullable as it needs to be saved sto the config * @param getter Function to convert primtive types to [T]. The parameter is of a primitive type as returned by [Configuration.get] * @param setter Function to convert [T] to a primitive type. The result should be a primitive type or string that can be retrieved correctly later * @param readOnly If true, changing the value will have no effect * @param T The type of the config value. May be nullable if the getter cannot always return a value */ class ConfigData internal constructor( - var config: IHaveConfig?, + val config: IHaveConfig, override val path: String, private val primitiveDef: Any, private val getter: Function, @@ -47,7 +47,7 @@ class ConfigData internal constructor( override fun get(): T { val cachedValue = value if (cachedValue != null) return cachedValue //Speed things up - val config = config?.config + val config = config.config val freshValue = config?.get(path) ?: primitiveDef.also { setInternal(it) } return getter.apply(convertPrimitiveType(freshValue)).also { value = it } } @@ -76,9 +76,13 @@ class ConfigData internal constructor( } private fun setInternal(`val`: Any?) { - val conf = config ?: return - conf.config.set(path, `val`) - signalChange(conf) + val config = config.config + if (config != null) { + config.set(path, `val`) + signalChange(this.config) + } else { + ChromaUtils.logWarn("Attempted to get/set config value with no config! Path: $path, value: $`val`") + } } /** @@ -95,13 +99,9 @@ class ConfigData internal constructor( fun signalChange(config: IHaveConfig) { val cc = config.config val sa = config.saveAction - val root = cc.root + val root = cc?.root if (root == null) { - if (MainPlugin.isInitialized) { - MainPlugin.instance.logger.warning("Attempted to save config with no root! Name: ${config.config.name}") - } else { - println("Attempted to save config with no root! Name: ${config.config.name}") - } + ChromaUtils.logWarn("Attempted to save config with no root! Name: ${cc?.name ?: "NONEXISTENT CONFIG"}") return } if (!MainPlugin.isInitialized) { 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 0606512..d466ee4 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt @@ -15,12 +15,20 @@ class IHaveConfig( /** * The way the underlying configuration gets saved to disk */ - val saveAction: Runnable, + saveAction: Runnable, + config: ConfigurationSection? +) { + /** + * The way the underlying configuration gets saved to disk + */ + var saveAction = saveAction + private set + /** * Returns the Bukkit ConfigurationSection. Use [.signalChange] after changing it. */ - var config: ConfigurationSection -) { + var config = config + private set private val datamap = HashMap>() /** @@ -110,8 +118,9 @@ class IHaveConfig( ConfigData.signalChange(this) } - fun reload(section: ConfigurationSection) { + fun reload(section: ConfigurationSection, saveAction: Runnable = this.saveAction) { config = section + this.saveAction = saveAction datamap.forEach { it.value.reload() } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt index 15167e2..42e3453 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt @@ -7,7 +7,7 @@ import java.util.function.Predicate import java.util.function.UnaryOperator class ListConfigData internal constructor( - config: IHaveConfig?, + config: IHaveConfig, path: String, primitiveDef: ArrayList<*>, private val elementGetter: Function, @@ -27,9 +27,7 @@ class ListConfigData internal constructor( override val size: Int get() = primitiveList.size private fun update() { val config = listConfig.config - if (config != null) { - ConfigData.signalChange(config) //Update the config model and start save task if needed - } + ConfigData.signalChange(config) //Update the config model and start save task if needed } override fun set(index: Int, element: T): T {