package net.t7seven7t.craftfx.command; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.MapMaker; import com.sk89q.intake.CommandCallable; import com.sk89q.intake.CommandException; import com.sk89q.intake.CommandMapping; import com.sk89q.intake.Intake; import com.sk89q.intake.InvalidUsageException; import com.sk89q.intake.InvocationCommandException; import com.sk89q.intake.argument.ArgumentException; import com.sk89q.intake.argument.Namespace; import com.sk89q.intake.completion.CommandCompleter; import com.sk89q.intake.dispatcher.Dispatcher; import com.sk89q.intake.fluent.CommandGraph; import com.sk89q.intake.parametric.ArgumentParser; import com.sk89q.intake.parametric.Binding; import com.sk89q.intake.parametric.Injector; import com.sk89q.intake.parametric.ParametricBuilder; import com.sk89q.intake.parametric.provider.PrimitivesModule; import com.sk89q.intake.util.auth.AuthorizationException; import net.t7seven7t.craftfx.CraftFX; import net.t7seven7t.craftfx.util.MessageUtil; import net.t7seven7t.util.intake.module.BukkitModule; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * */ public class Commands implements TabExecutor, CommandCompleter { private static final Joiner SPACE_JOINER = Joiner.on(" "); private final Dispatcher dispatcher; private final CommandGraph graph; private final Map<CommandMapping, List<Binding<?>>> bindingMap = new MapMaker().makeMap(); public Commands() { Injector injector = Intake.createInjector(); injector.install(new PrimitivesModule()); injector.install(new BukkitModule()); injector.install(new CraftFXModule()); ParametricBuilder builder = new ParametricBuilder(injector); builder.setAuthorizer((namespace, permission) -> { CommandSender sender = namespace.get(CommandSender.class); return sender.isOp() || sender.hasPermission(permission); }); graph = new CommandGraph().builder(builder); dispatcher = graph.getDispatcher(); registerSubCommands("fx", new AdminCommands()); fixCommandCompletion(dispatcher.getCommands()); } private void registerCommands(Object o) { graph.commands().registerMethods(o); } private void registerSubCommands(String group, Object o) { graph.commands().group(group).registerMethods(o); } private void fixCommandCompletion(Collection<CommandMapping> commandMappings) { for (CommandMapping commandMapping : commandMappings) { final CommandCallable callable = commandMapping.getCallable(); if (callable instanceof Dispatcher) { fixCommandCompletion(((Dispatcher) callable).getCommands()); } else if (callable.getClass().getSimpleName().equals("MethodCallable")) { try { Field field = callable.getClass().getSuperclass().getDeclaredField("builder"); field.setAccessible(true); final ParametricBuilder builder = (ParametricBuilder) field.get(callable); field = callable.getClass().getSuperclass().getDeclaredField("parser"); field.setAccessible(true); final ArgumentParser parser = (ArgumentParser) field.get(callable); builder.setDefaultCompleter(this); field = parser.getClass().getDeclaredField("parameters"); field.setAccessible(true); final List parameterList = (List) field.get(parser); if (parameterList.isEmpty()) continue; final Method bindingsMethod = parameterList.get(0).getClass() .getMethod("getBinding"); bindingsMethod.setAccessible(true); final List<Binding<?>> bindings = new ArrayList<>(); for (Object o : parameterList) { final Binding b = (Binding) bindingsMethod.invoke(o); if (!b.getProvider().isProvided()) bindings.add(b); } bindingMap.put(commandMapping, bindings); } catch (Exception e) { CraftFX.log().severe("Error occurred while fixing tab completion for " + "command %s", MessageUtil.translate(null, callable.getDescription().getUsage()), e); } } } } public Dispatcher getDispatcher() { return dispatcher; } public CommandMapping getCommand(String alias) { String[] arguments = alias.split(" "); Dispatcher d = dispatcher; CommandMapping result = null; for (String s : arguments) { result = d.get(s); if (result != null && result.getCallable() instanceof Dispatcher) { d = (Dispatcher) result.getCallable(); continue; } break; // not a dispatcher } return result; } public void printHelp(CommandSender sender, CommandMapping mapping) { printHelp(sender, mapping, ImmutableList.of()); } void printHelp(CommandSender sender, CommandMapping mapping, List<String> parentAliases) { if (mapping.getCallable() instanceof Dispatcher) { List<String> l = ImmutableList.<String>builder().addAll(parentAliases) .add(mapping.getPrimaryAlias()).build(); ((Dispatcher) mapping.getCallable()).getCommands() .forEach(c -> printHelp(sender, c, l)); return; } Namespace n = new Namespace(); n.put(CommandSender.class, sender); if (!mapping.getCallable().testPermission(n)) return; String parent = ""; for (String p : parentAliases) parent += p + " "; MessageUtil.message(sender, "command-help", parent, mapping.getPrimaryAlias(), MessageUtil.translate(sender, mapping.getDescription().getUsage()), MessageUtil.translate(sender, mapping.getDescription().getShortDescription())); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { Namespace namespace = new Namespace(); namespace.put(CommandSender.class, sender); try { dispatcher.call(joinCommandArgs(command, args), namespace, ImmutableList.of()); } catch (CommandException e) { if (e instanceof InvalidUsageException && e.getMessage() .equals("Please choose a sub-command.")) { String alias = SPACE_JOINER.join(((InvalidUsageException) e).getAliasStack()); MessageUtil.message(sender, "command-help-header", alias); printHelp(sender, getCommand(alias)); return true; } MessageUtil.message(sender, "&c" + e.getMessage()); final CommandMapping mapping = getCommand(joinCommandArgs(command, args)); if (mapping != null) { MessageUtil.message(sender, "&cHelp: %s", MessageUtil.translate(sender, // show desc if help is null mapping.getDescription().getHelp() == null ? mapping.getDescription() .getShortDescription() : mapping.getDescription().getHelp())); } } catch (AuthorizationException e) { MessageUtil.message(sender, "command-authorization-exception"); } catch (InvocationCommandException e) { final Throwable cause = getCause(e); if (cause instanceof NumberFormatException || cause instanceof ArgumentException) { MessageUtil.message(sender, "&c" + cause.getMessage()); } else if (cause instanceof InvalidUsageException && cause.getMessage() .equals("Please choose a sub-command.")) { final String alias = SPACE_JOINER .join(((InvalidUsageException) cause).getAliasStack()); MessageUtil.message(sender, "command-help-header", alias); printHelp(sender, getCommand(alias)); } else { MessageUtil.message(sender, "&cAn error occurred. Tell an admin."); cause.printStackTrace(); } } return true; } private Throwable getCause(InvocationCommandException e) { if (e.getCause() instanceof InvocationCommandException) { return getCause((InvocationCommandException) e.getCause()); } return e.getCause(); } @Override public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { Namespace namespace = new Namespace(); namespace.put(CommandSender.class, sender); if (dispatcher.testPermission(namespace)) { final String joinedArgs = joinCommandArgs(command, args); final CommandMapping commandMapping = getCommand(joinedArgs); namespace.put(CommandMapping.class, commandMapping); try { // todo: fix tab completion in intake or replace with something else return dispatcher.getSuggestions(joinedArgs, namespace); } catch (CommandException e) { // o: } } return ImmutableList.of(); } private String joinCommandArgs(Command command, String[] args) { return command.getName() + " " + SPACE_JOINER.join(args); } @Override public List<String> getSuggestions(String arguments, Namespace locals) throws CommandException { final CommandMapping mapping = locals.get(CommandMapping.class); final List<Binding<?>> bindings = bindingMap.get(mapping); if (bindings == null || bindings.isEmpty()) return ImmutableList.of(); final String[] args = arguments.split(" "); if (args.length > bindings.size()) return ImmutableList.of(); return bindings.get(args.length - 1).getProvider().getSuggestions(args[args.length - 1]); } }