Skip to content

Commit

Permalink
started work on completers
Browse files Browse the repository at this point in the history
  • Loading branch information
Badbird5907 committed Jun 7, 2022
1 parent bbb2ed0 commit 3954b3a
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,13 @@ public void registerCommand(CommandInfo command) {

@Override
public boolean isSenderParameter(ParameterInfo parameterInfo) {
return parameterInfo.getParameter().isAnnotationPresent(Sender.class) || parameterInfo.getParameter().getName().equalsIgnoreCase("sender") || parameterInfo.getParameter().getType().equals(CoreCommandSender.class)
|| parameterInfo.getParameter().getType().equals(BukkitCommandSender.class) || parameterInfo.getParameter().getType().equals(CommandSender.class) || parameterInfo.getParameter().getType().equals(BukkitCommandSenderImpl.class);
return parameterInfo.getParameter().isAnnotationPresent(Sender.class) ||
parameterInfo.getParameter().getName().equalsIgnoreCase("sender") ||
parameterInfo.getParameter().getType().equals(CoreCommandSender.class) ||
parameterInfo.getParameter().getType().equals(BukkitCommandSender.class) ||
parameterInfo.getParameter().getType().equals(CommandSender.class) ||
parameterInfo.getParameter().getType().equals(BukkitCommandSenderImpl.class)
;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package net.octopvp.commander.bukkit.impl;

import net.octopvp.commander.Commander;
import net.octopvp.commander.command.CommandInfo;
import org.bukkit.command.CommandSender;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import lombok.Getter;
import lombok.Setter;
import net.octopvp.commander.annotation.Sender;
import net.octopvp.commander.bukkit.BukkitCommandSender;
import net.octopvp.commander.bukkit.annotation.DefaultSelf;
import net.octopvp.commander.bukkit.impl.BukkitCommandSenderImpl;
Expand Down Expand Up @@ -31,7 +30,18 @@ public Player provide(CommandContext context, CommandInfo commandInfo, Parameter
return ((BukkitCommandSender) context.getCommandSender()).getPlayer();
return null;
}
return parameterInfo.getParameter().isAnnotationPresent(Sender.class) || parameterInfo.getParameter().getName().equalsIgnoreCase("sender") ? (Player) ((BukkitCommandSender) context.getCommandSender()).getSender() : Bukkit.getPlayer(args.pop());
if (parameterInfo.getCommander().getPlatform().isSenderParameter(parameterInfo)) {
return (Player) ((BukkitCommandSender) context.getCommandSender()).getSender();
}
return Bukkit.getPlayer(args.pop());
}

@Override
public Player provideDefault(CommandContext context, CommandInfo commandInfo, ParameterInfo parameterInfo, Deque<String> args) {
if (parameterInfo.getCommander().getPlatform().isSenderParameter(parameterInfo)) {
return (Player) ((BukkitCommandSender) context.getCommandSender()).getSender();
}
return null;
}

@Override
Expand Down
180 changes: 138 additions & 42 deletions Commander-Core/src/main/java/net/octopvp/commander/CommanderImpl.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package net.octopvp.commander;

import net.octopvp.commander.annotation.Command;
import net.octopvp.commander.annotation.DistributeOnMethods;
import net.octopvp.commander.annotation.DontAutoInit;
import net.octopvp.commander.annotation.Range;
import net.octopvp.commander.annotation.*;
import net.octopvp.commander.argument.ArgumentParser;
import net.octopvp.commander.argument.CommandArgs;
import net.octopvp.commander.command.CommandContext;
import net.octopvp.commander.command.CommandInfo;
import net.octopvp.commander.command.CompleterInfo;
import net.octopvp.commander.command.ParameterInfo;
import net.octopvp.commander.config.CommanderConfig;
import net.octopvp.commander.exception.*;
Expand Down Expand Up @@ -107,6 +105,7 @@ public Commander init() {

@Override
public Commander register(Object... objects) {
if (objects == null) return this;
for (Object object : objects) {
if (object == null) {
continue;
Expand All @@ -120,9 +119,12 @@ public Commander register(Object... objects) {
}
registerCmd(object);
}
registerCompleters();
return this;
}

private Map<Method, Object> completerCache = new HashMap<>();

private void registerCmd(Object object) {
if (object == null) return;
List<Annotation> distributedAnnotations = new ArrayList<>();
Expand Down Expand Up @@ -153,35 +155,102 @@ private void registerCmd(Object object) {
platform.registerCommand(parentInfo);
}
for (Method method : object.getClass().getDeclaredMethods()) {
if (!method.isAnnotationPresent(Command.class)) {
continue;
}
Command command = method.getAnnotation(Command.class);
String name = command.name();
List<ParameterInfo> parameters = new ArrayList<>();
for (Parameter parameter : method.getParameters()) {
parameters.add(new ParameterInfo(parameter, this));
}
Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
for (Annotation declaredAnnotation : method.getDeclaredAnnotations()) {
annotations.put(declaredAnnotation.annotationType(), declaredAnnotation);
}
for (Annotation distributedAnnotation : distributedAnnotations) {
annotations.put(distributedAnnotation.annotationType(), distributedAnnotation);
if (method.isAnnotationPresent(Command.class)) {
Command command = method.getAnnotation(Command.class);
String name = command.name();
List<ParameterInfo> parameters = new ArrayList<>();
for (Parameter parameter : method.getParameters()) {
parameters.add(new ParameterInfo(parameter, this));
}
Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
for (Annotation declaredAnnotation : method.getDeclaredAnnotations()) {
annotations.put(declaredAnnotation.annotationType(), declaredAnnotation);
}
for (Annotation distributedAnnotation : distributedAnnotations) {
annotations.put(distributedAnnotation.annotationType(), distributedAnnotation);
}
CommandInfo commandInfo = new CommandInfo(parameters.toArray(new ParameterInfo[0]), name, command.description(), command.usage(), command.aliases(), method, object, annotations, this);
if (classHasMainCommand) {
commandInfo.setSubCommand(true);
commandInfo.setParent(parentInfo);
parentInfo.getSubCommands().add(commandInfo);
} else {
commandMap.put(name.toLowerCase(), commandInfo);
for (String alias : command.aliases()) {
commandMap.put(alias.toLowerCase(), commandInfo);
}
platform.registerCommand(commandInfo);
}
} else if (method.isAnnotationPresent(Completer.class)) {
completerCache.put(method, object);
}
CommandInfo commandInfo = new CommandInfo(parameters.toArray(new ParameterInfo[0]), name, command.description(), command.usage(), command.aliases(), method, object, annotations, this);
if (classHasMainCommand) {
commandInfo.setSubCommand(true);
commandInfo.setParent(parentInfo);
parentInfo.getSubCommands().add(commandInfo);
} else {
commandMap.put(name.toLowerCase(), commandInfo);
for (String alias : command.aliases()) {
commandMap.put(alias.toLowerCase(), commandInfo);
}
}

private void registerCompleters() {
Iterator<Map.Entry<Method, Object>> iterator = completerCache.entrySet().iterator();
while (iterator.hasNext()) {
try {
Map.Entry<Method, Object> entry = iterator.next();
iterator.remove();
Method method = entry.getKey();
Completer completer = method.getAnnotation(Completer.class);
String targetCommand = completer.name();
int[] indexes = completer.index();
for (int index : indexes) {
if (index < 0) {
throw new IllegalArgumentException("Completer index must be greater than 0!");
}
}
if (targetCommand.equals("")) {
throw new IllegalArgumentException("Completer name cannot be empty!");
}
platform.registerCommand(commandInfo);
String[] split = targetCommand.toLowerCase().split(" ");
boolean isSubCommand = split.length > 1;

CommandInfo command = getCommand(split[0]), parent;
if (command == null) return;

if (isSubCommand && !command.isParentCommand())
throw new IllegalArgumentException("Completer references a subcommand, where the command is not a parent command!");
if (!isSubCommand && command.isParentCommand())
throw new IllegalArgumentException("Completer references a parent command, which cannot be tab-completed!");

CompleterInfo completerInfo;
if (isSubCommand) {
parent = command;
command = parent.getSubCommand(split[1]);
if (command == null)
throw new IllegalArgumentException("Completer references a subcommand, which does not exist!");
completerInfo = new CompleterInfo(
command,
entry.getValue(),
method,
completer,
parent
);
} else {
completerInfo = new CompleterInfo(
command,
entry.getValue(),
method,
completer
);
}
if (indexes.length == 0) {
command.getCompleters().put(-1, completerInfo); //This means the completer will be called for every parameter, not recommended but it's there if you want it
} else {
for (int index : indexes) {
command.getCompleters().put(index, completerInfo);
}
}
} catch (Exception e) {
System.err.println("Failed to register completer!");
e.printStackTrace();
}
}

completerCache.clear(); //try to remove any leftover completers in case of an exception/concurrent modification
}

@Override
Expand Down Expand Up @@ -362,7 +431,7 @@ public void executeCommand(CoreCommandSender sender, String label, String[] args
try {
result = context.getCommandInfo().getMethod().invoke(context.getCommandInfo().getInstance(), arguments);
} catch (IllegalAccessException | IllegalArgumentException e) {
e.printStackTrace();
e.printStackTrace();
} catch (InvocationTargetException e) {
if (e.getCause() != null && e.getCause() instanceof CommandException) {
platform.handleCommandException(context, (CommandException) e.getCause());
Expand Down Expand Up @@ -472,26 +541,53 @@ public List<String> getSuggestions(CoreCommandSender sender, final String input)

int diff = parent == null ? 1 : 2;
int index = split.length - diff;
String lastArg = split[split.length - 1];


if (!input.endsWith(" ") && config.isShowNextSuggestionOnlyIfEndsWithSpace()) index--;

if (index >= params.length) {
return null;
CompleterInfo customCompleter = command.getCompleters().get(index);
boolean allParams = false;
if (customCompleter == null) {
customCompleter = command.getCompleters().get(-1);
allParams = true;
}
Collection<String> customReturn = null;
if (customCompleter != null) {
Method method = customCompleter.getMethod();
Object[] args = ArgumentParser.parseCompleterArguments(customCompleter, command, sender, method.getParameters(), input, label, lastArg, split, allParams ? -1 : index);
if (args == null) return null;
try {
Object result = method.invoke(customCompleter.getInstance(), args);
if (result instanceof Collection) {
customReturn = (Collection<String>) result;
}else {
throw new SuggestionException("Completer method must return a collection of strings.");
}
} catch (InvocationTargetException | IllegalAccessException e) {
throw new SuggestionException("Failed to invoke completer method.", e);
}
}

ParameterInfo param = params[index];
Provider<?> provider = param.getProvider();
Collection<String> suggestionsProvided;
if (customReturn == null) {
if (index >= params.length) {
return null;
}

if (provider == null) {
return null;
}
ParameterInfo param = params[index];
Provider<?> provider = param.getProvider();

String lastArg = split[split.length - 1];
if (provider == null) {
return null;
}

List<String> suggestionsProvided = provider.provideSuggestions(input, lastArg, sender);
if (suggestionsProvided == null) {
return null;
}

suggestionsProvided = provider.provideSuggestions(input, lastArg, sender);
if (suggestionsProvided == null) {
return null;
}
}else suggestionsProvided = customReturn;
List<String> suggestions = new ArrayList<>(suggestionsProvided);
if (config.isFilterSuggestions() && !input.endsWith(" ")) {
suggestions.removeIf(s -> !s.trim().toLowerCase().startsWith(lastArg.trim().toLowerCase()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.octopvp.commander.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Completer {
String name();
int[] index();
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package net.octopvp.commander.argument;

import net.octopvp.commander.annotation.Dependency;
import net.octopvp.commander.annotation.Sender;
import net.octopvp.commander.command.CommandContext;
import net.octopvp.commander.command.CommandInfo;
import net.octopvp.commander.command.CompleterInfo;
import net.octopvp.commander.command.ParameterInfo;
import net.octopvp.commander.exception.CommandParseException;
import net.octopvp.commander.exception.InvalidArgsException;
import net.octopvp.commander.provider.Provider;
import net.octopvp.commander.sender.CoreCommandSender;
import net.octopvp.commander.util.Primitives;
import net.octopvp.commander.validator.Validator;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -87,6 +92,43 @@ public static Object[] parseArguments(CommandContext ctx, CommandArgs cArgs) {
return arguments;
}

public static Object[] parseCompleterArguments(CompleterInfo completerInfo, CommandInfo commandInfo, CoreCommandSender sender, Parameter[] parameters, String input, String label, String lastArg, String[] split, int index) {
Object[] arguments = new Object[parameters.length];
int paramIndex = 0;
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.isAnnotationPresent(Dependency.class)) {
Class<?> classType = parameter.getType();
Supplier<?> supplier = commandInfo.getCommander().getDependencies().get(classType);
if (supplier == null) {
throw new CommandParseException("Dependency not found for " + classType.getName()); //maybe let the user control this?
}
arguments[i] = supplier.get();
continue;
}

if (parameter.isAnnotationPresent(Sender.class) || parameter.getName().equalsIgnoreCase("sender")) {
arguments[i] = sender;
continue;
}

switch (paramIndex++) {
case 0: {
arguments[i] = input;
break;
}
case 1: {
arguments[i] = lastArg;
break;
}
default: {
throw new CommandParseException("Too many arguments! Completer Usage: (String input, String lastArg) @Dependency & @Sender also works.");
}
}
}
return arguments;
}

private static void validate(Object obj, ParameterInfo parameter, CommandContext ctx) {
Class<?> type = Primitives.wrap(parameter.getParameter().getType());
for (Map.Entry<Class<?>, Validator<Object>> entry : ctx.getCommandInfo().getCommander().getValidators().entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class CommandInfo { //This is the object that is stored in the command ma
private CommandInfo parent;
private List<CommandInfo> subCommands;

private Map<Integer, CompleterInfo> completers = new HashMap<>();

public CommandInfo(ParameterInfo[] parameters, String name, String description, String usage, String[] aliases, Method method, Object instance, Map<Class<? extends Annotation>, Annotation> annotations, Commander commander) {
this.parameters = parameters;
this.name = name.toLowerCase();
Expand Down
Loading

0 comments on commit 3954b3a

Please sign in to comment.