Convert some code to Kotlin

Why not
Lombok didn't work and I had other frustations with Java streams
Removed some unnecessary tab complete code
This commit is contained in:
Norbi Peti 2023-02-20 22:31:14 +01:00
parent 9a859de583
commit 0bf1f9789b
9 changed files with 1143 additions and 1250 deletions

View file

@ -35,11 +35,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>8</source>
<target>8</target>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>

View file

@ -27,31 +27,62 @@
</resources>
<finalName>Chroma-Core</finalName>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/main/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<configuration>
<artifactSet>
<includes>
<include>me.lucko:commodore</include>
<include>org.javatuples:javatuples</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>me.lucko.commodore</pattern>
<!-- vvv Replace with the package of your plugin vvv -->
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</artifactSet>
<relocations>
<relocation>
<pattern>me.lucko.commodore</pattern>
<!-- vvv Replace with the package of your plugin vvv -->
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
@ -200,6 +231,11 @@
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
<organization>
<name>TBMCPlugins</name>
@ -217,6 +253,7 @@
<github.global.server>github</github.global.server>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<noprefix.version>1.0.1</noprefix.version>
<kotlin.version>1.8.10</kotlin.version>
</properties>
<scm>
<url>https://github.com/TBMCPlugins/mvn-repo</url>

View file

@ -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<Component<?>> 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<Component<*>>()
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<String>(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.map<String> { str: String -> "# " + str.trim { it <= ' ' } }.toArray<String> { _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<ConfigOpts, Boolean> { obj: ConfigOpts -> obj.disableConfigGen() }).orElse(false)
}
}
}

View file

@ -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<TP extends JavaPlugin> {
@SuppressWarnings("rawtypes") private static HashMap<Class<? extends Component>, 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<TP : JavaPlugin?> {
@Getter
private var enabled = false
public final ConfigData<Boolean> 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)}.<br>
* Make sure to register the dependencies first.<br>
* The component will be enabled automatically, regardless of when it was registered.<br>
* <b>If not using {@link ButtonPlugin}, call {@link ComponentManager#unregComponents(ButtonPlugin)} on plugin disable.</b>
*
* @param component The component to register
* @return Whether the component is registered successfully (it may have failed to enable)
*/
public static <T extends JavaPlugin> boolean registerComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, true);
}
@Getter
private val config = IHaveConfig(null)
/**
* Unregisters a component by calling {@link #unregister(JavaPlugin)}.<br>
* Make sure to unregister the dependencies last.<br>
* <b>Components will be unregistered in opposite order of registering by default by {@link ButtonPlugin} or {@link ComponentManager#unregComponents(ButtonPlugin)}.</b>
*
* @param component The component to unregister
* @return Whether the component is unregistered successfully (it also got disabled)
*/
public static <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, false);
}
@Getter
private val data //TODO
: IHaveConfig? = null
public static <T extends JavaPlugin> boolean registerUnregisterComponent(T plugin, Component<T> 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<ComponentMetadata, Boolean> { 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<br>
*
* @return The currently registered components
*/
@SuppressWarnings("rawtypes")
public static Map<Class<? extends Component>, 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.<br></br>
* 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.<br>
* 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<String, Consumer<IHaveConfig?>>): Map<String, IHaveConfig> {
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<String?, Any?> -> value is ConfigurationSection }
.collect(Collectors.toMap<Map.Entry<String?, Any?>, String, IHaveConfig>(Function<Map.Entry<String?, Any?>, String> { (key1, value) -> java.util.Map.Entry.key }, Function<Map.Entry<String?, Any?>, IHaveConfig> { (_, value): Map.Entry<String?, Any?> ->
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<Class<out Component<*>>, Component<out JavaPlugin>>()
/**
* 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<String, IHaveConfig> getConfigMap(String key, Map<String, Consumer<IHaveConfig>> 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].<br></br>
* Make sure to register the dependencies first.<br></br>
* The component will be enabled automatically, regardless of when it was registered.<br></br>
* **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 <T : JavaPlugin?> registerComponent(plugin: T, component: Component<T>): Boolean {
return registerUnregisterComponent(plugin, component, true)
}
private String getClassName() {
return getClass().getSimpleName();
}
/**
* Unregisters a component by calling [.unregister].<br></br>
* Make sure to unregister the dependencies last.<br></br>
* **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 <T : JavaPlugin?> unregisterComponent(plugin: T, component: Component<T>): Boolean {
return registerUnregisterComponent(plugin, component, false)
}
fun <T : JavaPlugin?> registerUnregisterComponent(plugin: T, component: Component<T>, register: Boolean): Boolean {
return try {
val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java)
if (metaAnn != null) {
val dependencies: Array<Class<out Component<*>>> = 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<br></br>
*
* @return The currently registered components
*/
@JvmStatic
fun getComponents(): Map<Class<out Component<*>>, Component<out JavaPlugin>> {
return Collections.unmodifiableMap(components)
}
}
}

View file

@ -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<TC extends ICommand2<TP>, TP extends Command2Sender> {
abstract class Command2<TC : ICommand2<TP>, 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<String> = [],
/**
* 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<String> = []) {
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<T>(val converter: Function<String, T>, val errormsg: String, val allSupplier: Supplier<Iterable<String>>)
/**
* 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<Class<*>, ParamConverter<*>>()
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
private val dispatcher = CommandDispatcher<TP>()
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<T> {
public final Function<String, T> converter;
public final String errormsg;
public final Supplier<Iterable<String>> allSupplier;
}
/**
* Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null.
*
* @param <T> 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)
</T> */
open fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String,
allSupplier: Supplier<Iterable<String>>) {
paramConverters[cl] = ParamConverter<T>(converter, errormsg, allSupplier)
}
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private final CommandDispatcher<TP> 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<TC, TP>, params: ArrayList<Any>): 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 <T> 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 <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg,
Supplier<Iterable<String>> 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<TP> {
var mainCommandNode: LiteralCommandNode<TP>? = null
for (meth in command.javaClass.getMethods()) {
val ann = meth.getAnnotation<Subcommand>(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<TC, TP>): LiteralCommandNode<TP> {
val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager) // Param order is important
val params = paramsAndSenderType.value0
val paramMap = HashMap<String, CommandArgument?>()
for (param in params) {
paramMap[param!!.name] = param
}
val node = CoreCommandBuilder.literal<TP, TC>(path, params[0]!!.type, paramMap, params, command)
.helps(command!!.getHelpText(method, ann)).permits { sender: TP -> hasPermission(sender, command, method) }
.executes { context: CommandContext<TP> -> executeCommand(context) }
var parent: ArgumentBuilder<TP, *> = 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<CommandNode<TP>, LiteralCommandNode<TP>?, String> {
val split = path.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var parent: CommandNode<TP> = dispatcher.root
var mainCommand: LiteralCommandNode<TP>? = 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<TP, TC>(part).executes { context: CommandContext<TP> -> executeHelpText(context) }.build().also { parent = it }) else parent = child
if (i == 0) mainCommand = parent as LiteralCommandNode<TP> // Has to be a literal, if not, well, error
}
return Triplet(parent, mainCommand, split[split.size - 1])
}
private boolean processSenderType(TP sender, SubcommandData<TC, TP> sd, ArrayList<Object> params) {
val sendertype = sd.senderType;
final ChromaGamerBase cg;
if (sendertype.isAssignableFrom(sender.getClass()))
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
else if (sender instanceof Command2MCSender // TODO: This is Minecraft only
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
String type = sendertype.getSimpleName().chars().mapToObj(ch -> Character.isUpperCase(ch)
? " " + Character.toLowerCase(ch)
: ch + "").collect(Collectors.joining());
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;
}
/**
* Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info.
* The first parameter is always the sender both in the methods themselves and in the returned array.
*
* @param method The method the subcommand is created from
* @return Parameter data objects and the sender type
* @throws RuntimeException If there is no sender parameter declared in the method
*/
private fun getCommandParametersAndSender(method: Method, argHelpManager: CommandArgumentHelpManager<TC, TP>): Pair<Array<CommandArgument?>, Class<*>> {
val parameters = method.parameters
if (parameters.size == 0) throw RuntimeException("No sender parameter for method '$method'")
val ret = arrayOfNulls<CommandArgument>(parameters.size)
val usage = argHelpManager.getParameterHelpForMethod(method)
val paramNames = usage?.split(" ".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
for (i in 1 until parameters.size) {
val numAnn = parameters[i].getAnnotation(NumberArg::class.java)
ret[i - 1] = CommandArgument(paramNames?.get(i) ?: "param$i", parameters[i].type,
parameters[i].isVarArgs || parameters[i].isAnnotationPresent(TextArg::class.java),
if (numAnn == null) null else Pair(numAnn.lowerLimit(), numAnn.upperLimit()),
parameters[i].isAnnotationPresent(OptionalArg::class.java),
paramNames?.get(i) ?: "param$i") // TODO: Description (JavaDoc?)
}
return Pair(ret, parameters[0].type)
}
/**
* Register a command in the command system. The way this command gets registered may change depending on the implementation.
* Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method.
*
* @param command The command to register
*/
public abstract void registerCommand(TC command);
/**
* Converts the Chroma representation of the argument declaration into Brigadier format.
* It does part of the command argument type processing.
*
* @param arg Our representation of the command argument
* @return The Brigadier representation of the command argument
*/
private fun getArgumentType(arg: CommandArgument?): ArgumentType<*> {
val ptype = arg!!.type
val lowerLimit: Number = arg.limits.value0
val upperLimit: Number = arg.limits.value1
return if (arg.greedy) StringArgumentType.greedyString() else if (ptype == String::class.java) StringArgumentType.word() else if (ptype == Int::class.javaPrimitiveType || ptype == Int::class.java || ptype == Byte::class.javaPrimitiveType || ptype == Byte::class.java || ptype == Short::class.javaPrimitiveType || ptype == Short::class.java) IntegerArgumentType.integer(lowerLimit.toInt(), upperLimit.toInt()) else if (ptype == Long::class.javaPrimitiveType || ptype == Long::class.java) LongArgumentType.longArg(lowerLimit.toLong(), upperLimit.toLong()) else if (ptype == Float::class.javaPrimitiveType || ptype == Float::class.java) FloatArgumentType.floatArg(lowerLimit.toFloat(), upperLimit.toFloat()) else if (ptype == Double::class.javaPrimitiveType || ptype == Double::class.java) DoubleArgumentType.doubleArg(lowerLimit.toDouble(), upperLimit.toDouble()) else if (ptype == Char::class.javaPrimitiveType || ptype == Char::class.java) StringArgumentType.word() else if (ptype == Boolean::class.javaPrimitiveType || ptype == Boolean::class.java) BoolArgumentType.bool() else {
StringArgumentType.word()
}
}
/**
* 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 LiteralCommandNode<TP> registerCommandSuper(TC command) {
LiteralCommandNode<TP> mainCommandNode = null;
for (val meth : command.getClass().getMethods()) {
val ann = meth.getAnnotation(Subcommand.class);
if (ann == null) continue;
String methodPath = getCommandPath(meth.getName(), ' ');
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2(), new CommandArgumentHelpManager<>(command)));
if (mainCommandNode == null) mainCommandNode = result.getValue1();
else if (!result.getValue1().getName().equals(mainCommandNode.getName())) {
MainPlugin.Instance.getLogger().warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.getClass().getSimpleName());
}
}
if (mainCommandNode == null) {
throw new RuntimeException("There are no subcommands defined in the command class " + command.getClass().getSimpleName() + "!");
}
return mainCommandNode;
}
/**
* Displays the help text based on the executed command. Each command node might have a help text stored.
* The help text is displayed either because of incorrect usage or it's explicitly requested.
*
* @param context The command context
* @return Vanilla command success level (0)
*/
private fun executeHelpText(context: CommandContext<TP>): Int {
println("""
Nodes:
${context.nodes.stream().map { node: ParsedCommandNode<TP> -> node.node.name + "@" + node.range }.collect(Collectors.joining("\n"))}
""".trimIndent())
return 0
}
/**
* 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 LiteralCommandNode<TP> getExecutableNode(Method method, TC command, Subcommand ann, String path, CommandArgumentHelpManager<TC, TP> argHelpManager) {
val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager); // Param order is important
val params = paramsAndSenderType.getValue0();
val paramMap = new HashMap<String, CommandArgument>();
for (val param : params) {
paramMap.put(param.name, param);
}
val node = CoreCommandBuilder.<TP, TC>literal(path, params[0].type, paramMap, params, command)
.helps(command.getHelpText(method, ann)).permits(sender -> hasPermission(sender, command, method))
.executes(this::executeCommand);
ArgumentBuilder<TP, ?> parent = node;
for (val param : params) { // Register parameters in the right order
parent.then(parent = CoreArgumentBuilder.argument(param.name, getArgumentType(param), param.optional));
}
return node.build();
}
/**
* Executes the command itself by calling the subcommand method associated with the input command node.
*
* @param context The command context
* @return Vanilla command success level (0)
*/
private fun executeCommand(context: CommandContext<TP>): Int {
println("Execute command")
println("Should be running sync: $runOnPrimaryThread")
/**
* 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 Triplet<CommandNode<TP>, LiteralCommandNode<TP>, String> registerNodeFromPath(String path) {
String[] split = path.split(" ");
CommandNode<TP> parent = dispatcher.getRoot();
LiteralCommandNode<TP> mainCommand = null;
for (int i = 0; i < split.length - 1; i++) {
String part = split[i];
var child = parent.getChild(part);
if (child == null)
parent.addChild(parent = CoreCommandBuilder.<TP, TC>literalNoOp(part).executes(this::executeHelpText).build());
else parent = child;
if (i == 0) mainCommand = (LiteralCommandNode<TP>) parent; // Has to be a literal, if not, well, error
}
return new Triplet<>(parent, mainCommand, split[split.length - 1]);
}
/**
* Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info.
* The first parameter is always the sender both in the methods themselves and in the returned array.
*
* @param method The method the subcommand is created from
* @return Parameter data objects and the sender type
* @throws RuntimeException If there is no sender parameter declared in the method
*/
private Pair<CommandArgument[], Class<?>> getCommandParametersAndSender(Method method, CommandArgumentHelpManager<TC, TP> argHelpManager) {
val parameters = method.getParameters();
if (parameters.length == 0)
throw new RuntimeException("No sender parameter for method '" + method + "'");
val ret = new CommandArgument[parameters.length];
val usage = argHelpManager.getParameterHelpForMethod(method);
val paramNames = usage != null ? usage.split(" ") : null;
for (int i = 1; i < parameters.length; i++) {
val numAnn = parameters[i].getAnnotation(NumberArg.class);
ret[i - 1] = new CommandArgument(paramNames == null ? "param" + i : paramNames[i], parameters[i].getType(),
parameters[i].isVarArgs() || parameters[i].isAnnotationPresent(TextArg.class),
numAnn == null ? null : new Pair<>(numAnn.lowerLimit(), numAnn.upperLimit()),
parameters[i].isAnnotationPresent(OptionalArg.class),
paramNames == null ? "param" + i : paramNames[i]); // TODO: Description (JavaDoc?)
}
return new Pair<>(ret, parameters[0].getType());
}
/**
* Converts the Chroma representation of the argument declaration into Brigadier format.
* It does part of the command argument type processing.
*
* @param arg Our representation of the command argument
* @return The Brigadier representation of the command argument
*/
private ArgumentType<?> getArgumentType(CommandArgument arg) {
final Class<?> ptype = arg.type;
Number lowerLimit = arg.limits.getValue0(), upperLimit = arg.limits.getValue1();
if (arg.greedy)
return StringArgumentType.greedyString();
else if (ptype == String.class)
return StringArgumentType.word();
else if (ptype == int.class || ptype == Integer.class
|| ptype == byte.class || ptype == Byte.class
|| ptype == short.class || ptype == Short.class)
return IntegerArgumentType.integer(lowerLimit.intValue(), upperLimit.intValue());
else if (ptype == long.class || ptype == Long.class)
return LongArgumentType.longArg(lowerLimit.longValue(), upperLimit.longValue());
else if (ptype == float.class || ptype == Float.class)
return FloatArgumentType.floatArg(lowerLimit.floatValue(), upperLimit.floatValue());
else if (ptype == double.class || ptype == Double.class)
return DoubleArgumentType.doubleArg(lowerLimit.doubleValue(), upperLimit.doubleValue());
else if (ptype == char.class || ptype == Character.class)
return StringArgumentType.word();
else if (ptype == boolean.class || ptype == Boolean.class)
return BoolArgumentType.bool();
else {
return StringArgumentType.word();
}
}
/**
* Displays the help text based on the executed command. Each command node might have a help text stored.
* The help text is displayed either because of incorrect usage or it's explicitly requested.
*
* @param context The command context
* @return Vanilla command success level (0)
*/
private int executeHelpText(CommandContext<TP> context) {
System.out.println("Nodes:\n" + context.getNodes().stream().map(node -> node.getNode().getName() + "@" + node.getRange()).collect(Collectors.joining("\n")));
return 0;
}
/**
* Executes the command itself by calling the subcommand method associated with the input command node.
*
* @param context The command context
* @return Vanilla command success level (0)
*/
private int executeCommand(CommandContext<TP> context) {
System.out.println("Execute command");
System.out.println("Should be running sync: " + runOnPrimaryThread);
/*if (!hasPermission(sender, sd.command, sd.method)) {
/*if (!hasPermission(sender, sd.command, sd.method)) {
sender.sendMessage("§cYou don't have permission to use this command");
return;
}
@ -327,8 +280,8 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
if (processSenderType(sender, sd, params, parameterTypes)) return; // Checks if the sender is the wrong type
val args = parsed.getContext().getArguments();
for (var arg : sd.arguments.entrySet()) {*/
// TODO: Invoke using custom method
/*if (pj == commandline.length() + 1) { //No param given
// TODO: Invoke using custom method
/*if (pj == commandline.length() + 1) { //No param given
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
if (cl.isPrimitive())
params.add(Defaults.defaultValue(cl));
@ -343,13 +296,13 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
return;
}
}*/
/*if (paramArr[i1].isVarArgs()) { - TODO: Varargs support? (colors?)
/*if (paramArr[i1].isVarArgs()) { - TODO: Varargs support? (colors?)
params.add(commandline.substring(j + 1).split(" +"));
continue;
}*/
// TODO: Character handling (strlen)
// TODO: Param converter
/*}
// TODO: Character handling (strlen)
// TODO: Param converter
/*}
Runnable invokeCommand = () -> {
try {
sd.method.setAccessible(true); //It may be part of a private class
@ -368,72 +321,53 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
if (sync)
Bukkit.getScheduler().runTask(MainPlugin.Instance, invokeCommand);
else
invokeCommand.run();*/
return 0;
}
invokeCommand.run();*/return 0
}
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
abstract fun hasPermission(sender: TP, command: TC, subcommand: Method?): Boolean
val commandsText: Array<String>
get() = commandHelp.toTypedArray()
public String[] getCommandsText() {
return commandHelp.toArray(new String[0]);
}
/**
* Get all registered command nodes. This returns all registered Chroma commands with all the information about them.
*
* @return A set of command node objects containing the commands
*/
val commandNodes: Set<CoreCommandNode<TP, TC?>?>
get() = dispatcher.root.children.stream().map { node: CommandNode<TP>? -> node as CoreCommandNode<TP, TC?>? }.collect(Collectors.toUnmodifiableSet())
/**
* Returns the path of the given subcommand excluding the class' path. It will start with the given replace char.
*
* @param methodName The method's name, method.getName()
* @param replaceChar The character to use between subcommands
* @return The command path starting with the replace char.
*/
@NotNull
public String getCommandPath(String methodName, char replaceChar) {
return methodName.equals("def") ? "" : replaceChar + methodName.replace('_', replaceChar).toLowerCase();
}
/**
* Get a node that belongs to the given command.
*
* @param command The exact name of the command
* @return A command node
*/
fun getCommandNode(command: String?): CoreCommandNode<TP, TC> {
return dispatcher.root.getChild(command) as CoreCommandNode<TP, TC>
}
/**
* Get all registered command nodes. This returns all registered Chroma commands with all the information about them.
*
* @return A set of command node objects containing the commands
*/
public Set<CoreCommandNode<TP, TC>> getCommandNodes() {
return dispatcher.getRoot().getChildren().stream().map(node -> (CoreCommandNode<TP, TC>) node).collect(Collectors.toUnmodifiableSet());
}
/**
* Unregister all subcommands that were registered with the given command class.
*
* @param command The command class (object) to unregister
*/
fun unregisterCommand(command: ICommand2<TP>) {
dispatcher.root.children.removeIf { node: CommandNode<TP> -> (node as CoreCommandNode<TP, TC>).data.command === command }
}
/**
* Get a node that belongs to the given command.
*
* @param command The exact name of the command
* @return A command node
*/
public CoreCommandNode<TP, TC> getCommandNode(String command) {
return (CoreCommandNode<TP, TC>) dispatcher.getRoot().getChild(command);
}
/**
* Unregisters all commands that match the given predicate.
*
* @param condition The condition for removing a given command
*/
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>?>, nested: Boolean) {
dispatcher.root.children.removeIf { node: CommandNode<TP>? -> condition.test(node as CoreCommandNode<TP, TC>?) }
if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child as CoreCommandNode<TP, TC>)
}
/**
* Unregister all subcommands that were registered with the given command class.
*
* @param command The command class (object) to unregister
*/
public void unregisterCommand(ICommand2<TP> command) {
dispatcher.getRoot().getChildren().removeIf(node -> ((CoreCommandNode<TP, TC>) node).getData().command == command);
}
/**
* Unregisters all commands that match the given predicate.
*
* @param condition The condition for removing a given command
*/
public void unregisterCommandIf(Predicate<CoreCommandNode<TP, TC>> condition, boolean nested) {
dispatcher.getRoot().getChildren().removeIf(node -> condition.test((CoreCommandNode<TP, TC>) node));
if (nested)
for (var child : dispatcher.getRoot().getChildren())
unregisterCommandIf(condition, (CoreCommandNode<TP, TC>) child);
}
private void unregisterCommandIf(Predicate<CoreCommandNode<TP, TC>> condition, CoreCommandNode<TP, TC> root) {
// Can't use getCoreChildren() here because the collection needs to be modifiable
root.getChildren().removeIf(node -> condition.test((CoreCommandNode<TP, TC>) node));
for (var child : root.getCoreChildren())
unregisterCommandIf(condition, child);
}
private fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>?>, root: CoreCommandNode<TP, TC>) {
// Can't use getCoreChildren() here because the collection needs to be modifiable
root.children.removeIf { node: CommandNode<TP>? -> condition.test(node as CoreCommandNode<TP, TC>?) }
for (child in root.coreChildren) unregisterCommandIf(condition, child)
}
}

View file

@ -1,438 +1,390 @@
package buttondevteam.lib.chat;
package buttondevteam.lib.chat
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.commands.SubcommandData;
import buttondevteam.lib.player.ChromaGamerBase;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.val;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.javatuples.Triplet;
import buttondevteam.core.MainPlugin
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component
import buttondevteam.lib.chat.commands.CommandUtils
import buttondevteam.lib.chat.commands.SubcommandData
import buttondevteam.lib.player.ChromaGamerBase
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.builder.RequiredArgumentBuilder
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.Suggestion
import com.mojang.brigadier.suggestion.SuggestionProvider
import com.mojang.brigadier.suggestion.Suggestions
import com.mojang.brigadier.suggestion.SuggestionsBuilder
import com.mojang.brigadier.tree.ArgumentCommandNode
import com.mojang.brigadier.tree.CommandNode
import com.mojang.brigadier.tree.LiteralCommandNode
import me.lucko.commodore.Commodore
import me.lucko.commodore.CommodoreProvider
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.OfflinePlayer
import org.bukkit.command.*
import org.bukkit.entity.Player
import org.bukkit.event.Listener
import org.bukkit.permissions.Permission
import org.bukkit.permissions.PermissionDefault
import org.javatuples.Triplet
import java.lang.reflect.Method
import java.lang.reflect.Parameter
import java.util.*
import java.util.function.BiConsumer
import java.util.function.Function
import java.util.function.Supplier
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
public Command2MC() {
super('/', true);
}
/**
* Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed.
*
* @param command The command to register
*/
@Override
public void registerCommand(ICommand2MC command) {
/*String mainpath;
class Command2MC : Command2<ICommand2MC?, Command2MCSender?>('/', true), Listener {
/**
* Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed.
*
* @param command The command to register
*/
override fun registerCommand(command: ICommand2MC) {
/*String mainpath;
var plugin = command.getPlugin();
{
String cpath = command.getCommandPath();
int i = cpath.indexOf(' ');
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
}*/
var commandNode = super.registerCommandSuper(command);
var bcmd = registerOfficially(command, commandNode);
if (bcmd != null) // TODO: Support aliases
super.registerCommandSuper(command);
val commandNode = super.registerCommandSuper(command)
val bcmd = registerOfficially(command, commandNode)
if (bcmd != null) // TODO: Support aliases
super.registerCommandSuper(command)
val perm = "chroma.command." + command.commandPath.replace(' ', '.')
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(Permission(perm,
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
for (method in command.javaClass.methods) {
if (!method.isAnnotationPresent(Subcommand::class.java)) continue
val path = CommandUtils.getCommandPath(method.name, '.')
if (path.length > 0) {
val subperm = perm + path
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(Permission(subperm,
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
}
val pg = permGroup(command, method)
if (pg.length == 0) continue
val permGroup = "chroma.$pg"
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(Permission(permGroup,
PermissionDefault.OP)) //Do not allow any commands that belong to a group
}
}
var perm = "chroma.command." + command.getCommandPath().replace(' ', '.');
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(new Permission(perm,
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
for (val method : command.getClass().getMethods()) {
if (!method.isAnnotationPresent(Subcommand.class)) continue;
var path = getCommandPath(method.getName(), '.');
if (path.length() > 0) {
var subperm = perm + path;
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(new Permission(subperm,
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
}
String pg = permGroup(command, method);
if (pg.length() == 0) continue;
String permGroup = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(permGroup,
PermissionDefault.OP)); //Do not allow any commands that belong to a group
}
}
override fun hasPermission(sender: Command2MCSender, command: ICommand2MC, method: Method): Boolean {
return hasPermission(sender.sender, command, method)
}
@Override
public boolean hasPermission(Command2MCSender sender, ICommand2MC command, Method method) {
return hasPermission(sender.getSender(), command, method);
}
fun hasPermission(sender: CommandSender, command: ICommand2MC?, method: Method): Boolean {
if (sender is ConsoleCommandSender) return true //Always allow the console
if (command == null) return true //Allow viewing the command - it doesn't do anything anyway
var pg: String
var p = true
val cmdperm = "chroma.command." + command.commandPath.replace(' ', '.')
val path = CommandUtils.getCommandPath(method.name, '.')
val perms = arrayOf(
if (path.length > 0) cmdperm + path else null,
cmdperm,
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
)
for (perm in perms) {
if (perm != null) {
if (p) { //Use OfflinePlayer to avoid fetching player data
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(if (sender is Player) sender.location.world.name else null, sender as OfflinePlayer, perm) else false //Use sender's method
if (!p) p = sender.hasPermission(perm)
} else break //If any of the permissions aren't granted then don't allow
}
}
return p
}
public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) {
if (sender instanceof ConsoleCommandSender) return true; //Always allow the console
if (command == null) return true; //Allow viewing the command - it doesn't do anything anyway
String pg;
boolean p = true;
var cmdperm = "chroma.command." + command.getCommandPath().replace(' ', '.');
var path = getCommandPath(method.getName(), '.');
String[] perms = {
path.length() > 0 ? cmdperm + path : null,
cmdperm,
(pg = permGroup(command, method)).length() > 0 ? "chroma." + pg : null
};
for (String perm : perms) {
if (perm != null) {
if (p) { //Use OfflinePlayer to avoid fetching player data
if (sender instanceof OfflinePlayer)
p = MainPlugin.permission.playerHas(sender instanceof Player ? ((Player) sender).getLocation().getWorld().getName() : null, (OfflinePlayer) sender, perm);
else
p = false; //Use sender's method
if (!p) p = sender.hasPermission(perm);
} else break; //If any of the permissions aren't granted then don't allow
}
}
return p;
}
/**
* Returns the first group found in the hierarchy starting from the command method **or** the mod group if *any* of the superclasses are mod only.
*
* @param method The subcommand to check
* @return The permission group for the subcommand or empty string
*/
private fun permGroup(command: ICommand2MC, method: Method?): String {
if (method != null) {
val sc = method.getAnnotation(Subcommand::class.java)
if (sc != null && sc.permGroup().length > 0) {
return sc.permGroup()
}
}
return if (getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.modOnly() }, false)) Subcommand.MOD_GROUP else getAnnForValue(command.javaClass, CommandClass::class.java, Function<CommandClass, String> { obj: CommandClass -> obj.permGroup() }, "")
}
/**
* Returns the first group found in the hierarchy starting from the command method <b>or</b> the mod group if <i>any</i></i> of the superclasses are mod only.
*
* @param method The subcommand to check
* @return The permission group for the subcommand or empty string
*/
private String permGroup(ICommand2MC command, Method method) {
if (method != null) {
val sc = method.getAnnotation(Subcommand.class);
if (sc != null && sc.permGroup().length() > 0) {
return sc.permGroup();
}
}
if (getAnnForValue(command.getClass(), CommandClass.class, CommandClass::modOnly, false))
return Subcommand.MOD_GROUP;
return getAnnForValue(command.getClass(), CommandClass.class, CommandClass::permGroup, "");
}
/**
* Loops until it finds a value that is **not** the same as def
*
* @param sourceCl The class which has the annotation
* @param annCl The annotation to get
* @param annMethod The annotation method to check
* @param def The value to ignore when looking for the result
* @param <T> The annotation type
* @param <V> The type of the value
* @return The value returned by the first superclass or def
</V></T> */
private fun <T : Annotation?, V> getAnnForValue(sourceCl: Class<*>, annCl: Class<T>, annMethod: Function<T, V>, def: V): V {
var cl: Class<*>? = sourceCl
while (cl != null) {
val cc = cl.getAnnotation(annCl)
var r: V
if (cc != null && annMethod.apply(cc).also { r = it } !== def) return r
cl = cl.superclass
}
return def
}
/**
* Loops until it finds a value that is <b>not</b> the same as def
*
* @param sourceCl The class which has the annotation
* @param annCl The annotation to get
* @param annMethod The annotation method to check
* @param def The value to ignore when looking for the result
* @param <T> The annotation type
* @param <V> The type of the value
* @return The value returned by the first superclass or def
*/
private <T extends Annotation, V> V getAnnForValue(Class<?> sourceCl, Class<T> annCl, Function<T, V> annMethod, V def) {
for (Class<?> cl = sourceCl; cl != null; cl = cl.getSuperclass()) {
val cc = cl.getAnnotation(annCl);
V r;
if (cc != null && (r = annMethod.apply(cc)) != def) return r;
}
return def;
}
/**
* Automatically colors the message red.
* {@see super#addParamConverter}
*/
override fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String, allSupplier: Supplier<Iterable<String>>) {
super.addParamConverter(cl, converter, "§c$errormsg", allSupplier)
}
/**
* Automatically colors the message red.
* {@see super#addParamConverter}
*/
@Override
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
}
fun unregisterCommands(plugin: ButtonPlugin) {
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> -> Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }.map { obj: ButtonPlugin? -> plugin.equals(obj) }.orElse(false) }, true)
}
public void unregisterCommands(ButtonPlugin plugin) {
unregisterCommandIf(node -> Optional.ofNullable(node.getData().command).map(ICommand2MC::getPlugin).map(plugin::equals).orElse(false), true);
}
fun unregisterCommands(component: Component<*>) {
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> ->
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
}, true)
}
public void unregisterCommands(Component<?> component) {
unregisterCommandIf(node -> Optional.ofNullable(node.getData().command).map(ICommand2MC::getPlugin)
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false), true);
}
override fun handleCommand(sender: Command2MCSender, commandline: String): Boolean {
return handleCommand(sender, commandline, true)
}
@Override
public boolean handleCommand(Command2MCSender sender, String commandline) {
return handleCommand(sender, commandline, true);
}
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
val i = commandline.indexOf(' ')
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash
var pcmd: PluginCommand
return if ((!checkPlugin
|| MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands
|| pcmd.plugin is ButtonPlugin) //Unless it's specified in the plugin.yml
super.handleCommand(sender, commandline) else false
}
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
int i = commandline.indexOf(' ');
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
PluginCommand pcmd;
if (!checkPlugin
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
return super.handleCommand(sender, commandline);
else
return false;
}
private var shouldRegisterOfficially = true
private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode<Command2MCSender>): Command? {
return if (!shouldRegisterOfficially || command.plugin == null) null else try {
val cmdmap = Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap
val path = command.commandPath
val x = path.indexOf(' ')
val mainPath = path.substring(0, if (x == -1) path.length else x)
var bukkitCommand: Command
run {
//Commands conflicting with Essentials have to be registered in plugin.yml
val oldcmd = cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = BukkitCommand(mainPath)
cmdmap.register(command.plugin.name, bukkitCommand)
} else {
bukkitCommand = oldcmd
if (bukkitCommand is PluginCommand) (bukkitCommand as PluginCommand).executor = CommandExecutor { sender: CommandSender, command: Command, label: String, args: Array<String> -> this.executeCommand(sender, command, label, args) }
}
bukkitCommand = oldcmd ?: BukkitCommand(mainPath)
}
if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand)
bukkitCommand
} catch (e: Exception) {
if (command.component == null) TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin) else TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component)
shouldRegisterOfficially = false
null
}
}
private boolean shouldRegisterOfficially = true;
private fun executeCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
val user = ChromaGamerBase.getFromSender(sender)
if (user == null) {
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", Throwable("No Chroma user found"), MainPlugin.Instance)
sender.sendMessage("§cAn internal error occurred.")
return true
}
handleCommand(Command2MCSender(sender, user.channel.get(), sender),
("/" + command.name + " " + java.lang.String.join(" ", *args)).trim { it <= ' ' }, false) ///trim(): remove space if there are no args
return true
}
private Command registerOfficially(ICommand2MC command, LiteralCommandNode<Command2MCSender> node) {
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
try {
var cmdmap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
var path = command.getCommandPath();
int x = path.indexOf(' ');
var mainPath = path.substring(0, x == -1 ? path.length() : x);
Command bukkitCommand;
{ //Commands conflicting with Essentials have to be registered in plugin.yml
var oldcmd = cmdmap.getCommand(command.getPlugin().getName() + ":" + mainPath); //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = new BukkitCommand(mainPath);
cmdmap.register(command.getPlugin().getName(), bukkitCommand);
} else {
bukkitCommand = oldcmd;
if (bukkitCommand instanceof PluginCommand)
((PluginCommand) bukkitCommand).setExecutor(this::executeCommand);
}
bukkitCommand = oldcmd == null ? new BukkitCommand(mainPath) : oldcmd;
}
if (CommodoreProvider.isSupported())
TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand);
return bukkitCommand;
} catch (Exception e) {
if (command.getComponent() == null)
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
else
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
shouldRegisterOfficially = false;
return null;
}
}
private class BukkitCommand(name: String?) : Command(name) {
override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean {
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args)
}
private boolean executeCommand(CommandSender sender, Command command, String label, String[] args) {
var user = ChromaGamerBase.getFromSender(sender);
if (user == null) {
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", new Throwable("No Chroma user found"), MainPlugin.Instance);
sender.sendMessage("§cAn internal error occurred.");
return true;
}
handleCommand(new Command2MCSender(sender, user.channel.get(), sender),
("/" + command.getName() + " " + String.join(" ", args)).trim(), false); ///trim(): remove space if there are no args
return true;
}
@Throws(IllegalArgumentException::class)
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>): List<String> {
return emptyList()
}
private static class BukkitCommand extends Command {
protected BukkitCommand(String name) {
super(name);
}
@Throws(IllegalArgumentException::class)
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>, location: Location): List<String> {
return emptyList()
}
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args);
}
private object TabcompleteHelper {
private var commodore: Commodore? = null
private fun appendSubcommand(path: String, parent: CommandNode<Any>,
subcommand: SubcommandData<ICommand2MC>?): LiteralCommandNode<Any> {
var scmd: LiteralCommandNode<Any>
if (parent.getChild(path) as LiteralCommandNode<kotlin.Any?>?. also { scmd = it } != null) return scmd
val scmdBuilder = LiteralArgumentBuilder.literal<Any>(path)
if (subcommand != null) scmdBuilder.requires { o: Any? ->
val sender = commodore!!.getBukkitSender(o)
subcommand.hasPermission(sender)
}
scmd = scmdBuilder.build()
parent.addChild(scmd)
return scmd
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
return Collections.emptyList();
}
private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode<Command2MCSender>, bukkitCommand: Command) {
if (commodore == null) {
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance) //Register all to the Core, it's easier
commodore.register(LiteralArgumentBuilder.literal<Any?>("un").redirect(RequiredArgumentBuilder.argument<Any?, String>("unsomething",
StringArgumentType.word()).suggests { context: CommandContext<Any?>?, builder: SuggestionsBuilder -> builder.suggest("untest").buildFuture() }.build()))
}
commodore!!.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary
val customTCmethods = Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments
.flatMap { method: Method ->
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream()
.flatMap { ctcmAnn: CustomTabCompleteMethod ->
val paths = Optional.of<Array<String?>>(ctcmAnn.subcommand()).filter { s: Array<String?> -> s.size > 0 }
.orElseGet {
arrayOf(
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }
)
}
Arrays.stream(paths).map { name: String? -> Triplet(name, ctcmAnn, method) }
}
}.toList()
for (subcmd in subcmds) {
val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' }
val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
var scmd: CommandNode<Any> = cmd
if (subpath[0].length > 0) { //If the method is def, it will contain one empty string
for (s in subpath) {
scmd = appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple())
}
}
val parameters: Array<Parameter> = subcmd.method.getParameters()
for (i in 1 until parameters.size) { //Skip sender
val parameter = parameters[i]
val customParamType: Boolean
// TODO: Arg type
val param: Any = subcmd.parameters.get(i - 1)
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java))
.map(Function<CustomTabComplete, Array<String>> { obj: CustomTabComplete -> obj.value() })
val customTCmethod = customTCmethods.stream().filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> subpathAsOne.equals(t.value0, ignoreCase = true) }
.filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.value1.param()) }
.findAny()
val argb: RequiredArgumentBuilder<S, T> = RequiredArgumentBuilder.argument(param, type)
.suggests(SuggestionProvider<S?> { context: CommandContext<S?>, builder: SuggestionsBuilder ->
if (parameter.isVarArgs) { //Do it before the builder is used
val nextTokenStart = context.getInput().lastIndexOf(' ') + 1
builder = builder.createOffset(nextTokenStart)
}
if (customTC.isPresent) for (ctc in customTC.get()) builder.suggest(ctc)
var ignoreCustomParamType = false
if (customTCmethod.isPresent) {
val tr = customTCmethod.get()
if (tr.value1.ignoreTypeCompletion()) ignoreCustomParamType = true
val method = tr.value2
val params = method.parameters
val args = arrayOfNulls<Any>(params.size)
var j = 0
var k = 0
while (j < args.size && k < subcmd.parameters.length) {
val paramObj = params[j]
if (CommandSender::class.java.isAssignableFrom(paramObj.type)) {
args[j] = commodore!!.getBukkitSender(context.getSource())
j++
continue
}
val paramValueString = context.getArgument(subcmd.parameters.get(k), String::class.java)
if (paramObj.type == String::class.java) {
args[j] = paramValueString
j++
continue
}
val converter = getParamConverter(params[j].type, command2MC) ?: break
val paramValue = converter.converter.apply(paramValueString)
?: //For example, the player provided an invalid plugin name
break
args[j] = paramValue
k++ //Only increment if not CommandSender
j++
}
if (args.size == 0 || args[args.size - 1] != null) { //Arguments filled entirely
try {
val suggestions = method.invoke(command2MC, *args)
if (suggestions is Iterable<*>) {
for (suggestion in suggestions) if (suggestion is String) builder.suggest(suggestion as String?) else throw ClassCastException("Bad return type! It should return an Iterable<String> or a String[].")
} else if (suggestions is Array<String>) for (suggestion in suggestions as Array<String?>) builder.suggest(suggestion) else throw ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.")
} catch (e: Exception) {
val msg = "Failed to run tabcomplete method " + method.name + " for command " + command2MC.javaClass.simpleName
if (command2MC.component == null) TBMCCoreAPI.SendException(msg, e, command2MC.plugin) else TBMCCoreAPI.SendException(msg, e, command2MC.component)
}
}
}
if (!ignoreCustomParamType && customParamType) {
val converter = getParamConverter(ptype, command2MC)
if (converter != null) {
val suggestions = converter.allSupplier.get()
for (suggestion in suggestions) builder.suggest(suggestion)
}
}
if (ptype === Boolean::class.javaPrimitiveType || ptype === Boolean::class.java) builder.suggest("true").suggest("false")
val loweredInput = builder.remaining.lowercase(Locale.getDefault())
builder.suggest(param).buildFuture().whenComplete(BiConsumer<Suggestions, Throwable> { s: Suggestions, e: Throwable? -> //The list is automatically ordered
s.list.add(s.list.removeAt(0))
}) //So we need to put the <param> at the end after that
.whenComplete(BiConsumer<Suggestions, Throwable> { ss: Suggestions, e: Throwable? ->
ss.list.removeIf { s: Suggestion ->
val text = s.text
!text.startsWith("<") && !text.startsWith("[") && !text.lowercase(Locale.getDefault()).startsWith(loweredInput)
}
})
})
val arg: ArgumentCommandNode<S, T> = argb.build()
scmd.addChild(arg)
scmd = arg
}
}
if (shouldRegister.get()) {
commodore.register(maincmd)
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault())
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0))
.redirect(maincmd).build()
commodore!!.register(prefixedcmd)
for (alias in bukkitCommand.aliases) {
commodore!!.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
commodore!!.register(LiteralArgumentBuilder.literal<Any>("$pluginName:$alias").redirect(maincmd).build())
}
}
}
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
return Collections.emptyList();
}
}
private static class TabcompleteHelper {
private static Commodore commodore;
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
SubcommandData<ICommand2MC> subcommand) {
LiteralCommandNode<Object> scmd;
if ((scmd = (LiteralCommandNode<Object>) parent.getChild(path)) != null)
return scmd;
var scmdBuilder = LiteralArgumentBuilder.literal(path);
if (subcommand != null)
scmdBuilder.requires(o -> {
var sender = commodore.getBukkitSender(o);
return subcommand.hasPermission(sender);
});
scmd = scmdBuilder.build();
parent.addChild(scmd);
return scmd;
}
private static void registerTabcomplete(ICommand2MC command2MC, LiteralCommandNode<Command2MCSender> commandNode, Command bukkitCommand) {
if (commodore == null) {
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance); //Register all to the Core, it's easier
commodore.register(LiteralArgumentBuilder.literal("un").redirect(RequiredArgumentBuilder.argument("unsomething",
StringArgumentType.word()).suggests((context, builder) -> builder.suggest("untest").buildFuture()).build()));
}
String[] path = command2MC.getCommandPath().split(" ");
var shouldRegister = new AtomicBoolean(true);
@SuppressWarnings("unchecked") var maincmd = commodore.getRegisteredNodes().stream()
.filter(node -> node.getLiteral().equalsIgnoreCase(path[0]))
.filter(node -> {shouldRegister.set(false); return true;})
.map(node -> (LiteralCommandNode<Object>) node).findAny()
.orElseGet(() -> LiteralArgumentBuilder.literal(path[0]).build()); //Commodore 1.8 removes previous nodes
var cmd = maincmd;
for (int i = 1; i < path.length; i++) {
var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null);
cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one
}
final var customTCmethods = Arrays.stream(command2MC.getClass().getDeclaredMethods()) //val doesn't recognize the type arguments
.flatMap(method -> Stream.of(Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod.class)))
.filter(Optional::isPresent).map(Optional::get) // Java 9 has .stream()
.flatMap(ctcm -> {
var paths = Optional.of(ctcm.subcommand()).filter(s -> s.length > 0)
.orElseGet(() -> new String[]{
ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim()
});
return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm, method));
})).collect(Collectors.toList());
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim();
String[] subpath = subpathAsOne.split(" ");
CommandNode<Object> scmd = cmd;
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
for (String s : subpath) {
scmd = appendSubcommand(s, scmd, subcmd); //Add method name part of the path (could_be_multiple())
}
}
Parameter[] parameters = subcmd.method.getParameters();
for (int i = 1; i < parameters.length; i++) { //Skip sender
Parameter parameter = parameters[i];
final boolean customParamType;
// TODO: Arg type
val param = subcmd.parameters[i - 1];
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
.map(CustomTabComplete::value);
var customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0()))
.filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1().param()))
.findAny();
var argb = RequiredArgumentBuilder.argument(param, type)
.suggests((context, builder) -> {
if (parameter.isVarArgs()) { //Do it before the builder is used
int nextTokenStart = context.getInput().lastIndexOf(' ') + 1;
builder = builder.createOffset(nextTokenStart);
}
if (customTC.isPresent())
for (val ctc : customTC.get())
builder.suggest(ctc);
boolean ignoreCustomParamType = false;
if (customTCmethod.isPresent()) {
var tr = customTCmethod.get();
if (tr.getValue1().ignoreTypeCompletion())
ignoreCustomParamType = true;
final var method = tr.getValue2();
val params = method.getParameters();
val args = new Object[params.length];
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
val paramObj = params[j];
if (CommandSender.class.isAssignableFrom(paramObj.getType())) {
args[j] = commodore.getBukkitSender(context.getSource());
continue;
}
val paramValueString = context.getArgument(subcmd.parameters[k], String.class);
if (paramObj.getType() == String.class) {
args[j] = paramValueString;
continue;
}
val converter = getParamConverter(params[j].getType(), command2MC);
if (converter == null)
break;
val paramValue = converter.converter.apply(paramValueString);
if (paramValue == null) //For example, the player provided an invalid plugin name
break;
args[j] = paramValue;
k++; //Only increment if not CommandSender
}
if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely
try {
val suggestions = method.invoke(command2MC, args);
if (suggestions instanceof Iterable) {
//noinspection unchecked
for (Object suggestion : (Iterable<Object>) suggestions)
if (suggestion instanceof String)
builder.suggest((String) suggestion);
else
throw new ClassCastException("Bad return type! It should return an Iterable<String> or a String[].");
} else if (suggestions instanceof String[])
for (String suggestion : (String[]) suggestions)
builder.suggest(suggestion);
else
throw new ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.");
} catch (Exception e) {
String msg = "Failed to run tabcomplete method " + method.getName() + " for command " + command2MC.getClass().getSimpleName();
if (command2MC.getComponent() == null)
TBMCCoreAPI.SendException(msg, e, command2MC.getPlugin());
else
TBMCCoreAPI.SendException(msg, e, command2MC.getComponent());
}
}
}
if (!ignoreCustomParamType && customParamType) {
val converter = getParamConverter(ptype, command2MC);
if (converter != null) {
var suggestions = converter.allSupplier.get();
for (String suggestion : suggestions)
builder.suggest(suggestion);
}
}
if (ptype == boolean.class || ptype == Boolean.class)
builder.suggest("true").suggest("false");
final String loweredInput = builder.getRemaining().toLowerCase();
return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered
s.getList().add(s.getList().remove(0))) //So we need to put the <param> at the end after that
.whenComplete((ss, e) -> ss.getList().removeIf(s -> {
String text = s.getText();
return !text.startsWith("<") && !text.startsWith("[") && !text.toLowerCase().startsWith(loweredInput);
}));
});
var arg = argb.build();
scmd.addChild(arg);
scmd = arg;
}
}
if (shouldRegister.get()) {
commodore.register(maincmd);
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
String pluginName = command2MC.getPlugin().getName().toLowerCase();
var prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path[0])
.redirect(maincmd).build();
commodore.register(prefixedcmd);
for (String alias : bukkitCommand.getAliases()) {
commodore.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build());
commodore.register(LiteralArgumentBuilder.literal(pluginName + ":" + alias).redirect(maincmd).build());
}
}
}
}
private static ParamConverter<?> getParamConverter(Class<?> cl, ICommand2MC command2MC) {
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(cl);
if (converter == null) {
String msg = "Could not find a suitable converter for type " + cl.getSimpleName();
Exception exception = new NullPointerException("converter is null");
if (command2MC.getComponent() == null)
TBMCCoreAPI.SendException(msg, exception, command2MC.getPlugin());
else
TBMCCoreAPI.SendException(msg, exception, command2MC.getComponent());
return null;
}
return converter;
}
companion object {
private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? {
val converter = ButtonPlugin.getCommand2MC().paramConverters[cl]
if (converter == null) {
val msg = "Could not find a suitable converter for type " + cl.simpleName
val exception: Exception = NullPointerException("converter is null")
if (command2MC.component == null) TBMCCoreAPI.SendException(msg, exception, command2MC.plugin) else TBMCCoreAPI.SendException(msg, exception, command2MC.component)
return null
}
return converter
}
}
}

View file

@ -0,0 +1,19 @@
package buttondevteam.lib.chat.commands;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
@UtilityClass
public class CommandUtils {
/**
* Returns the path of the given subcommand excluding the class' path. It will start with the given replace char.
*
* @param methodName The method's name, method.getName()
* @param replaceChar The character to use between subcommands
* @return The command path starting with the replacement char.
*/
@NotNull
public static String getCommandPath(String methodName, char replaceChar) {
return methodName.equals("def") ? "" : replaceChar + methodName.replace('_', replaceChar).toLowerCase();
}
}

View file

@ -9,7 +9,7 @@
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.10</lombok.version>
<lombok.version>1.18.26</lombok.version>
</properties>
<name>Core POM for Chroma</name>
@ -21,30 +21,14 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<release>17</release>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version>
</annotationProcessorPath>
<annotationProcessorPath>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
</annotationProcessorPath>
<version>1.18.26</version>
</path>
</annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
@ -97,31 +81,4 @@
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>intellij-idea-only</id>
<activation>
<property>
<name>idea.maven.embedder.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
<!--
<compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs>
-->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -9,8 +9,8 @@
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.12</lombok.version>
</properties>
<lombok.version>1.18.26</lombok.version>
</properties>
<name>Chroma Parent</name>
<modules>
@ -27,7 +27,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<release>17</release>
</configuration>
</plugin>
<plugin>