diff --git a/src/main/java/buttondevteam/core/CommandCaller.java b/src/main/java/buttondevteam/core/CommandCaller.java index 42b5866..50a65a9 100644 --- a/src/main/java/buttondevteam/core/CommandCaller.java +++ b/src/main/java/buttondevteam/core/CommandCaller.java @@ -13,42 +13,36 @@ import buttondevteam.lib.chat.TBMCCommandBase; public class CommandCaller implements CommandExecutor { - private static final String REGISTER_ERROR_MSG = "An error occured while registering commands"; - private CommandCaller() { } private static CommandCaller instance; - public static void RegisterCommand(TBMCCommandBase cmd) { + public static void RegisterCommand(TBMCCommandBase cmd) throws Exception { if (instance == null) instance = new CommandCaller(); - if (cmd.GetCommandPath() == null) { - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, - new Exception("Command " + cmd.getClass().getSimpleName() + " has no command path!")); - return; - } - if (cmd.getPlugin() == null) { - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, - new Exception("Command " + cmd.GetCommandPath() + " has no plugin!")); - return; - } + String topcmd = cmd.GetCommandPath(); + if (topcmd == null) + throw new Exception("Command " + cmd.getClass().getSimpleName() + " has no command path!"); + if (cmd.getPlugin() == null) + throw new Exception("Command " + cmd.GetCommandPath() + " has no plugin!"); int i; - String topcmd; - if ((i = (topcmd = cmd.GetCommandPath()).indexOf(' ')) != -1) // Get top-level command - topcmd = cmd.GetCommandPath().substring(0, i); + if ((i = topcmd.indexOf(' ')) != -1) // Get top-level command + topcmd = topcmd.substring(0, i); { PluginCommand pc = ((JavaPlugin) cmd.getPlugin()).getCommand(topcmd); if (pc == null) - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, new Exception("Top level command " + topcmd - + " not registered in plugin.yml for plugin: " + cmd.getPlugin().getName())); + throw new Exception("Top level command " + topcmd + " not registered in plugin.yml for plugin: " + + cmd.getPlugin().getName()); else pc.setExecutor(instance); + System.out.println("Executor set"); } } @Override public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) { + System.out.println("onCommand called"); String path = command.getName().toLowerCase(); for (String arg : args) path += " " + arg; diff --git a/src/main/java/buttondevteam/lib/TBMCCoreAPI.java b/src/main/java/buttondevteam/lib/TBMCCoreAPI.java index 469095e..a80923c 100644 --- a/src/main/java/buttondevteam/lib/TBMCCoreAPI.java +++ b/src/main/java/buttondevteam/lib/TBMCCoreAPI.java @@ -201,8 +201,10 @@ public class TBMCCoreAPI { SendUnsentExceptions(); TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e); Bukkit.getPluginManager().callEvent(event); - if (!event.isHandled()) - exceptionsToSend.put(sourcemsg, e); + synchronized (exceptionsToSend) { + if (!event.isHandled()) + exceptionsToSend.put(sourcemsg, e); + } Bukkit.getLogger().warning(sourcemsg); e.printStackTrace(); if (debugPotato) { @@ -212,7 +214,6 @@ public class TBMCCoreAPI { devsOnline.add(player); } } - ; if (!devsOnline.isEmpty()) { DebugPotato potato = new DebugPotato() .setMessage(new String[] { // @@ -234,8 +235,10 @@ public class TBMCCoreAPI { SendUnsentDebugMessages(); TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(debugMessage); Bukkit.getPluginManager().callEvent(event); - if (!event.isSent()) - debugMessagesToSend.add(debugMessage); + synchronized (debugMessagesToSend) { + if (!event.isSent()) + debugMessagesToSend.add(debugMessage); + } } /** @@ -258,28 +261,32 @@ public class TBMCCoreAPI { * Send exceptions that haven't been sent (their events didn't get handled). This method is used by the DiscordPlugin's ready event */ public static void SendUnsentExceptions() { - if (exceptionsToSend.size() > 20) { - exceptionsToSend.clear(); // Don't call more and more events if all the handler plugins are unloaded - Bukkit.getLogger().warning("Unhandled exception list is over 20! Clearing!"); - } - for (Entry entry : exceptionsToSend.entrySet()) { - TBMCExceptionEvent event = new TBMCExceptionEvent(entry.getKey(), entry.getValue()); - Bukkit.getPluginManager().callEvent(event); - if (event.isHandled()) - exceptionsToSend.remove(entry.getKey()); + synchronized (exceptionsToSend) { + if (exceptionsToSend.size() > 20) { + exceptionsToSend.clear(); // Don't call more and more events if all the handler plugins are unloaded + Bukkit.getLogger().warning("Unhandled exception list is over 20! Clearing!"); + } + for (Entry entry : exceptionsToSend.entrySet()) { + TBMCExceptionEvent event = new TBMCExceptionEvent(entry.getKey(), entry.getValue()); + Bukkit.getPluginManager().callEvent(event); + if (event.isHandled()) + exceptionsToSend.remove(entry.getKey()); + } } } public static void SendUnsentDebugMessages() { - if (debugMessagesToSend.size() > 20) { - debugMessagesToSend.clear(); // Don't call more and more DebugMessages if all the handler plugins are unloaded - Bukkit.getLogger().warning("Unhandled Debug Message list is over 20! Clearing!"); - } - for (String message : debugMessagesToSend) { - TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(message); - Bukkit.getPluginManager().callEvent(event); - if (event.isSent()) - debugMessagesToSend.remove(message); + synchronized (debugMessagesToSend) { + if (debugMessagesToSend.size() > 20) { + debugMessagesToSend.clear(); // Don't call more and more DebugMessages if all the handler plugins are unloaded + Bukkit.getLogger().warning("Unhandled Debug Message list is over 20! Clearing!"); + } + for (String message : debugMessagesToSend) { + TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(message); + Bukkit.getPluginManager().callEvent(event); + if (event.isSent()) + debugMessagesToSend.remove(message); + } } } diff --git a/src/main/java/buttondevteam/lib/chat/CommandClass.java b/src/main/java/buttondevteam/lib/chat/CommandClass.java index fd5ac9d..1a49d87 100644 --- a/src/main/java/buttondevteam/lib/chat/CommandClass.java +++ b/src/main/java/buttondevteam/lib/chat/CommandClass.java @@ -6,6 +6,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Abstract classes with no {@link CommandClass} annotations will be ignored. Classes that are not abstract or have the annotation will be included in the command path unless + * {@link #excludeFromPath()} is true. + * + * @author NorbiPeti + * + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @@ -26,4 +33,9 @@ public @interface CommandClass { * @return The command path, which is the command class name by default (removing any "command" from it) */ public String path() default ""; + + /** + * Exclude this class from the path. Useful if more commands share some property but aren't subcommands of a common command. See {@link CommandClass} for more details. + */ + public boolean excludeFromPath() default false; } diff --git a/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java b/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java index 86e7c37..50b0086 100644 --- a/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java +++ b/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java @@ -4,6 +4,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; @@ -61,20 +62,20 @@ public class TBMCChatAPI { cmds.add("ยง6---- Subcommands ----"); cmds.add(cmd); }; - for (TBMCCommandBase cmd : TBMCChatAPI.GetCommands().values()) { - if (cmd.GetCommandPath().startsWith(command + " ")) { - if (cmd.isPlayerOnly() && !(sender instanceof Player)) + for (Entry cmd : TBMCChatAPI.GetCommands().entrySet()) { + if (cmd.getKey().startsWith(command + " ")) { + if (cmd.getValue().isPlayerOnly() && !(sender instanceof Player)) continue; if (cmd.getClass().getAnnotation(CommandClass.class).modOnly() && !MainPlugin.permission.has(sender, "tbmc.admin")) continue; - int ind = cmd.GetCommandPath().indexOf(' ', command.length() + 2); + int ind = cmd.getKey().indexOf(' ', command.length() + 2); if (ind >= 0) { - String newcmd = cmd.GetCommandPath().substring(0, ind); + String newcmd = cmd.getKey().substring(0, ind); if (!cmds.contains("/" + newcmd)) addToCmds.accept("/" + newcmd); } else - addToCmds.accept("/" + cmd.GetCommandPath()); + addToCmds.accept("/" + cmd.getKey()); } } return cmds.toArray(new String[cmds.size()]); @@ -99,15 +100,22 @@ public class TBMCChatAPI { * @param acmdclass * A command's class to get the package name for commands. The provided class's package and subpackages are scanned for commands. */ - public static void AddCommands(JavaPlugin plugin, Class acmdclass) { - plugin.getLogger().info("Registering commands for " + plugin.getName()); + public static synchronized void AddCommands(JavaPlugin plugin, Class acmdclass) { + plugin.getLogger().info("Registering commands from " + acmdclass.getPackage().getName()); Reflections rf = new Reflections(new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage(acmdclass.getPackage().getName(), plugin.getClass().getClassLoader())) + .addUrls( + ClasspathHelper.forClass(OptionallyPlayerCommandBase.class, + OptionallyPlayerCommandBase.class.getClassLoader()), + ClasspathHelper.forClass(PlayerCommandBase.class, PlayerCommandBase.class.getClassLoader())) // http://stackoverflow.com/questions/12917417/using-reflections-for-finding-the-transitive-subtypes-of-a-class-when-not-all .addClassLoader(plugin.getClass().getClassLoader()).addScanners(new SubTypesScanner())); Set> cmds = rf.getSubTypesOf(TBMCCommandBase.class); for (Class cmd : cmds) { try { + if (!cmd.getPackage().getName().startsWith(acmdclass.getPackage().getName())) + continue; // It keeps including the commands from here + //System.out.println("Class found: " + cmd.getName()); if (Modifier.isAbstract(cmd.getModifiers())) continue; TBMCCommandBase c = cmd.newInstance(); @@ -116,9 +124,7 @@ public class TBMCChatAPI { continue; commands.put(c.GetCommandPath(), c); CommandCaller.RegisterCommand(c); - } catch (InstantiationException e) { - TBMCCoreAPI.SendException("An error occured while registering command " + cmd.getName(), e); - } catch (IllegalAccessException e) { + } catch (Exception e) { TBMCCoreAPI.SendException("An error occured while registering command " + cmd.getName(), e); } } diff --git a/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java b/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java index 157dd1f..e751a9a 100644 --- a/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java +++ b/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java @@ -5,9 +5,11 @@ import java.util.function.Function; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; +import javassist.Modifier; + /** * Extend this class to create new TBMCCommand and use {@link TBMCChatAPI#AddCommand(org.bukkit.plugin.java.JavaPlugin, TBMCCommandBase)} to add it. Note: The command path (command name - * and subcommand arguments) will be the class name by default, removing any "command" from it. To change it (especially for subcommands), override {@link #GetCommandPath()}. + * and subcommand arguments) will be the class name by default, removing any "command" from it. To change it (especially for subcommands), use the path field in the {@link CommandClass} annotation. * * @author Norbi * @@ -15,36 +17,54 @@ import org.bukkit.plugin.Plugin; public abstract class TBMCCommandBase { public TBMCCommandBase() { + path = getcmdpath(); } public abstract boolean OnCommand(CommandSender sender, String alias, String[] args); public abstract String[] GetHelpText(String alias); + private String path = null; + /** * The command's path, or name if top-level command.
* For example:
* "u admin updateplugin" or "u" for the top level one
* The path must be lowercase!
+ * Abstract classes with no {@link CommandClass} annotations will be ignored. * - * @return The command path, which is the command class name by default (removing any "command" from it) + * @return The command path, which is the command class name by default (removing any "command" from it) - Change via the {@link CommandClass} annotation */ public final String GetCommandPath() { + return path; + } + + private final String getcmdpath() { if (!getClass().isAnnotationPresent(CommandClass.class)) - throw new RuntimeException("No @Command annotation on command class " + getClass().getSimpleName() + "!"); - Function, String> getFromClass = cl -> getClass().getSimpleName().toLowerCase() - .replace("commandbase", "").replace("command", ""); - String path = getClass().getAnnotation(CommandClass.class).path(), prevpath = path; // TODO: Check if annotation exists (No @Inherited?) + throw new RuntimeException( + "No @CommandClass annotation on command class " + getClass().getSimpleName() + "!"); + Function, String> getFromClass = cl -> cl.getSimpleName().toLowerCase().replace("commandbase", "") // <-- ... + .replace("command", ""); + String path = getClass().getAnnotation(CommandClass.class).path(), + prevpath = path = path.length() == 0 ? getFromClass.apply(getClass()) : path; + // System.out.println("Path: " + (path.length() == 0 ? getFromClass.apply(getClass()) : path)) for (Class cl = getClass().getSuperclass(); cl != null - && !cl.getName().equals(TBMCCommandBase.class.getName()); cl = cl.getSuperclass()) { - //com.sun.xml.internal.bind.v2.TODO.prototype(); - String newpath = cl.getAnnotation(CommandClass.class).path(); - if (newpath.length() == 0) + && !cl.getPackage().getName().equals(TBMCCommandBase.class.getPackage().getName()); cl = cl + .getSuperclass()) { // + String newpath; + if (!cl.isAnnotationPresent(CommandClass.class) + || (newpath = cl.getAnnotation(CommandClass.class).path()).length() == 0 + || newpath.equals(prevpath)) { + if (Modifier.isAbstract(cl.getModifiers()) && (!cl.isAnnotationPresent(CommandClass.class)) + || cl.getAnnotation(CommandClass.class).excludeFromPath()) // <-- + continue; newpath = getFromClass.apply(cl); - if (!newpath.equals(prevpath)) - path = (prevpath = newpath) + " " + path; + } + path = (prevpath = newpath) + " " + path; + // System.out.println("Path: " + (path.length() == 0 ? getFromClass.apply(cl) : path)); } - return path.length() == 0 ? getFromClass.apply(getClass()) : path; + //System.out.println("Path: " + path); + return path; } Plugin plugin; // Used By TBMCChatAPI