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
This commit is contained in:
Norbi Peti 2023-03-04 19:50:49 +01:00
parent 89b7246f4f
commit be5f9ded60
6 changed files with 137 additions and 383 deletions

View file

@ -169,7 +169,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version> <version>1.18.1-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -18,7 +18,7 @@ import java.util.function.Consumer
@HasConfig(global = true) @HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() { abstract class ButtonPlugin : JavaPlugin() {
protected val iConfig = IHaveConfig { saveConfig() } protected val iConfig = IHaveConfig { saveConfig() }
private var yaml: CommentedConfiguration? = null private var yaml: YamlConfiguration? = null
protected val data //TODO protected val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
@ -39,7 +39,7 @@ abstract class ButtonPlugin : JavaPlugin() {
*/ */
protected fun pluginPreDisable() {} protected fun pluginPreDisable() {}
override fun onEnable() { override fun onEnable() {
if (!loadConfig()) { if (!reloadIConfig()) {
logger.warning("Please fix the issues and restart the server to load the plugin.") logger.warning("Please fix the issues and restart the server to load the plugin.")
return return
} }
@ -52,12 +52,12 @@ abstract class ButtonPlugin : JavaPlugin() {
IHaveConfig.pregenConfig(this, null) IHaveConfig.pregenConfig(this, null)
} }
private fun loadConfig(): Boolean { private fun reloadIConfig(): Boolean {
val config = config ?: return false val config = config
var section = config.getConfigurationSection("global") var section = config.getConfigurationSection("global")
if (section == null) section = config.createSection("global") if (section == null) section = config.createSection("global")
iConfig.reset(section) iConfig.reset(section)
return true return configLoaded // If loading fails, getConfig() returns a temporary instance
} }
override fun onDisable() { override fun onDisable() {
@ -78,7 +78,7 @@ abstract class ButtonPlugin : JavaPlugin() {
fun tryReloadConfig(): Boolean { fun tryReloadConfig(): Boolean {
if (!justReload()) return false if (!justReload()) return false
loadConfig() reloadIConfig()
componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) }) componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) })
return true return true
} }
@ -89,7 +89,7 @@ abstract class ButtonPlugin : JavaPlugin() {
return false return false
} }
val file = File(dataFolder, "config.yml") val file = File(dataFolder, "config.yml")
val yaml = CommentedConfiguration(file) val yaml = YamlConfiguration()
if (file.exists()) { if (file.exists()) {
try { try {
yaml.load(file) yaml.load(file)
@ -102,15 +102,16 @@ abstract class ButtonPlugin : JavaPlugin() {
e.printStackTrace() e.printStackTrace()
return false return false
} }
}
this.yaml = yaml this.yaml = yaml
} else {
return false
}
val res = getTextResource("configHelp.yml") ?: return true val res = getTextResource("configHelp.yml") ?: return true
val yc = YamlConfiguration.loadConfiguration(res) val yc = YamlConfiguration.loadConfiguration(res)
for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace( for ((key, value) in yc.getValues(true))
".generalDescriptionInsteadOfAConfig", if (value is String) yaml.setComments(
"" key.replace(".generalDescriptionInsteadOfAConfig", ""),
), value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }
*value.split("\n").map { str -> "# " + str.trim { it <= ' ' } }.toTypedArray()
) )
return true return true
} }
@ -121,13 +122,11 @@ abstract class ButtonPlugin : JavaPlugin() {
} }
override fun saveConfig() { override fun saveConfig() {
try { if (configLoaded) super.saveConfig()
if (yaml != null) yaml!!.save()
} catch (e: Exception) {
TBMCCoreAPI.SendException("Failed to save config", e, this)
}
} }
val configLoaded get() = yaml != null
/** /**
* Registers command and sets its plugin. * Registers command and sets its plugin.
* *

View file

@ -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 &amp; Articdive
*/
public class CommentedConfiguration extends YamlConfiguration {
private HashMap<String, String> 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;
}
}

View file

@ -6,7 +6,6 @@ import lombok.*
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.configuration.Configuration import org.bukkit.configuration.Configuration
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import java.util.function.BiFunction
import java.util.function.Function import java.util.function.Function
/** /**
@ -21,10 +20,10 @@ open class ConfigData<T> internal constructor(
val path: String, val path: String,
def: T?, def: T?,
primitiveDef: Any?, primitiveDef: Any?,
private val getter: Function<Any?, T>?, private val getter: Function<Any?, T>,
private val setter: Function<T, Any?>? private val setter: Function<T, Any?>
) { ) {
private val def: Any? private val pdef: Any?
/** /**
* The config value should not change outside this instance * The config value should not change outside this instance
@ -32,9 +31,8 @@ open class ConfigData<T> internal constructor(
private var value: T? = null private var value: T? = null
init { 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.") ?: 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 get() //Generate config automatically
} }
@ -50,67 +48,46 @@ open class ConfigData<T> internal constructor(
if (value != null) return value //Speed things up if (value != null) return value //Speed things up
val config = config?.config val config = config?.config
var `val`: Any? var `val`: Any?
if (config == null || !config.isSet(path)) { //Call set() if config == null if (config == null || !config.isSet(path)) {
`val` = primitiveDef // TODO: primitiveDef --> def, def --> getter(primitiveDef) `val` = pdef
if ((def == null || this is ReadOnlyConfigData<*>) && config != null) //In Discord's case def may be null setInternal(pdef) // Save default value even if read-only
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 } else `val` = config.get(path) //config==null: testing
if (`val` == null) //If it's set to null explicitly if (`val` == null) //If it's set to null explicitly
`val` = primitiveDef `val` = pdef
val convert = BiFunction { _val: Any?, _def: Any? -> fun convert(_val: Any?, _pdef: Any?): Any? {
if (_def is Number) //If we expect a number return if (_pdef is Number) //If we expect a number
_val = if (_val is Number) ChromaUtils.convertNumber( if (_val is Number)
_val as Number?, ChromaUtils.convertNumber(_val as Number?, _pdef.javaClass as Class<out Number?>)
_def.javaClass as Class<out Number?> else _pdef //If we didn't get a number, return default (which is a number)
) else _def //If we didn't get a number, return default (which is a number) else if (_val is List<*> && _pdef != null && _pdef.javaClass.isArray)
else if (_val is List<*> && _def != null && _def.javaClass.isArray) _val = (_val as List<T>).toArray<T>( _val.toTypedArray()
java.lang.reflect.Array.newInstance( else _val
_def.javaClass.componentType,
0
) as Array<T>
)
_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
} }
return getter.apply(convert(`val`, pdef)).also { value = it }
} }
fun set(value: T?) { fun set(value: T?) {
if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data if (this is ReadOnlyConfigData<*>) return //Safety for Discord channel/role data
val `val`: Any? val `val` = value?.let { setter.apply(value) }
`val` = if (setter != null && value != null) setter.apply(value) else value setInternal(`val`)
if (config!!.getConfig<Any>() != null) setInternal(`val`)
this.value = value this.value = value
} }
private fun setInternal(`val`: Any?) { private fun setInternal(`val`: Any?) {
config!!.getConfig<Any>().set(path, `val`) if (config == null) return
config.config.set(path, `val`)
signalChange(config) signalChange(config)
} }
@AllArgsConstructor private class SaveTask(val task: BukkitTask, val saveAction: Runnable)
private class SaveTask {
var task: BukkitTask? = null
var saveAction: Runnable? = null
}
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) class ConfigDataBuilder<T> internal constructor(private val config: IHaveConfig, private val path: String) {
class ConfigDataBuilder<T> {
private val config: IHaveConfig? = null
private val path: String? = null
private var def: T? = null private var def: T? = null
private var primitiveDef: Any? = null private var primitiveDef: Any? = null
private var getter: Function<Any?, T?>? = null
private var setter: Function<T?, Any?>? = null @Suppress("UNCHECKED_CAST")
private var getter: Function<Any?, T> = Function { it as T }
private var setter: Function<T, Any?> = Function { it }
/** /**
* The default value to use, as used in code. If not a primitive type, use the [.getter] and [.setter] methods. * 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<T> internal constructor(
* @param getter A function that receives the primitive type and returns the runtime type * @param getter A function that receives the primitive type and returns the runtime type
* @return This builder * @return This builder
*/ */
fun getter(getter: Function<Any?, T>?): ConfigDataBuilder<T> { fun getter(getter: Function<Any?, T>): ConfigDataBuilder<T> {
this.getter = getter this.getter = getter
return this return this
} }
@ -157,7 +134,7 @@ open class ConfigData<T> internal constructor(
* @param setter A function that receives the runtime type and returns the primitive type * @param setter A function that receives the runtime type and returns the primitive type
* @return This builder * @return This builder
*/ */
fun setter(setter: Function<T, Any?>?): ConfigDataBuilder<T> { fun setter(setter: Function<T, Any?>): ConfigDataBuilder<T> {
this.setter = setter this.setter = setter
return this return this
} }
@ -167,9 +144,9 @@ open class ConfigData<T> internal constructor(
* *
* @return A ConfigData instance. * @return A ConfigData instance.
*/ */
fun build(): ConfigData<T?> { fun build(): ConfigData<T> {
val config = ConfigData(config, path, def, primitiveDef, getter, setter) val config = ConfigData(config, path, def, primitiveDef, getter, setter)
this.config!!.onConfigBuild(config) this.config.onConfigBuild(config)
return config return config
} }
@ -178,26 +155,26 @@ open class ConfigData<T> internal constructor(
* *
* @return A ReadOnlyConfigData instance. * @return A ReadOnlyConfigData instance.
*/ */
fun buildReadOnly(): ReadOnlyConfigData<T?> { fun buildReadOnly(): ReadOnlyConfigData<T> {
val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter) val config = ReadOnlyConfigData(config, path, def, primitiveDef, getter, setter)
this.config!!.onConfigBuild(config) this.config.onConfigBuild(config)
return config return config
} }
override fun toString(): String { 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 { companion object {
private val saveTasks = HashMap<Configuration, SaveTask>() private val saveTasks = HashMap<Configuration, SaveTask>()
fun signalChange(config: IHaveConfig?) { fun signalChange(config: IHaveConfig) {
val cc = config!!.getConfig<Any>() val cc = config.config
val sa = config.saveAction val sa = config.saveAction
if (!saveTasks.containsKey(cc.getRoot())) { if (!saveTasks.containsKey(cc.root)) {
synchronized(saveTasks) { synchronized(saveTasks) {
saveTasks.put( saveTasks.put(
cc.getRoot(), cc.root,
SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, { SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, {
synchronized( synchronized(
saveTasks saveTasks
@ -216,16 +193,16 @@ open class ConfigData<T> internal constructor(
synchronized(saveTasks) { synchronized(saveTasks) {
val st = saveTasks[config] val st = saveTasks[config]
if (st != null) { if (st != null) {
st.task!!.cancel() st.task.cancel()
saveTasks.remove(config) saveTasks.remove(config)
st.saveAction!!.run() st.saveAction.run()
return true return true
} }
} }
return false return false
} }
fun <T> builder(config: IHaveConfig?, path: String?): ConfigDataBuilder<T> { fun <T> builder(config: IHaveConfig, path: String): ConfigDataBuilder<T> {
return ConfigDataBuilder(config, path) return ConfigDataBuilder(config, path)
} }
} }

View file

@ -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<T> extends ConfigData<T> { class ReadOnlyConfigData<T> internal constructor(
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) { config: IHaveConfig?,
super(config, path, def, primitiveDef, getter, setter); path: String,
} def: T?,
primitiveDef: Any?,
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) { getter: Function<Any?, T>,
super(config, path, def, primitiveDef, null, null); setter: Function<T, Any?>
} ) : ConfigData<T>(config, path, def, primitiveDef, getter, setter)
}

View file

@ -1,45 +1,49 @@
package buttondevteam.lib.chat.commands; package buttondevteam.lib.chat.commands
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.chat.Command2Sender; import buttondevteam.lib.chat.Command2Sender
import buttondevteam.lib.chat.ICommand2; import buttondevteam.lib.chat.ICommand2
import lombok.val; import org.bukkit.configuration.ConfigurationSection
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.configuration.file.YamlConfiguration; import java.io.IOException
import java.io.InputStreamReader
import java.io.IOException; import java.lang.reflect.Method
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. * Deals with reading the commands.yml file from the plugin. The file is generated by ButtonProcessor at compile-time.
* Only used when registering commands. * Only used when registering commands.
*/ */
public class CommandArgumentHelpManager<TC extends ICommand2<TP>, TP extends Command2Sender> { class CommandArgumentHelpManager<TC : ICommand2<TP>, TP : Command2Sender>(command: TC) {
private ConfigurationSection commandConfig; private var commandConfig: ConfigurationSection? = null
/** /**
* Read the yaml file for the given command class. * Read the yaml file for the given command class.
* *
* @param command The command object to use * @param command The command object to use
*/ */
public CommandArgumentHelpManager(TC command) { init {
val commandClass = command.getClass(); val commandClass = command.javaClass
// It will load it for each class, but it would be complicated to solve that // 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 // Most plugins don't have a lot of command classes anyway
try (val str = commandClass.getResourceAsStream("/commands.yml")) { try {
commandClass.getResourceAsStream("/commands.yml").use { str ->
if (str == null) { if (str == null) {
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance); TBMCCoreAPI.SendException(
return; "Error while getting command data!",
Exception("Resource not found!"),
MainPlugin.Instance
)
return@use
} }
val config = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); val config = YamlConfiguration.loadConfiguration(InputStreamReader(str))
commandConfig = config.getConfigurationSection(commandClass.getCanonicalName().replace('$', '.')); commandConfig = config.getConfigurationSection(commandClass.canonicalName.replace('$', '.'))
if (commandConfig == null) { if (commandConfig == null) {
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + commandClass + "! Make sure to use 'clean install' when building the project."); MainPlugin.Instance.logger.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); } catch (e: IOException) {
TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance)
} }
} }
@ -49,19 +53,23 @@ public class CommandArgumentHelpManager<TC extends ICommand2<TP>, TP extends Com
* @param method The subcommand method * @param method The subcommand method
* @return The parameter part of the usage string for the command * @return The parameter part of the usage string for the command
*/ */
public String getParameterHelpForMethod(Method method) { fun getParameterHelpForMethod(method: Method): String? {
val cs = commandConfig.getConfigurationSection(method.getName()); val cs = commandConfig?.getConfigurationSection(method.name)
if (cs == null) { if (cs == null) {
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + "! Make sure to use 'clean install' when building the project."); MainPlugin.Instance.logger.warning("Failed to get command data for $method! Make sure to use 'clean install' when building the project.")
return null; return null
} }
val mname = cs.getString("method"); val mname = cs.getString("method")
val params = cs.getString("params"); 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 val i =
if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) { mname.indexOf('(') //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful
return params; if (i != -1 && method.name == mname.substring(0, i) && params != null) {
} else return params
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance); } else TBMCCoreAPI.SendException(
return null; "Error while getting command data for $method!",
Exception("Method '$method' != $mname or params is $params"),
MainPlugin.Instance
)
return null
} }
} }