diff --git a/ButtonProcessor/pom.xml b/ButtonProcessor/pom.xml
index 7c0d6f0..dfbfe2b 100644
--- a/ButtonProcessor/pom.xml
+++ b/ButtonProcessor/pom.xml
@@ -35,11 +35,11 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.8.0
+ 3.8.1
-proc:none
- 8
- 8
+ 17
+ 17
diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml
index 22d1edc..e457d89 100755
--- a/Chroma-Core/pom.xml
+++ b/Chroma-Core/pom.xml
@@ -27,31 +27,62 @@
Chroma-Core
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
+
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/kotlin
+ ${project.basedir}/src/test/java
+
+
+
+
+
org.apache.maven.plugins
maven-shade-plugin
- 3.2.1
+ 3.2.1
package
shade
-
-
-
+
+
+
me.lucko:commodore
org.javatuples:javatuples
-
-
-
- me.lucko.commodore
-
- buttondevteam.core.commodore
-
-
-
+
+
+
+ me.lucko.commodore
+
+ buttondevteam.core.commodore
+
+
+
@@ -200,6 +231,11 @@
javatuples
1.2
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
TBMCPlugins
@@ -217,6 +253,7 @@
github
UTF-8
1.0.1
+ 1.8.10
https://github.com/TBMCPlugins/mvn-repo
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 4f0a96a..7124b04 100644
--- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt
+++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.kt
@@ -1,171 +1,158 @@
-package buttondevteam.lib.architecture;
+package buttondevteam.lib.architecture
-import buttondevteam.buttonproc.HasConfig;
-import buttondevteam.core.ComponentManager;
-import buttondevteam.lib.TBMCCoreAPI;
-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;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.Stack;
+import buttondevteam.buttonproc.HasConfig
+import buttondevteam.core.ComponentManager
+import buttondevteam.lib.TBMCCoreAPI
+import buttondevteam.lib.architecture.Component.Companion.updateConfig
+import buttondevteam.lib.chat.Command2MC
+import buttondevteam.lib.chat.Command2MC.registerCommand
+import buttondevteam.lib.chat.Command2MC.unregisterCommands
+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
+import org.bukkit.plugin.java.JavaPlugin
+import java.io.File
+import java.io.IOException
+import java.util.*
+import java.util.function.Consumer
+import java.util.function.Function
@HasConfig(global = true)
-public abstract class ButtonPlugin extends JavaPlugin {
- @Getter //Needs to be static as we don't know the plugin when a command is handled
- private static final Command2MC command2MC = new Command2MC();
- @Getter(AccessLevel.PROTECTED)
- private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
- private CommentedConfiguration yaml;
- @Getter(AccessLevel.PROTECTED)
- private IHaveConfig data; //TODO
- /**
- * Used to unregister components in the right order - and to reload configs
- */
- @Getter
- private final Stack> componentStack = new Stack<>();
+abstract class ButtonPlugin : JavaPlugin() {
+ @Getter(AccessLevel.PROTECTED)
+ private val iConfig = IHaveConfig { saveConfig() }
+ private var yaml: CommentedConfiguration? = null
- protected abstract void pluginEnable();
+ @Getter(AccessLevel.PROTECTED)
+ private val data //TODO
+ : IHaveConfig? = null
- /**
- * Called after the components are unregistered
- */
- protected abstract void pluginDisable();
+ /**
+ * Used to unregister components in the right order - and to reload configs
+ */
+ @Getter
+ private val componentStack = Stack>()
+ protected abstract fun pluginEnable()
- /**
- * Called before the components are unregistered
- */
- protected void pluginPreDisable() {
- }
+ /**
+ * Called after the components are unregistered
+ */
+ protected abstract fun pluginDisable()
- @Override
- public final void onEnable() {
- if (!loadConfig()) {
- getLogger().warning("Please fix the issues and restart the server to load the plugin.");
- return;
- }
- try {
- pluginEnable();
- } catch (Exception e) {
- TBMCCoreAPI.SendException("Error while enabling plugin " + getName() + "!", e, this);
- }
- if (configGenAllowed(this)) //If it's not disabled (by default it's not)
- IHaveConfig.pregenConfig(this, null);
- }
+ /**
+ * Called before the components are unregistered
+ */
+ protected fun pluginPreDisable() {}
+ override fun onEnable() {
+ if (!loadConfig()) {
+ logger.warning("Please fix the issues and restart the server to load the plugin.")
+ return
+ }
+ try {
+ pluginEnable()
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Error while enabling plugin $name!", e, this)
+ }
+ if (configGenAllowed(this)) //If it's not disabled (by default it's not)
+ IHaveConfig.pregenConfig(this, null)
+ }
- private boolean loadConfig() {
- var config = getConfig();
- if (config == null)
- return false;
- var section = config.getConfigurationSection("global");
- if (section == null) section = config.createSection("global");
- iConfig.reset(section);
- return true;
- }
+ private fun loadConfig(): Boolean {
+ val config = config ?: return false
+ var section = config.getConfigurationSection("global")
+ if (section == null) section = config.createSection("global")
+ iConfig.reset(section)
+ return true
+ }
- @Override
- public final void onDisable() {
- try {
- pluginPreDisable();
- ComponentManager.unregComponents(this);
- pluginDisable();
- if (ConfigData.saveNow(getConfig()))
- getLogger().info("Saved configuration changes.");
- getCommand2MC().unregisterCommands(this);
- } catch (Exception e) {
- TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
- }
- }
+ override fun onDisable() {
+ try {
+ pluginPreDisable()
+ ComponentManager.unregComponents(this)
+ pluginDisable()
+ if (ConfigData.saveNow(config)) logger.info("Saved configuration changes.")
+ ButtonPlugin.getCommand2MC().unregisterCommands(this)
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Error while disabling plugin $name!", e, this)
+ }
+ }
- @Override
- public void reloadConfig() {
- tryReloadConfig();
- }
+ override fun reloadConfig() {
+ tryReloadConfig()
+ }
- public boolean tryReloadConfig() {
- if (!justReload()) return false;
- loadConfig();
- componentStack.forEach(c -> Component.updateConfig(this, c));
- return true;
- }
+ fun tryReloadConfig(): Boolean {
+ if (!justReload()) return false
+ loadConfig()
+ componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) })
+ return true
+ }
- public boolean justReload() {
- if (yaml != null && ConfigData.saveNow(getConfig())) {
- getLogger().warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.");
- return false;
- }
- var file = new File(getDataFolder(), "config.yml");
- var yaml = new CommentedConfiguration(file);
- if (file.exists()) {
- try {
- yaml.load(file);
- } catch (IOException | InvalidConfigurationException e) {
- getLogger().warning("Failed to load config! Check for syntax errors.");
- e.printStackTrace();
- return false;
- }
- }
- this.yaml = yaml;
- var res = getTextResource("configHelp.yml");
- if (res == null)
- return true;
- var yc = YamlConfiguration.loadConfiguration(res);
- for (var kv : yc.getValues(true).entrySet())
- if (kv.getValue() instanceof String)
- yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
- Arrays.stream(((String) kv.getValue()).split("\n"))
- .map(str -> "# " + str.trim()).toArray(String[]::new));
- return true;
- }
+ fun justReload(): Boolean {
+ if (yaml != null && ConfigData.saveNow(config)) {
+ logger.warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.")
+ return false
+ }
+ val file = File(dataFolder, "config.yml")
+ val yaml = CommentedConfiguration(file)
+ if (file.exists()) {
+ try {
+ yaml.load(file)
+ } catch (e: IOException) {
+ logger.warning("Failed to load config! Check for syntax errors.")
+ e.printStackTrace()
+ return false
+ } catch (e: InvalidConfigurationException) {
+ logger.warning("Failed to load config! Check for syntax errors.")
+ e.printStackTrace()
+ 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", ""),
+ *Arrays.stream(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
+ .map { str: String -> "# " + str.trim { it <= ' ' } }.toArray { _Dummy_.__Array__() })
+ return true
+ }
- @Override
- public FileConfiguration getConfig() {
- if (yaml == null)
- justReload();
- if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
- return yaml;
- }
+ override fun getConfig(): FileConfiguration {
+ if (yaml == null) justReload()
+ return if (yaml == null) YamlConfiguration() else yaml //Return a temporary instance
+ }
- @Override
- public void saveConfig() {
- try {
- if (yaml != null)
- yaml.save();
- } catch (Exception e) {
- TBMCCoreAPI.SendException("Failed to save config", e, this);
- }
- }
+ override fun saveConfig() {
+ try {
+ if (yaml != null) yaml!!.save()
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Failed to save config", e, this)
+ }
+ }
- /**
- * Registers command and sets its plugin.
- *
- * @param command The command to register
- */
- protected void registerCommand(ICommand2MC command) {
- command.registerToPlugin(this);
- getCommand2MC().registerCommand(command);
- }
+ /**
+ * Registers command and sets its plugin.
+ *
+ * @param command The command to register
+ */
+ fun registerCommand(command: ICommand2MC) {
+ command.registerToPlugin(this)
+ ButtonPlugin.getCommand2MC().registerCommand(command)
+ }
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface ConfigOpts {
- boolean disableConfigGen() default false;
- }
+ @Retention(AnnotationRetention.RUNTIME)
+ @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
+ annotation class ConfigOpts(val disableConfigGen: Boolean = false)
+ companion object {
+ @Getter //Needs to be static as we don't know the plugin when a command is handled
- public static boolean configGenAllowed(Object obj) {
- return !Optional.ofNullable(obj.getClass().getAnnotation(ConfigOpts.class))
- .map(ConfigOpts::disableConfigGen).orElse(false);
- }
-}
+ private 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)
+ }
+ }
+}
\ 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 29cb5cf..93c24c0 100644
--- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt
+++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/Component.kt
@@ -1,274 +1,281 @@
-package buttondevteam.lib.architecture;
+package buttondevteam.lib.architecture
-import buttondevteam.buttonproc.HasConfig;
-import buttondevteam.core.ComponentManager;
-import buttondevteam.lib.TBMCCoreAPI;
-import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException;
-import buttondevteam.lib.chat.ICommand2MC;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.val;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.event.Listener;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
+import buttondevteam.buttonproc.HasConfig
+import buttondevteam.core.ComponentManager
+import buttondevteam.lib.TBMCCoreAPI
+import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException
+import buttondevteam.lib.chat.ICommand2MC
+import lombok.Getter
+import org.bukkit.configuration.ConfigurationSection
+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
-public abstract class Component {
- @SuppressWarnings("rawtypes") private static HashMap, Component extends JavaPlugin>> components = new HashMap<>();
- @Getter
- private boolean enabled = false;
- @Getter
- @NonNull
- private TP plugin;
- private @Getter final IHaveConfig config = new IHaveConfig(null);
- private @Getter IHaveConfig data; //TODO
+abstract class Component {
+ @Getter
+ private var enabled = false
- public final ConfigData shouldBeEnabled = config.getData("enabled",
- Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
+ @Getter
+ private var plugin: TP = null
- /**
- * Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.
- * Make sure to register the dependencies first.
- * The component will be enabled automatically, regardless of when it was registered.
- * If not using {@link ButtonPlugin}, call {@link ComponentManager#unregComponents(ButtonPlugin)} on plugin disable.
- *
- * @param component The component to register
- * @return Whether the component is registered successfully (it may have failed to enable)
- */
- public static boolean registerComponent(T plugin, Component component) {
- return registerUnregisterComponent(plugin, component, true);
- }
+ @Getter
+ private val config = IHaveConfig(null)
- /**
- * Unregisters a component by calling {@link #unregister(JavaPlugin)}.
- * Make sure to unregister the dependencies last.
- * Components will be unregistered in opposite order of registering by default by {@link ButtonPlugin} or {@link ComponentManager#unregComponents(ButtonPlugin)}.
- *
- * @param component The component to unregister
- * @return Whether the component is unregistered successfully (it also got disabled)
- */
- public static boolean unregisterComponent(T plugin, Component component) {
- return registerUnregisterComponent(plugin, component, false);
- }
+ @Getter
+ private val data //TODO
+ : IHaveConfig? = null
- public static boolean registerUnregisterComponent(T plugin, Component component, boolean register) {
- try {
- val metaAnn = component.getClass().getAnnotation(ComponentMetadata.class);
- if (metaAnn != null) {
- @SuppressWarnings("rawtypes") Class extends Component>[] dependencies = metaAnn.depends();
- for (val dep : dependencies) { //TODO: Support dependencies at enable/disable as well
- if (!components.containsKey(dep)) {
- plugin.getLogger().warning("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + " as a required dependency is missing/disabled: " + dep.getSimpleName());
- return false;
- }
- }
- }
- if (register) {
- if (components.containsKey(component.getClass())) {
- TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"), plugin);
- return false;
- }
- component.plugin = plugin;
- component.config.setSaveAction(plugin::saveConfig);
- updateConfig(plugin, component);
- component.register(plugin);
- components.put(component.getClass(), component);
- if (plugin instanceof ButtonPlugin)
- ((ButtonPlugin) plugin).getComponentStack().push(component);
- if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
- try { //Enable components registered after the previous ones getting enabled
- setComponentEnabled(component, true);
- return true;
- } catch (Exception | NoClassDefFoundError e) {
- TBMCCoreAPI.SendException("Failed to enable component " + component.getClassName() + "!", e, component);
- return true;
- }
- }
- } else {
- if (!components.containsKey(component.getClass()))
- return true; //Already unregistered
- if (component.enabled) {
- try {
- setComponentEnabled(component, false);
- } catch (Exception | NoClassDefFoundError e) {
- TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e, component);
- return false; //If failed to disable, won't unregister either
- }
- }
- component.unregister(plugin);
- components.remove(component.getClass());
- }
- return true;
- } catch (Exception e) {
- TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e, plugin);
- return false;
- }
- }
+ @JvmField
+ val shouldBeEnabled = config.getData("enabled",
+ Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map(Function { obj: ComponentMetadata -> obj.enabledByDefault() }).orElse(true))
- /**
- * Enables or disables the given component. If the component fails to enable, it will be disabled.
- *
- * @param component The component to register
- * @param enabled Whether it's enabled or not
- */
- public static void setComponentEnabled(Component> component, boolean enabled) throws UnregisteredComponentException {
- if (!components.containsKey(component.getClass()))
- throw new UnregisteredComponentException(component);
- if (component.enabled == enabled) return; //Don't do anything
- if (component.enabled = enabled) {
- try {
- updateConfig(component.getPlugin(), component);
- component.enable();
- if (ButtonPlugin.configGenAllowed(component)) {
- IHaveConfig.pregenConfig(component, null);
- }
- } catch (Exception e) {
- try { //Automatically disable components that fail to enable properly
- setComponentEnabled(component, false);
- throw e;
- } catch (Exception ex) {
- Throwable t = ex;
- for (var th = t; th != null; th = th.getCause())
- t = th; //Set if not null
- if (t != e)
- t.initCause(e);
- throw ex;
- }
- }
- } else {
- component.disable();
- ButtonPlugin.getCommand2MC().unregisterCommands(component);
- }
- }
+ fun log(message: String) {
+ plugin!!.logger.info("[" + className + "] " + message)
+ }
- public static void updateConfig(JavaPlugin plugin, Component> component) {
- if (plugin.getConfig() != null) { //Production
- var compconf = plugin.getConfig().getConfigurationSection("components");
- if (compconf == null) compconf = plugin.getConfig().createSection("components");
- var configSect = compconf.getConfigurationSection(component.getClassName());
- if (configSect == null)
- configSect = compconf.createSection(component.getClassName());
- component.config.reset(configSect);
- } //Testing: it's already set
- }
+ fun logWarn(message: String) {
+ plugin!!.logger.warning("[" + className + "] " + message)
+ }
- /**
- * Returns the currently registered components
- *
- * @return The currently registered components
- */
- @SuppressWarnings("rawtypes")
- public static Map, Component extends JavaPlugin>> getComponents() {
- return Collections.unmodifiableMap(components);
- }
+ /**
+ * Registers the module, when called by the JavaPlugin class.
+ * This gets fired when the plugin is enabled. Use [.enable] to register commands and such.
+ *
+ * @param plugin Plugin object
+ */
+ protected open fun register(plugin: JavaPlugin?) {}
- public void log(String message) {
- plugin.getLogger().info("[" + getClassName() + "] " + message);
- }
+ /**
+ * Unregisters the module, when called by the JavaPlugin class.
+ * This gets fired when the plugin is disabled.
+ * Do any cleanups needed within this method.
+ *
+ * @param plugin Plugin object
+ */
+ protected open fun unregister(plugin: JavaPlugin?) {}
- public void logWarn(String message) {
- plugin.getLogger().warning("[" + getClassName() + "] " + message);
- }
+ /**
+ * Enables the module, when called by the JavaPlugin class. Call
+ * registerCommand() and registerListener() within this method.
+ * To access the plugin, use [.getPlugin].
+ */
+ protected abstract fun enable()
- /**
- * Registers the module, when called by the JavaPlugin class.
- * This gets fired when the plugin is enabled. Use {@link #enable()} to register commands and such.
- *
- * @param plugin Plugin object
- */
- @SuppressWarnings({"unused"})
- protected void register(JavaPlugin plugin) {
- }
+ /**
+ * Disables the module, when called by the JavaPlugin class. Do
+ * any cleanups needed within this method.
+ * To access the plugin, use [.getPlugin].
+ */
+ protected abstract fun disable()
- /**
- * Unregisters the module, when called by the JavaPlugin class.
- * This gets fired when the plugin is disabled.
- * Do any cleanups needed within this method.
- *
- * @param plugin Plugin object
- */
- @SuppressWarnings({"unused"})
- protected void unregister(JavaPlugin plugin) {
- }
+ /**
+ * Registers a command to the component. Make sure to use [buttondevteam.lib.chat.CommandClass] and [buttondevteam.lib.chat.Command2.Subcommand].
+ * You don't need to register the command in plugin.yml.
+ *
+ * @param command Custom coded command class
+ */
+ fun registerCommand(command: ICommand2MC) {
+ if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin)
+ command.registerToComponent(this)
+ ButtonPlugin.getCommand2MC().registerCommand(command)
+ }
- /**
- * Enables the module, when called by the JavaPlugin class. Call
- * registerCommand() and registerListener() within this method.
- * To access the plugin, use {@link #getPlugin()}.
- */
- protected abstract void enable();
+ /**
+ * Registers a Listener to this component
+ *
+ * @param listener The event listener to register
+ * @return The provided listener
+ */
+ protected fun registerListener(listener: Listener): Listener {
+ TBMCCoreAPI.RegisterEventsForExceptions(listener, plugin)
+ return listener
+ }
- /**
- * Disables the module, when called by the JavaPlugin class. Do
- * any cleanups needed within this method.
- * To access the plugin, use {@link #getPlugin()}.
- */
- protected abstract void disable();
+ /**
+ * Returns a map of configs that are under the given key.
+ *
+ * @param key The key to use
+ * @param defaultProvider A mapping between config paths and config generators
+ * @return A map containing configs
+ */
+ fun getConfigMap(key: String?, defaultProvider: Map>): Map {
+ val c: ConfigurationSection = getConfig().getConfig()
+ 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) {
+ for ((key1, value) in defaultProvider) {
+ val conf = IHaveConfig { getPlugin().saveConfig() }
+ conf.reset(cs.createSection(key1))
+ value.accept(conf)
+ res[key1] = conf
+ }
+ }
+ return res
+ }
- /**
- * Registers a command to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}.
- * You don't need to register the command in plugin.yml.
- *
- * @param command Custom coded command class
- */
- protected final void registerCommand(ICommand2MC command) {
- if (plugin instanceof ButtonPlugin)
- command.registerToPlugin((ButtonPlugin) plugin);
- command.registerToComponent(this);
- ButtonPlugin.getCommand2MC().registerCommand(command);
- }
+ private val className: String
+ private get() = javaClass.simpleName
- /**
- * Registers a Listener to this component
- *
- * @param listener The event listener to register
- * @return The provided listener
- */
- protected final Listener registerListener(Listener listener) {
- TBMCCoreAPI.RegisterEventsForExceptions(listener, plugin);
- return listener;
- }
+ companion object {
+ private val components = HashMap>, Component>()
- /**
- * Returns a map of configs that are under the given key.
- *
- * @param key The key to use
- * @param defaultProvider A mapping between config paths and config generators
- * @return A map containing configs
- */
- protected Map getConfigMap(String key, Map> defaultProvider) {
- val c = getConfig().getConfig();
- var cs = c.getConfigurationSection(key);
- if (cs == null) cs = c.createSection(key);
- val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection)
- .collect(Collectors.toMap(Map.Entry::getKey, kv -> {
- var conf = new IHaveConfig(getPlugin()::saveConfig);
- conf.reset((ConfigurationSection) kv.getValue());
- return conf;
- }));
- if (res.size() == 0) {
- for (val entry : defaultProvider.entrySet()) {
- val conf = new IHaveConfig(getPlugin()::saveConfig);
- conf.reset(cs.createSection(entry.getKey()));
- entry.getValue().accept(conf);
- res.put(entry.getKey(), conf);
- }
- }
- return res;
- }
+ /**
+ * Registers a component checking it's dependencies and calling [.register].
+ * Make sure to register the dependencies first.
+ * The component will be enabled automatically, regardless of when it was registered.
+ * **If not using [ButtonPlugin], call [ComponentManager.unregComponents] on plugin disable.**
+ *
+ * @param component The component to register
+ * @return Whether the component is registered successfully (it may have failed to enable)
+ */
+ @JvmStatic
+ fun registerComponent(plugin: T, component: Component): Boolean {
+ return registerUnregisterComponent(plugin, component, true)
+ }
- private String getClassName() {
- return getClass().getSimpleName();
- }
-}
+ /**
+ * Unregisters a component by calling [.unregister].
+ * Make sure to unregister the dependencies last.
+ * **Components will be unregistered in opposite order of registering by default by [ButtonPlugin] or [ComponentManager.unregComponents].**
+ *
+ * @param component The component to unregister
+ * @return Whether the component is unregistered successfully (it also got disabled)
+ */
+ @JvmStatic
+ fun unregisterComponent(plugin: T, component: Component): Boolean {
+ return registerUnregisterComponent(plugin, component, false)
+ }
+
+ 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()
+ 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)
+ return false
+ }
+ }
+ }
+ if (register) {
+ if (components.containsKey(component.javaClass)) {
+ 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() }
+ updateConfig(plugin, component)
+ component.register(plugin)
+ components[component.javaClass] = component
+ if (plugin is ButtonPlugin) (plugin as ButtonPlugin).componentStack.push(component)
+ if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
+ return try { //Enable components registered after the previous ones getting enabled
+ setComponentEnabled(component, true)
+ true
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
+ true
+ } catch (e: NoClassDefFoundError) {
+ TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
+ true
+ }
+ }
+ } else {
+ if (!components.containsKey(component.javaClass)) return true //Already unregistered
+ if (component.enabled) {
+ try {
+ setComponentEnabled(component, false)
+ } catch (e: Exception) {
+ 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)
+ return false
+ }
+ }
+ component.unregister(plugin)
+ components.remove(component.javaClass)
+ }
+ true
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Failed to " + (if (register) "" else "un") + "register component " + component.className + "!", e, plugin)
+ false
+ }
+ }
+
+ /**
+ * Enables or disables the given component. If the component fails to enable, it will be disabled.
+ *
+ * @param component The component to register
+ * @param enabled Whether it's enabled or not
+ */
+ @JvmStatic
+ @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 }) {
+ try {
+ updateConfig(component.getPlugin(), component)
+ component.enable()
+ if (ButtonPlugin.configGenAllowed(component)) {
+ IHaveConfig.pregenConfig(component, null)
+ }
+ } catch (e: Exception) {
+ try { //Automatically disable components that fail to enable properly
+ setComponentEnabled(component, false)
+ throw e
+ } catch (ex: Exception) {
+ var t: Throwable = ex
+ var th: Throwable? = t
+ while (th != null) {
+ t = th //Set if not null
+ th = th.cause
+ }
+ if (t !== e) t.initCause(e)
+ throw ex
+ }
+ }
+ } else {
+ component.disable()
+ ButtonPlugin.getCommand2MC().unregisterCommands(component)
+ }
+ }
+
+ @JvmStatic
+ fun updateConfig(plugin: JavaPlugin, component: Component<*>) {
+ if (plugin.config != null) { //Production
+ var compconf = plugin.config.getConfigurationSection("components")
+ if (compconf == null) compconf = plugin.config.createSection("components")
+ var configSect = compconf!!.getConfigurationSection(component.className)
+ if (configSect == null) configSect = compconf.createSection(component.className)
+ component.config.reset(configSect)
+ } //Testing: it's already set
+ }
+
+ /**
+ * Returns the currently registered components
+ *
+ * @return The currently registered components
+ */
+ @JvmStatic
+ fun getComponents(): Map>, Component> {
+ return Collections.unmodifiableMap(components)
+ }
+ }
+}
\ No newline at end of file
diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt
index a9e6df7..bccd5ae 100644
--- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt
+++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt
@@ -1,325 +1,278 @@
-package buttondevteam.lib.chat;
+package buttondevteam.lib.chat
-import buttondevteam.core.MainPlugin;
-import buttondevteam.lib.TBMCCoreAPI;
-import buttondevteam.lib.chat.commands.CommandArgument;
-import buttondevteam.lib.chat.commands.CommandArgumentHelpManager;
-import buttondevteam.lib.chat.commands.NumberArg;
-import buttondevteam.lib.chat.commands.SubcommandData;
-import buttondevteam.lib.player.ChromaGamerBase;
-import com.mojang.brigadier.CommandDispatcher;
-import com.mojang.brigadier.arguments.*;
-import com.mojang.brigadier.builder.ArgumentBuilder;
-import com.mojang.brigadier.context.CommandContext;
-import com.mojang.brigadier.exceptions.CommandSyntaxException;
-import com.mojang.brigadier.tree.CommandNode;
-import com.mojang.brigadier.tree.LiteralCommandNode;
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-import org.bukkit.Bukkit;
-import org.javatuples.Pair;
-import org.javatuples.Triplet;
-import org.jetbrains.annotations.NotNull;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
+import buttondevteam.core.MainPlugin
+import buttondevteam.lib.TBMCCoreAPI
+import buttondevteam.lib.chat.Command2.Subcommand
+import buttondevteam.lib.chat.commands.*
+import buttondevteam.lib.player.ChromaGamerBase
+import com.mojang.brigadier.CommandDispatcher
+import com.mojang.brigadier.arguments.*
+import com.mojang.brigadier.builder.ArgumentBuilder
+import com.mojang.brigadier.context.CommandContext
+import com.mojang.brigadier.context.ParsedCommandNode
+import com.mojang.brigadier.exceptions.CommandSyntaxException
+import com.mojang.brigadier.tree.CommandNode
+import com.mojang.brigadier.tree.LiteralCommandNode
+import lombok.RequiredArgsConstructor
+import org.bukkit.Bukkit
+import org.javatuples.Pair
+import org.javatuples.Triplet
+import java.lang.reflect.Method
+import java.util.function.Function
+import java.util.function.Predicate
+import java.util.function.Supplier
+import java.util.stream.Collectors
/**
* The method name is the subcommand, use underlines (_) to add further subcommands.
* The args may be null if the conversion failed and it's optional.
*/
@RequiredArgsConstructor
-public abstract class Command2, TP extends Command2Sender> {
+abstract class Command2, TP : Command2Sender> {
+ /**
+ * Parameters annotated with this receive all the remaining arguments
+ */
+ @Target(AnnotationTarget.VALUE_PARAMETER)
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class TextArg
- /**
- * Parameters annotated with this receive all the remaining arguments
- */
- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TextArg {
- }
+ /**
+ * Methods annotated with this will be recognised as subcommands
+ */
+ @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class Subcommand(
+ /**
+ * Help text to show players. A usage message will be also shown below it.
+ */
+ val helpText: Array = [],
+ /**
+ * The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
+ * Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
+ */
+ val permGroup: String = "", val aliases: Array = []) {
+ companion object {
+ /**
+ * Allowed for OPs only by default
+ */
+ const val MOD_GROUP = "mod"
+ }
+ }
- /**
- * Methods annotated with this will be recognised as subcommands
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Subcommand {
- /**
- * Allowed for OPs only by default
- */
- String MOD_GROUP = "mod";
+ @Target(AnnotationTarget.VALUE_PARAMETER)
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class OptionalArg
- /**
- * Help text to show players. A usage message will be also shown below it.
- */
- String[] helpText() default {};
+ protected class ParamConverter(val converter: Function, val errormsg: String, val allSupplier: Supplier>)
- /**
- * The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
- * Used to be "tbmc.admin". The {@link #MOD_GROUP} is provided to use with this.
- */
- String permGroup() default "";
+ protected val paramConverters = HashMap, ParamConverter<*>>()
+ private val commandHelp = ArrayList() //Mainly needed by Discord
+ private val dispatcher = CommandDispatcher()
- String[] aliases() default {};
- }
+ /**
+ * The first character in the command line that shows that it's a command.
+ */
+ private val commandChar = 0.toChar()
- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface OptionalArg {
- }
+ /**
+ * Whether the command's actual code has to be run on the primary thread.
+ */
+ private val runOnPrimaryThread = false
- @RequiredArgsConstructor
- protected static class ParamConverter {
- public final Function converter;
- public final String errormsg;
- public final Supplier> allSupplier;
- }
+ /**
+ * Adds a param converter that obtains a specific object from a string parameter.
+ * The converter may return null.
+ *
+ * @param The type of the result
+ * @param cl The class of the result object
+ * @param converter The converter to use
+ * @param allSupplier The supplier of all possible values (ideally)
+ */
+ open fun addParamConverter(cl: Class, converter: Function, errormsg: String,
+ allSupplier: Supplier>) {
+ paramConverters[cl] = ParamConverter(converter, errormsg, allSupplier)
+ }
- protected final HashMap, ParamConverter>> paramConverters = new HashMap<>();
- private final ArrayList commandHelp = new ArrayList<>(); //Mainly needed by Discord
- private final CommandDispatcher dispatcher = new CommandDispatcher<>();
+ open fun handleCommand(sender: TP, commandline: String): Boolean {
+ val results = dispatcher.parse(commandline, sender)
+ if (results.reader.canRead()) {
+ return false // Unknown command
+ }
+ //Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
+ Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) {
+ try {
+ dispatcher.execute(results)
+ } catch (e: CommandSyntaxException) {
+ sender.sendMessage(e.message)
+ } catch (e: Exception) {
+ TBMCCoreAPI.SendException("Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, MainPlugin.Instance)
+ }
+ }
+ return true //We found a method
+ }
- /**
- * The first character in the command line that shows that it's a command.
- */
- private final char commandChar;
- /**
- * Whether the command's actual code has to be run on the primary thread.
- */
- private final boolean runOnPrimaryThread;
+ //TODO: Add to the help
+ private fun processSenderType(sender: TP, sd: SubcommandData, params: ArrayList): Boolean {
+ val sendertype = sd.senderType
+ val cg: ChromaGamerBase
+ if (sendertype.isAssignableFrom(sender.javaClass)) params.add(sender) //The command either expects a CommandSender or it is a Player, or some other expected type
+ else if (sender is Command2MCSender // TODO: This is Minecraft only
+ && sendertype.isAssignableFrom((sender as Command2MCSender).sender.javaClass))
+ params.add((sender as Command2MCSender).sender)
+ else if ((ChromaGamerBase::class.java.isAssignableFrom(sendertype) && sender is Command2MCSender)
+ && ChromaGamerBase.getFromSender((sender as Command2MCSender).sender).also { cg = it } != null && cg.javaClass == sendertype) //The command expects a user of our system
+ params.add(cg) else {
+ val type = sendertype.simpleName.fold("") { s, ch -> s + if (ch.isUpperCase()) " " + ch.lowercase() else ch }
+ sender.sendMessage("§cYou need to be a $type to use this command.")
+ sender.sendMessage(sd.getHelpText(sender)) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
+ return true
+ }
+ return false
+ }
- /**
- * Adds a param converter that obtains a specific object from a string parameter.
- * The converter may return null.
- *
- * @param The type of the result
- * @param cl The class of the result object
- * @param converter The converter to use
- * @param allSupplier The supplier of all possible values (ideally)
- */
- public void addParamConverter(Class cl, Function converter, String errormsg,
- Supplier> allSupplier) {
- paramConverters.put(cl, new ParamConverter<>(converter, errormsg, allSupplier));
- }
+ /**
+ * Register a command in the command system. The way this command gets registered may change depending on the implementation.
+ * Always invoke [.registerCommandSuper] when implementing this method.
+ *
+ * @param command The command to register
+ */
+ abstract fun registerCommand(command: TC)
- public boolean handleCommand(TP sender, String commandline) {
- var results = dispatcher.parse(commandline, sender);
- if (results.getReader().canRead()) {
- return false; // Unknown command
- }
- Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
- try {
- dispatcher.execute(results);
- } catch (CommandSyntaxException e) {
- sender.sendMessage(e.getMessage());
- } catch (Exception e) {
- TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
- }
- });
- return true; //We found a method
- }
+ /**
+ * Registers a command in the Command2 system, so it can be looked up and executed.
+ *
+ * @param command The command to register
+ * @return The Brigadier command node if you need it for something (like tab completion)
+ */
+ protected fun registerCommandSuper(command: TC): LiteralCommandNode {
+ var mainCommandNode: LiteralCommandNode? = null
+ for (meth in command.javaClass.getMethods()) {
+ val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
+ val methodPath = CommandUtils.getCommandPath(meth.name, ' ')
+ val result = registerNodeFromPath(command!!.commandPath + methodPath)
+ result.value0.addChild(getExecutableNode(meth, command, ann, result.value2, CommandArgumentHelpManager(command)))
+ if (mainCommandNode == null) mainCommandNode = result.value1 else if (result.value1!!.name != mainCommandNode.name) {
+ MainPlugin.Instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName)
+ }
+ }
+ if (mainCommandNode == null) {
+ throw RuntimeException("There are no subcommands defined in the command class " + command.javaClass.getSimpleName() + "!")
+ }
+ return mainCommandNode
+ }
- //Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
+ /**
+ * Returns the node that can actually execute the given subcommand.
+ *
+ * @param method The subcommand method
+ * @param command The command object
+ * @param path The command path
+ * @return The executable node
+ */
+ private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager): LiteralCommandNode {
+ val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager) // Param order is important
+ val params = paramsAndSenderType.value0
+ val paramMap = HashMap()
+ for (param in params) {
+ paramMap[param!!.name] = param
+ }
+ val node = CoreCommandBuilder.literal(path, params[0]!!.type, paramMap, params, command)
+ .helps(command!!.getHelpText(method, ann)).permits { sender: TP -> hasPermission(sender, command, method) }
+ .executes { context: CommandContext -> executeCommand(context) }
+ var parent: ArgumentBuilder = node
+ for (param in params) { // Register parameters in the right order
+ parent.then(CoreArgumentBuilder.argument(param!!.name, getArgumentType(param), param.optional).also { parent = it })
+ }
+ return node.build()
+ }
- //TODO: Add to the help
+ /**
+ * Registers all necessary no-op nodes for the given path.
+ *
+ * @param path The full command path
+ * @return The last no-op node that can be used to register the executable node,
+ * the main command node and the last part of the command path (that isn't registered yet)
+ */
+ private fun registerNodeFromPath(path: String): Triplet, LiteralCommandNode?, String> {
+ val split = path.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ var parent: CommandNode = dispatcher.root
+ var mainCommand: LiteralCommandNode? = null
+ for (i in 0 until split.size - 1) {
+ val part = split[i]
+ val child = parent.getChild(part)
+ if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp(part).executes { context: CommandContext -> executeHelpText(context) }.build().also { parent = it }) else parent = child
+ if (i == 0) mainCommand = parent as LiteralCommandNode // Has to be a literal, if not, well, error
+ }
+ return Triplet(parent, mainCommand, split[split.size - 1])
+ }
- private boolean processSenderType(TP sender, SubcommandData sd, ArrayList
@@ -97,31 +81,4 @@
provided
-
-
-
- intellij-idea-only
-
-
- idea.maven.embedder.version
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- 11
-
-
-
-
-
-
-
diff --git a/pom.xml b/pom.xml
index eb8cfde..af71e1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,8 +9,8 @@
pom
master-SNAPSHOT
- 1.18.12
-
+ 1.18.26
+
Chroma Parent
@@ -27,7 +27,7 @@
maven-compiler-plugin
3.8.1
- 8
+ 17