From be5f9ded608f5b914442570d051b3da10f2ead6b Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 4 Mar 2023 19:50:49 +0100 Subject: [PATCH] Converted ConfigData and started others - Bumped Spigot version to 1.18.1, so we get config comment support natively - Made getters perform a basic casting by default, this was done by the ConfigData code before --- Chroma-Core/pom.xml | 2 +- .../lib/architecture/ButtonPlugin.kt | 37 ++- .../architecture/CommentedConfiguration.java | 229 ------------------ .../lib/architecture/ConfigData.kt | 105 ++++---- .../lib/architecture/ReadOnlyConfigData.kt | 21 +- .../commands/CommandArgumentHelpManager.kt | 126 +++++----- 6 files changed, 137 insertions(+), 383 deletions(-) delete mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/architecture/CommentedConfiguration.java diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index 1a3e21d..219be89 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -169,7 +169,7 @@ org.spigotmc spigot-api - 1.12.2-R0.1-SNAPSHOT + 1.18.1-R0.1-SNAPSHOT provided 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 dafa73e..8673bde 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt @@ -18,7 +18,7 @@ import java.util.function.Consumer @HasConfig(global = true) abstract class ButtonPlugin : JavaPlugin() { protected val iConfig = IHaveConfig { saveConfig() } - private var yaml: CommentedConfiguration? = null + private var yaml: YamlConfiguration? = null protected val data //TODO : IHaveConfig? = null @@ -39,7 +39,7 @@ abstract class ButtonPlugin : JavaPlugin() { */ protected fun pluginPreDisable() {} override fun onEnable() { - if (!loadConfig()) { + if (!reloadIConfig()) { logger.warning("Please fix the issues and restart the server to load the plugin.") return } @@ -52,12 +52,12 @@ abstract class ButtonPlugin : JavaPlugin() { IHaveConfig.pregenConfig(this, null) } - private fun loadConfig(): Boolean { - val config = config ?: return false + private fun reloadIConfig(): Boolean { + val config = config var section = config.getConfigurationSection("global") if (section == null) section = config.createSection("global") iConfig.reset(section) - return true + return configLoaded // If loading fails, getConfig() returns a temporary instance } override fun onDisable() { @@ -78,7 +78,7 @@ abstract class ButtonPlugin : JavaPlugin() { fun tryReloadConfig(): Boolean { if (!justReload()) return false - loadConfig() + reloadIConfig() componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) }) return true } @@ -89,7 +89,7 @@ abstract class ButtonPlugin : JavaPlugin() { return false } val file = File(dataFolder, "config.yml") - val yaml = CommentedConfiguration(file) + val yaml = YamlConfiguration() if (file.exists()) { try { yaml.load(file) @@ -102,16 +102,17 @@ abstract class ButtonPlugin : JavaPlugin() { e.printStackTrace() return false } + this.yaml = yaml + } else { + return false } - 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", - "" - ), - *value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }.toTypedArray() - ) + for ((key, value) in yc.getValues(true)) + if (value is String) yaml.setComments( + key.replace(".generalDescriptionInsteadOfAConfig", ""), + value.split("\n").map { str -> "# " + str.trim { it <= ' ' } } + ) return true } @@ -121,13 +122,11 @@ abstract class ButtonPlugin : JavaPlugin() { } override fun saveConfig() { - try { - if (yaml != null) yaml!!.save() - } catch (e: Exception) { - TBMCCoreAPI.SendException("Failed to save config", e, this) - } + if (configLoaded) super.saveConfig() } + val configLoaded get() = yaml != null + /** * Registers command and sets its plugin. * diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/CommentedConfiguration.java b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/CommentedConfiguration.java deleted file mode 100644 index d3ee07a..0000000 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/CommentedConfiguration.java +++ /dev/null @@ -1,229 +0,0 @@ -package buttondevteam.lib.architecture; - -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.configuration.file.YamlConstructor; -import org.bukkit.configuration.file.YamlRepresenter; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.representer.Representer; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.HashMap; - -/** - * A copy of Towny's CommentedConfiguration: https://github.com/TownyAdvanced/Towny/blob/master/src/com/palmergames/bukkit/config/CommentedConfiguration.java - * Modified to remove dependency on the FileMgmt class - * - * @author dumptruckman & Articdive - */ -public class CommentedConfiguration extends YamlConfiguration { - private HashMap comments; - private File file; - - private final DumperOptions yamlOptions = new DumperOptions(); - private final Representer yamlRepresenter = new YamlRepresenter(); - private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); - - public CommentedConfiguration(File file) { - - super(); - comments = new HashMap<>(); - this.file = file; - } - - public boolean load() { - - boolean loaded = true; - - try { - this.load(file); - } catch (InvalidConfigurationException | IOException e) { - loaded = false; - } - - return loaded; - } - - public void save() throws IOException { - - boolean saved = true; - - // Save the config just like normal - try { - this.save(file); - - } catch (Exception e) { - saved = false; - } - - // if there's comments to add and it saved fine, we need to add comments - if (!comments.isEmpty() && saved) { - // String array of each line in the config file - String[] yamlContents = Files.readAllLines(file.toPath()).toArray(new String[0]); - - // This will hold the newly formatted line - StringBuilder newContents = new StringBuilder(); - // This holds the current path the lines are at in the config - String currentPath = ""; - // This flags if the line is a node or unknown text. - boolean node; - // The depth of the path. (number of words separated by periods - 1) - int depth = 0; - - // Loop through the config lines - for (String line : yamlContents) { - // If the line is a node (and not something like a list value) - if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) { - - // This is a node so flag it as one - node = true; - - // Grab the index of the end of the node name - int index; - index = line.indexOf(": "); - if (index < 0) { - index = line.length() - 1; - } - // If currentPath is empty, store the node name as the currentPath. (this is only on the first iteration, i think) - if (currentPath.isEmpty()) { - currentPath = line.substring(0, index); - } else { - // Calculate the whitespace preceding the node name - int whiteSpace = 0; - for (int n = 0; n < line.length(); n++) { - if (line.charAt(n) == ' ') { - whiteSpace++; - } else { - break; - } - } - // Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth - if (whiteSpace / 2 > depth) { - // Path is deeper. Add a . and the node name - currentPath += "." + line.substring(whiteSpace, index); - depth++; - } else if (whiteSpace / 2 < depth) { - // Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath - int newDepth = whiteSpace / 2; - for (int i = 0; i < depth - newDepth; i++) { - currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), ""); - } - // Grab the index of the final period - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = ""; - } else { - // If there is a final period, replace everything after it with nothing - currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), ""); - currentPath += "."; - } - // Add the new node name to the path - currentPath += line.substring(whiteSpace, index); - // Reset the depth - depth = newDepth; - } else { - // Path is same depth, replace the last path node name to the current node name - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = ""; - } else { - // If there is a final period, replace everything after it with nothing - currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), ""); - currentPath += "."; - } - //currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), ""); - currentPath += line.substring(whiteSpace, index); - - } - - } - - } else { - node = false; - } - - if (node) { - // If there's a comment for the current path, retrieve it and flag that path as already commented - String comment = comments.get(currentPath); - - if (comment != null) { - // Add the comment to the beginning of the current line - line = comment + System.getProperty("line.separator") + line + System.getProperty("line.separator"); - } else { - // Add a new line as it is a node, but has no comment - line += System.getProperty("line.separator"); - } - } - // Add the (modified) line to the total config String - if (!node) { - newContents.append(line).append(System.getProperty("line.separator")); - } else { - newContents.append(line); - } - } - - /* - * Due to a Bukkit Bug with the Configuration - * we just need to remove any extra comments at the start of a file. - */ - while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) { - newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), "")); - } - Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8)); - } - } - - /** - * Adds a comment just before the specified path. The comment can be - * multiple lines. An empty string will indicate a blank line. - * - * @param path Configuration path to add comment. - * @param commentLines Comments to add. One String per line. - */ - public void addComment(String path, String... commentLines) { - - StringBuilder commentstring = new StringBuilder(); - StringBuilder leadingSpaces = new StringBuilder(); - for (int n = 0; n < path.length(); n++) { - if (path.charAt(n) == '.') { - leadingSpaces.append(" "); - } - } - for (String line : commentLines) { - if (!line.isEmpty()) { - line = leadingSpaces + line; - } else { - line = " "; - } - if (commentstring.length() > 0) { - commentstring.append(System.getProperty("line.separator")); - } - commentstring.append(line); - } - comments.put(path, commentstring.toString()); - } - - @Override - public String saveToString() { - yamlOptions.setIndent(options().indent()); - yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - yamlOptions.setWidth(10000); - yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - - String dump = yaml.dump(getValues(false)); - - - if (dump.equals(BLANK_CONFIG)) { - dump = ""; - } - - return dump; - } -} 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 20a72e7..0bfa5d8 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -6,7 +6,6 @@ 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 /** @@ -21,10 +20,10 @@ open class ConfigData internal constructor( val path: String, def: T?, primitiveDef: Any?, - private val getter: Function?, - private val setter: Function? + private val getter: Function, + private val setter: Function ) { - private val def: Any? + private val pdef: Any? /** * The config value should not change outside this instance @@ -32,9 +31,8 @@ open class ConfigData internal constructor( private var value: T? = null init { - this.def = primitiveDef ?: def?.let { setter?.apply(it) } + this.pdef = primitiveDef ?: def?.let { setter.apply(it) } ?: throw IllegalArgumentException("Either def or primitiveDef must be set. A getter and setter must be present when using primitiveDef.") - require(getter == null == (setter == null)) { "Both setters and getters must be present (or none if def is primitive)." } get() //Generate config automatically } @@ -50,67 +48,46 @@ open class ConfigData internal constructor( if (value != null) return value //Speed things up val config = config?.config var `val`: Any? - if (config == null || !config.isSet(path)) { //Call set() if config == null - `val` = primitiveDef // TODO: primitiveDef --> def, def --> getter(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 + if (config == null || !config.isSet(path)) { + `val` = pdef + setInternal(pdef) // Save default value even if read-only } 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 + `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) + 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 } + return getter.apply(convert(`val`, pdef)).also { value = it } } 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`) + val `val` = value?.let { setter.apply(value) } + setInternal(`val`) this.value = value } private fun setInternal(`val`: Any?) { - config!!.getConfig().set(path, `val`) + if (config == null) return + config.config.set(path, `val`) signalChange(config) } - @AllArgsConstructor - private class SaveTask { - var task: BukkitTask? = null - var saveAction: Runnable? = null - } + private class SaveTask(val task: BukkitTask, val saveAction: Runnable) - @RequiredArgsConstructor(access = AccessLevel.PACKAGE) - class ConfigDataBuilder { - private val config: IHaveConfig? = null - private val path: String? = null + class ConfigDataBuilder internal constructor(private val config: IHaveConfig, private val path: String) { private var def: T? = null private var primitiveDef: Any? = null - private var getter: Function? = null - private var setter: Function? = null + + @Suppress("UNCHECKED_CAST") + private var getter: Function = Function { it as T } + private var setter: Function = Function { it } /** * The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods. @@ -145,7 +122,7 @@ open class ConfigData internal constructor( * @param getter A function that receives the primitive type and returns the runtime type * @return This builder */ - fun getter(getter: Function?): ConfigDataBuilder { + fun getter(getter: Function): ConfigDataBuilder { this.getter = getter return this } @@ -157,7 +134,7 @@ open class ConfigData internal constructor( * @param setter A function that receives the runtime type and returns the primitive type * @return This builder */ - fun setter(setter: Function?): ConfigDataBuilder { + fun setter(setter: Function): ConfigDataBuilder { this.setter = setter return this } @@ -167,9 +144,9 @@ open class ConfigData internal constructor( * * @return A ConfigData instance. */ - fun build(): ConfigData { + fun build(): ConfigData { val config = ConfigData(config, path, def, primitiveDef, getter, setter) - this.config!!.onConfigBuild(config) + this.config.onConfigBuild(config) return config } @@ -178,26 +155,26 @@ open class ConfigData internal constructor( * * @return A ReadOnlyConfigData instance. */ - fun buildReadOnly(): ReadOnlyConfigData { + fun buildReadOnly(): ReadOnlyConfigData { val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter) - this.config!!.onConfigBuild(config) + this.config.onConfigBuild(config) return config } override fun toString(): String { - return "ConfigData.ConfigDataBuilder(config=" + config + ", path=" + path + ", def=" + def + ", primitiveDef=" + primitiveDef + ", getter=" + getter + ", setter=" + setter + ")" + 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() + fun signalChange(config: IHaveConfig) { + val cc = config.config val sa = config.saveAction - if (!saveTasks.containsKey(cc.getRoot())) { + if (!saveTasks.containsKey(cc.root)) { synchronized(saveTasks) { saveTasks.put( - cc.getRoot(), + cc.root, SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, { synchronized( saveTasks @@ -216,16 +193,16 @@ open class ConfigData internal constructor( synchronized(saveTasks) { val st = saveTasks[config] if (st != null) { - st.task!!.cancel() + st.task.cancel() saveTasks.remove(config) - st.saveAction!!.run() + st.saveAction.run() return true } } return false } - fun builder(config: IHaveConfig?, path: String?): ConfigDataBuilder { + fun builder(config: IHaveConfig, path: String): ConfigDataBuilder { return ConfigDataBuilder(config, path) } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt index fcc2b6e..9fb4bd6 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ReadOnlyConfigData.kt @@ -1,13 +1,12 @@ -package buttondevteam.lib.architecture; +package buttondevteam.lib.architecture -import java.util.function.Function; +import java.util.function.Function -public class ReadOnlyConfigData extends ConfigData { - ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function getter, Function setter) { - super(config, path, def, primitiveDef, getter, setter); - } - - ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) { - super(config, path, def, primitiveDef, null, null); - } -} +class ReadOnlyConfigData internal constructor( + config: IHaveConfig?, + path: String, + def: T?, + primitiveDef: Any?, + getter: Function, + setter: Function +) : ConfigData(config, path, def, primitiveDef, getter, setter) \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt index 3fe5ca8..f59ff29 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt @@ -1,67 +1,75 @@ -package buttondevteam.lib.chat.commands; +package buttondevteam.lib.chat.commands -import buttondevteam.core.MainPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.chat.Command2Sender; -import buttondevteam.lib.chat.ICommand2; -import lombok.val; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Method; +import buttondevteam.core.MainPlugin +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.chat.Command2Sender +import buttondevteam.lib.chat.ICommand2 +import org.bukkit.configuration.ConfigurationSection +import org.bukkit.configuration.file.YamlConfiguration +import java.io.IOException +import java.io.InputStreamReader +import java.lang.reflect.Method /** * Deals with reading the commands.yml file from the plugin. The file is generated by ButtonProcessor at compile-time. * Only used when registering commands. */ -public class CommandArgumentHelpManager, TP extends Command2Sender> { - private ConfigurationSection commandConfig; +class CommandArgumentHelpManager, TP : Command2Sender>(command: TC) { + private var commandConfig: ConfigurationSection? = null - /** - * Read the yaml file for the given command class. - * - * @param command The command object to use - */ - public CommandArgumentHelpManager(TC command) { - val commandClass = command.getClass(); - // It will load it for each class, but it would be complicated to solve that - // Most plugins don't have a lot of command classes anyway - try (val str = commandClass.getResourceAsStream("/commands.yml")) { - if (str == null) { - TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance); - return; - } - val config = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); - commandConfig = config.getConfigurationSection(commandClass.getCanonicalName().replace('$', '.')); - if (commandConfig == null) { - MainPlugin.Instance.getLogger().warning("Failed to get command data for " + commandClass + "! Make sure to use 'clean install' when building the project."); - } - } catch (IOException e) { - TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance); - } - } + /** + * Read the yaml file for the given command class. + * + * @param command The command object to use + */ + init { + val commandClass = command.javaClass + // It will load it for each class, but it would be complicated to solve that + // Most plugins don't have a lot of command classes anyway + try { + commandClass.getResourceAsStream("/commands.yml").use { str -> + if (str == null) { + TBMCCoreAPI.SendException( + "Error while getting command data!", + Exception("Resource not found!"), + MainPlugin.Instance + ) + return@use + } + val config = YamlConfiguration.loadConfiguration(InputStreamReader(str)) + commandConfig = config.getConfigurationSection(commandClass.canonicalName.replace('$', '.')) + if (commandConfig == null) { + MainPlugin.Instance.logger.warning("Failed to get command data for $commandClass! Make sure to use 'clean install' when building the project.") + } + } + } catch (e: IOException) { + TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance) + } + } - /** - * Returns a parameter help string for the given subcommand method by reading it from the plugin. - * - * @param method The subcommand method - * @return The parameter part of the usage string for the command - */ - public String getParameterHelpForMethod(Method method) { - val cs = commandConfig.getConfigurationSection(method.getName()); - if (cs == null) { - MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + "! Make sure to use 'clean install' when building the project."); - return null; - } - val mname = cs.getString("method"); - val params = cs.getString("params"); - int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful - if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) { - return params; - } else - TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance); - return null; - } -} + /** + * Returns a parameter help string for the given subcommand method by reading it from the plugin. + * + * @param method The subcommand method + * @return The parameter part of the usage string for the command + */ + fun getParameterHelpForMethod(method: Method): String? { + val cs = commandConfig?.getConfigurationSection(method.name) + if (cs == null) { + MainPlugin.Instance.logger.warning("Failed to get command data for $method! Make sure to use 'clean install' when building the project.") + return null + } + val mname = cs.getString("method") + val params = cs.getString("params") + val i = + mname.indexOf('(') //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful + if (i != -1 && method.name == mname.substring(0, i) && params != null) { + return params + } else TBMCCoreAPI.SendException( + "Error while getting command data for $method!", + Exception("Method '$method' != $mname or params is $params"), + MainPlugin.Instance + ) + return null + } +} \ No newline at end of file