diff --git a/README.md b/README.md index 1cf8171..99379d1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Maven com.github.vaperion blade - 2.1.5 + 2.1.6 compile @@ -40,7 +40,7 @@ allprojects { } dependencies { - implementation 'com.github.vaperion:blade:2.1.5' + implementation 'com.github.vaperion:blade:2.1.6' } ``` diff --git a/pom.xml b/pom.xml index 32b549e..f7e98cc 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.vaperion blade - 2.1.5 + 2.1.6 UTF-8 diff --git a/src/main/java/me/vaperion/blade/Blade.java b/src/main/java/me/vaperion/blade/Blade.java index 92260b3..406641c 100644 --- a/src/main/java/me/vaperion/blade/Blade.java +++ b/src/main/java/me/vaperion/blade/Blade.java @@ -8,10 +8,12 @@ import me.vaperion.blade.bindings.Binding; import me.vaperion.blade.container.ContainerCreator; import me.vaperion.blade.help.HelpGenerator; +import me.vaperion.blade.help.impl.BukkitHelpGenerator; import me.vaperion.blade.permissions.PermissionPredicate; import me.vaperion.blade.service.BladeCommandRegistrar; import me.vaperion.blade.service.BladeCommandService; import me.vaperion.blade.tabcompleter.TabCompleter; +import me.vaperion.blade.utils.ClassUtil; import org.jetbrains.annotations.NotNull; import java.lang.annotation.Annotation; @@ -103,6 +105,8 @@ public Blade build() { if (blade.helpGenerator != null) blade.commandService.setHelpGenerator(blade.helpGenerator); + else if (ClassUtil.classExists("org.bukkit.Bukkit")) + blade.commandService.setHelpGenerator(new BukkitHelpGenerator()); if (blade.asyncExecutor != null) { blade.commandService.setAsyncExecutor(blade.asyncExecutor); diff --git a/src/main/java/me/vaperion/blade/command/BladeCommand.java b/src/main/java/me/vaperion/blade/command/BladeCommand.java index 098b75e..2a91f6f 100644 --- a/src/main/java/me/vaperion/blade/command/BladeCommand.java +++ b/src/main/java/me/vaperion/blade/command/BladeCommand.java @@ -61,6 +61,8 @@ public BladeCommand(BladeCommandService commandService, Object instance, Method this.senderType = this.senderParameter ? method.getParameterTypes()[0] : null; if (method != null) { + method.setAccessible(true); + int i = 0; for (Parameter parameter : method.getParameters()) { if (i == 0 && senderParameter) { diff --git a/src/main/java/me/vaperion/blade/command/UsageMessage.java b/src/main/java/me/vaperion/blade/command/UsageMessage.java index dfc9cc9..14ccdbf 100644 --- a/src/main/java/me/vaperion/blade/command/UsageMessage.java +++ b/src/main/java/me/vaperion/blade/command/UsageMessage.java @@ -5,4 +5,5 @@ public interface UsageMessage { void sendTo(@NotNull BladeContext context); + @NotNull String toString(); } diff --git a/src/main/java/me/vaperion/blade/command/impl/BukkitUsageMessage.java b/src/main/java/me/vaperion/blade/command/impl/BukkitUsageMessage.java index 765adb1..c78e3b8 100644 --- a/src/main/java/me/vaperion/blade/command/impl/BukkitUsageMessage.java +++ b/src/main/java/me/vaperion/blade/command/impl/BukkitUsageMessage.java @@ -64,4 +64,10 @@ public BukkitUsageMessage(BladeCommand command) { public void sendTo(@NotNull BladeContext context) { messageBuilder.sendTo((CommandSender) context.sender().getBackingSender()); } + + @NotNull + @Override + public String toString() { + return messageBuilder.toStringFormat(); + } } diff --git a/src/main/java/me/vaperion/blade/container/impl/BukkitCommandContainer.java b/src/main/java/me/vaperion/blade/container/impl/BukkitCommandContainer.java index a4f33a5..cd2a6d4 100644 --- a/src/main/java/me/vaperion/blade/container/impl/BukkitCommandContainer.java +++ b/src/main/java/me/vaperion/blade/container/impl/BukkitCommandContainer.java @@ -22,7 +22,6 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; import java.util.*; import java.util.stream.Collectors; @@ -34,16 +33,14 @@ public class BukkitCommandContainer extends Command implements CommandContainer private static final String UNKNOWN_COMMAND_MESSAGE; static { - Class spigotConfigClass = null; - Field mapField = null, commandsField = null, unknownCommandField = null; + Field mapField = null, commandsField = null; String unknownCommandMessage = ChatColor.WHITE + "Unknown command. Type \"/help\" for help."; try { - spigotConfigClass = Class.forName("org.spigotmc.SpigotConfig"); + Class spigotConfigClass = Class.forName("org.spigotmc.SpigotConfig"); + Field unknownCommandField = spigotConfigClass.getDeclaredField("unknownCommandMessage"); - unknownCommandField = spigotConfigClass.getDeclaredField("unknownCommandMessage"); unknownCommandField.setAccessible(true); - unknownCommandMessage = ChatColor.WHITE + (String) unknownCommandField.get(null); } catch (Exception ex) { System.err.println("Failed to grab unknown command message from SpigotConfig."); @@ -52,15 +49,10 @@ public class BukkitCommandContainer extends Command implements CommandContainer try { mapField = SimplePluginManager.class.getDeclaredField("commandMap"); - mapField.setAccessible(true); commandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - commandsField.setAccessible(true); - Field modifiers = Field.class.getDeclaredField("modifiers"); - modifiers.setAccessible(true); - - modifiers.setInt(mapField, modifiers.getInt(mapField) & ~Modifier.FINAL); - modifiers.setInt(commandsField, modifiers.getInt(commandsField) & ~Modifier.FINAL); + mapField.setAccessible(true); + commandsField.setAccessible(true); } catch (Exception ex) { System.err.println("Failed to grab commandMap from the plugin manager."); ex.printStackTrace(); @@ -86,13 +78,17 @@ private BukkitCommandContainer(@NotNull BladeCommandService service, @NotNull Bl if (service.isOverrideCommands()) { Map knownCommands = (Map) KNOWN_COMMANDS.get(simpleCommandMap); - for (Command registeredCommand : new ArrayList<>(knownCommands.values())) { + Iterator> iterator = knownCommands.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + Command registeredCommand = entry.getValue(); + if (doesBukkitCommandConflict(registeredCommand, alias, command)) { registeredCommand.unregister(simpleCommandMap); - knownCommands.remove(registeredCommand.getName().toLowerCase(Locale.ENGLISH)); + iterator.remove(); } } - KNOWN_COMMANDS.set(simpleCommandMap, knownCommands); } simpleCommandMap.register(fallbackPrefix, this); @@ -197,6 +193,10 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String alias, @No final BladeCommand finalCommand = command; final String finalResolvedAlias = resolvedAlias; + if (finalCommand.getMethod() == null) { + throw new BladeExitMessage("The command " + finalResolvedAlias + " is a root command and cannot be executed."); + } + Runnable runnable = () -> { try { List parsed; @@ -207,7 +207,6 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String alias, @No if (finalCommand.isSenderParameter()) parsed.add(0, sender); } - finalCommand.getMethod().setAccessible(true); finalCommand.getMethod().invoke(finalCommand.getInstance(), parsed.toArray(new Object[0])); } catch (BladeUsageMessage ex) { sendUsageMessage(context, finalCommand); diff --git a/src/main/java/me/vaperion/blade/help/impl/BukkitHelpGenerator.java b/src/main/java/me/vaperion/blade/help/impl/BukkitHelpGenerator.java new file mode 100644 index 0000000..46418d5 --- /dev/null +++ b/src/main/java/me/vaperion/blade/help/impl/BukkitHelpGenerator.java @@ -0,0 +1,60 @@ +package me.vaperion.blade.help.impl; + +import me.vaperion.blade.command.BladeCommand; +import me.vaperion.blade.command.impl.BukkitUsageMessage; +import me.vaperion.blade.context.BladeContext; +import me.vaperion.blade.help.HelpGenerator; +import me.vaperion.blade.utils.PaginatedOutput; +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.stream.Collectors; + +public class BukkitHelpGenerator implements HelpGenerator { + + @NotNull + @Override + public List generate(@NotNull BladeContext context, @NotNull List commands) { + commands = commands.stream().distinct().filter(c -> !c.isHidden()).collect(Collectors.toList()); + + return new PaginatedOutput(10) { + @Override + public String formatErrorMessage(Error error, Object... args) { + switch (error) { + case NO_RESULTS: + return ChatColor.RED + "No results found."; + case PAGE_OUT_OF_BOUNDS: + return ChatColor.RED + String.format("Page %d does not exist, valid range is 1 to %d.", args); + } + return null; + } + + @Override + public String getHeader(int page, int totalPages) { + return ChatColor.AQUA + "==== " + ChatColor.YELLOW + "Help for /" + context.alias() + ChatColor.AQUA + " ===="; + } + + @Override + public String getFooter(int page, int totalPages) { + return ChatColor.AQUA + "==== " + ChatColor.YELLOW + "Page " + page + "/" + totalPages + ChatColor.AQUA + " ===="; + } + + @Override + public String formatLine(BladeCommand result, int index) { + return ChatColor.AQUA + " - " + + ChatColor.YELLOW + ChatColor.stripColor(result.getUsageMessage().ensureGetOrLoad(() -> new BukkitUsageMessage(result)).toString().replace("Usage: ", "")) + + (result.getDescription().isEmpty() ? "" : (" - " + ChatColor.GRAY + result.getDescription())); + } + }.generatePage(commands, parsePage(context.argument(0))); + } + + private int parsePage(String argument) { + if (argument == null) return 1; + try { + return Integer.parseInt(argument); + } catch (NumberFormatException e) { + return 1; + } + } +} diff --git a/src/main/java/me/vaperion/blade/help/impl/DefaultHelpGenerator.java b/src/main/java/me/vaperion/blade/help/impl/DefaultHelpGenerator.java deleted file mode 100644 index 0443331..0000000 --- a/src/main/java/me/vaperion/blade/help/impl/DefaultHelpGenerator.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.vaperion.blade.help.impl; - -import me.vaperion.blade.command.BladeCommand; -import me.vaperion.blade.context.BladeContext; -import me.vaperion.blade.help.HelpGenerator; -import org.bukkit.ChatColor; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.stream.Collectors; - -public class DefaultHelpGenerator implements HelpGenerator { - - private static final String LINE = ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------------------------"; - - @NotNull - @Override - public List generate(@NotNull BladeContext context, @NotNull List commands) { - commands = commands.stream().distinct().filter(c -> !c.isHidden()).collect(Collectors.toList()); - List lines = new ArrayList<>(); - - if (commands.isEmpty()) { - lines.add(ChatColor.RED + context.commandService().getDefaultPermissionMessage()); - return lines; - } - - lines.add(LINE); - - for (BladeCommand command : commands) { - String cmd = Arrays.stream(command.getAliases()) - .filter(a -> a.toLowerCase(Locale.ROOT).startsWith(context.alias().toLowerCase(Locale.ROOT))) - .findFirst().orElse(null); - if (cmd == null) continue; - lines.add(ChatColor.AQUA + "/" + cmd); - } - - lines.add(LINE); - - return lines; - } -} diff --git a/src/main/java/me/vaperion/blade/service/BladeCommandService.java b/src/main/java/me/vaperion/blade/service/BladeCommandService.java index 1fbef16..7028024 100644 --- a/src/main/java/me/vaperion/blade/service/BladeCommandService.java +++ b/src/main/java/me/vaperion/blade/service/BladeCommandService.java @@ -9,7 +9,7 @@ import me.vaperion.blade.container.CommandContainer; import me.vaperion.blade.container.ContainerCreator; import me.vaperion.blade.help.HelpGenerator; -import me.vaperion.blade.help.impl.DefaultHelpGenerator; +import me.vaperion.blade.help.impl.NoOpHelpGenerator; import me.vaperion.blade.permissions.PermissionPredicate; import me.vaperion.blade.tabcompleter.TabCompleter; import me.vaperion.blade.tabcompleter.impl.DefaultTabCompleter; @@ -31,7 +31,7 @@ public class BladeCommandService { @Setter @Getter private boolean overrideCommands = false; @Setter @Getter private ContainerCreator containerCreator = ContainerCreator.NONE; @Setter @Getter private TabCompleter tabCompleter = new DefaultTabCompleter(); - @Setter @Getter private HelpGenerator helpGenerator = new DefaultHelpGenerator(); + @Setter @Getter private HelpGenerator helpGenerator = new NoOpHelpGenerator(); @Setter @Getter private Consumer asyncExecutor = Runnable::run; @Setter @Getter private long executionTimeWarningThreshold = 5; @Setter @Getter private String defaultPermissionMessage = "You don't have permission to perform this command."; diff --git a/src/main/java/me/vaperion/blade/utils/ClassUtil.java b/src/main/java/me/vaperion/blade/utils/ClassUtil.java index 1e1fdb6..fae2f46 100644 --- a/src/main/java/me/vaperion/blade/utils/ClassUtil.java +++ b/src/main/java/me/vaperion/blade/utils/ClassUtil.java @@ -14,6 +14,15 @@ @UtilityClass public class ClassUtil { + public boolean classExists(@NotNull String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + @NotNull public List> getClassesInPackage(@NotNull Class clazz, @NotNull String packageName) { List> classes = new ArrayList<>(); diff --git a/src/main/java/me/vaperion/blade/utils/PaginatedOutput.java b/src/main/java/me/vaperion/blade/utils/PaginatedOutput.java new file mode 100644 index 0000000..637ac76 --- /dev/null +++ b/src/main/java/me/vaperion/blade/utils/PaginatedOutput.java @@ -0,0 +1,47 @@ +package me.vaperion.blade.utils; + +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +public abstract class PaginatedOutput { + + private final int resultsPerPage; + + public abstract String formatErrorMessage(Error error, Object... args); + + public abstract String getHeader(int page, int totalPages); + + public abstract String getFooter(int page, int totalPages); + + public abstract String formatLine(T result, int index); + + public final List generatePage(List results, int page) { + if (results.size() == 0) { + return Collections.singletonList(formatErrorMessage(Error.NO_RESULTS)); + } + + int totalPages = results.size() / resultsPerPage + (results.size() % resultsPerPage == 0 ? 0 : 1); + if (page < 1 || page > totalPages) { + return Collections.singletonList(formatErrorMessage(Error.PAGE_OUT_OF_BOUNDS, page, totalPages)); + } + + int startIndex = (page - 1) * resultsPerPage; + int endIndex = Math.min(startIndex + resultsPerPage, results.size()); + + List lines = new ArrayList<>(); + lines.add(getHeader(page, totalPages)); + results.subList(startIndex, endIndex).forEach(result -> lines.add(formatLine(result, startIndex + lines.size()))); + lines.add(getFooter(page, totalPages)); + return lines; + } + + public enum Error { + NO_RESULTS, + PAGE_OUT_OF_BOUNDS + } + +}