mirror of
https://github.com/aikar/commands.git
synced 2026-05-31 14:21:56 +00:00
Use MethodHandles for invocation (#112)
* Use method handles for invocation * Fix parsing, add more safety and add for named MessageChannels, and format * Add methodhandle support to BaseCommand
This commit is contained in:
@@ -42,7 +42,10 @@ import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -68,7 +71,8 @@ public abstract class BaseCommand {
|
||||
public static final String DEFAULT = "__default";
|
||||
final SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
|
||||
final Map<Class<?>, String> contextFlags = Maps.newHashMap();
|
||||
private Method preCommandHandler;
|
||||
@Nullable private MethodHandle preCommandHandler = null;
|
||||
private Method preCommandReflectiveHandler = null;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
private String execLabel;
|
||||
@@ -245,7 +249,12 @@ public abstract class BaseCommand {
|
||||
}
|
||||
} else if (preCommand) {
|
||||
if (this.preCommandHandler == null) {
|
||||
this.preCommandHandler = method;
|
||||
this.preCommandReflectiveHandler = method;
|
||||
try {
|
||||
this.preCommandHandler = MethodHandles.lookup().unreflect(method);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
// We use preCommandReflectiveHandler whenever the methodhandle is used instead
|
||||
}
|
||||
} else {
|
||||
ACFUtil.sneaky(new IllegalStateException("Multiple @PreCommand commands, duplicate on " + method.getDeclaringClass().getName() + "#" + method.getName()));
|
||||
}
|
||||
@@ -564,7 +573,7 @@ public abstract class BaseCommand {
|
||||
}
|
||||
|
||||
private boolean checkPrecommand(CommandOperationContext commandOperationContext, RegisteredCommand cmd, CommandIssuer issuer, String[] args) {
|
||||
Method pre = this.preCommandHandler;
|
||||
Method pre = this.preCommandReflectiveHandler;
|
||||
if (pre != null) {
|
||||
try {
|
||||
Class<?>[] types = pre.getParameterTypes();
|
||||
@@ -585,8 +594,11 @@ public abstract class BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if (preCommandHandler != null) {
|
||||
return (boolean) preCommandHandler.invoke(this, parameters);
|
||||
}
|
||||
return (boolean) pre.invoke(this, parameters);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
} catch (Throwable e) {
|
||||
this.manager.log(LogLevel.ERROR, "Exception encountered while command pre-processing", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
@@ -51,6 +53,7 @@ import java.util.stream.Collectors;
|
||||
public class RegisteredCommand <CEC extends CommandExecutionContext<CEC, ? extends CommandIssuer>> {
|
||||
final BaseCommand scope;
|
||||
final Method method;
|
||||
@Nullable final MethodHandle methodHandle;
|
||||
final CommandParameter<CEC>[] parameters;
|
||||
final CommandManager manager;
|
||||
final List<String> registeredSubcommands = new ArrayList<>();
|
||||
@@ -79,6 +82,14 @@ public class RegisteredCommand <CEC extends CommandExecutionContext<CEC, ? exten
|
||||
}
|
||||
this.command = command + (!annotations.hasAnnotation(method, CommandAlias.class, false) && !prefSubCommand.isEmpty() ? prefSubCommand : "");
|
||||
this.method = method;
|
||||
MethodHandle methodHandle = null;
|
||||
try {
|
||||
methodHandle = MethodHandles.lookup().unreflect(method);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
// As the method has been found, it cannot not have permission for it.
|
||||
// In the case that it does however manage to fail this, keep it null and handle it later on.
|
||||
}
|
||||
this.methodHandle = methodHandle;
|
||||
this.prefSubCommand = prefSubCommand;
|
||||
|
||||
this.permission = annotations.getAnnotationValue(method, CommandPermission.class, Annotations.REPLACEMENTS | Annotations.NO_EMPTY);
|
||||
@@ -139,9 +150,14 @@ public class RegisteredCommand <CEC extends CommandExecutionContext<CEC, ? exten
|
||||
Map<String, Object> passedArgs = resolveContexts(sender, args);
|
||||
if (passedArgs == null) return;
|
||||
|
||||
method.invoke(scope, passedArgs.values().toArray());
|
||||
} catch (Exception e) {
|
||||
handleException(sender, args, e);
|
||||
if (methodHandle != null) {
|
||||
methodHandle.invoke(passedArgs.values().toArray());
|
||||
} else {
|
||||
// The offchance the method handle wasn't unreflected, we're going to use the slower, working reflection.
|
||||
method.invoke(passedArgs.values().toArray());
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
handleException(sender, args, throwable instanceof Exception ? (Exception) throwable : new Exception(throwable));
|
||||
}
|
||||
postCommand();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
|
||||
public class JDACommandCompletions extends CommandCompletions<CommandCompletionContext<?>> {
|
||||
private boolean initialized;
|
||||
|
||||
public JDACommandCompletions(CommandManager manager) {
|
||||
super(manager);
|
||||
this.initialized = true;
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class JDACommandConfig implements CommandConfig {
|
||||
protected @NotNull List<String> commandPrefixes = new CopyOnWriteArrayList<>(new String[] {"!"});
|
||||
protected @NotNull List<String> commandPrefixes = new CopyOnWriteArrayList<>(new String[]{"!"});
|
||||
|
||||
public JDACommandConfig() {
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package co.aikar.commands;
|
||||
|
||||
import co.aikar.commands.annotation.Author;
|
||||
import co.aikar.commands.annotation.Optional;
|
||||
import co.aikar.commands.annotation.SelfUser;
|
||||
import net.dv8tion.jda.core.JDA;
|
||||
import net.dv8tion.jda.core.entities.ChannelType;
|
||||
import net.dv8tion.jda.core.entities.Guild;
|
||||
import net.dv8tion.jda.core.entities.Message;
|
||||
import net.dv8tion.jda.core.entities.MessageChannel;
|
||||
import net.dv8tion.jda.core.entities.TextChannel;
|
||||
import net.dv8tion.jda.core.entities.User;
|
||||
import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
|
||||
|
||||
@@ -21,10 +24,9 @@ public class JDACommandContexts extends CommandContexts<JDACommandExecutionConte
|
||||
this.jda = this.manager.getJDA();
|
||||
this.registerIssuerOnlyContext(JDACommandEvent.class, CommandExecutionContext::getIssuer);
|
||||
this.registerIssuerOnlyContext(MessageReceivedEvent.class, c -> c.getIssuer().getIssuer());
|
||||
this.registerIssuerOnlyContext(Message.class, c -> {
|
||||
MessageReceivedEvent event = c.getIssuer().getIssuer();
|
||||
return event.getMessage();
|
||||
});
|
||||
this.registerIssuerOnlyContext(Message.class, c -> c.issuer.getIssuer().getMessage());
|
||||
this.registerIssuerOnlyContext(ChannelType.class, c -> c.issuer.getIssuer().getChannelType());
|
||||
this.registerIssuerOnlyContext(JDA.class, c -> jda);
|
||||
this.registerIssuerOnlyContext(Guild.class, c -> {
|
||||
MessageReceivedEvent event = c.getIssuer().getIssuer();
|
||||
if (event.isFromType(ChannelType.PRIVATE) && !c.hasAnnotation(Optional.class)) {
|
||||
@@ -34,23 +36,39 @@ public class JDACommandContexts extends CommandContexts<JDACommandExecutionConte
|
||||
}
|
||||
});
|
||||
this.registerIssuerOnlyContext(MessageChannel.class, c -> {
|
||||
MessageReceivedEvent event = c.getIssuer().getIssuer();
|
||||
return event.getChannel();
|
||||
if (c.hasAnnotation(Author.class)) {
|
||||
return c.issuer.getIssuer().getChannel();
|
||||
}
|
||||
String argument = c.popFirstArg();
|
||||
MessageChannel channel = null;
|
||||
if (argument.startsWith("<#")) {
|
||||
channel = jda.getTextChannelById(argument.substring(2, argument.length() - 1));
|
||||
} else {
|
||||
List<TextChannel> channelList = c.issuer.getEvent().getGuild().getTextChannelsByName(argument.toLowerCase(), true);
|
||||
if (channelList.size() > 1) {
|
||||
throw new InvalidCommandArgument("Too many channels were found with the given name. Try with the `#channelname` syntax.", false);
|
||||
} else if (channelList.size() == 1) {
|
||||
channel = channelList.get(0);
|
||||
}
|
||||
}
|
||||
if (channel == null) {
|
||||
throw new InvalidCommandArgument("Couldn't find the channel with that name or ID.");
|
||||
}
|
||||
return channel;
|
||||
});
|
||||
this.registerIssuerOnlyContext(ChannelType.class, c -> {
|
||||
MessageReceivedEvent event = c.getIssuer().getIssuer();
|
||||
return event.getChannelType();
|
||||
});
|
||||
|
||||
|
||||
this.registerIssuerOnlyContext(JDA.class, c -> jda);
|
||||
this.registerContext(User.class, c -> {
|
||||
if (c.hasAnnotation(SelfUser.class)) {
|
||||
return jda.getSelfUser();
|
||||
}
|
||||
String arg = c.popFirstArg();
|
||||
User user = null;
|
||||
if (arg.startsWith("@")) {
|
||||
user = jda.getUserById(arg.substring(1));
|
||||
if (arg.startsWith("<@")) {
|
||||
user = jda.getUserById(arg.substring(2, arg.length() - 1));
|
||||
} else {
|
||||
List<User> users = jda.getUsersByName(arg, true);
|
||||
if (users.size() > 1) {
|
||||
throw new InvalidCommandArgument("Too many users were found with the given name. Try with the `@username#0000` syntax.", false);
|
||||
}
|
||||
if (!users.isEmpty()) {
|
||||
user = users.get(0);
|
||||
}
|
||||
@@ -61,5 +79,4 @@ public class JDACommandContexts extends CommandContexts<JDACommandExecutionConte
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ public class JDACommandEvent implements CommandIssuer {
|
||||
public void sendMessage(Message message) {
|
||||
this.event.getChannel().sendMessage(message).queue();
|
||||
}
|
||||
|
||||
public void sendMessage(MessageEmbed message) {
|
||||
this.event.getChannel().sendMessage(message).queue();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package co.aikar.commands;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class JDACommandExecutionContext extends CommandExecutionContext<JDACommandExecutionContext,JDACommandEvent> {
|
||||
public class JDACommandExecutionContext extends CommandExecutionContext<JDACommandExecutionContext, JDACommandEvent> {
|
||||
JDACommandExecutionContext(RegisteredCommand cmd, CommandParameter param, JDACommandEvent sender, List<String> args, int index, Map<String, Object> passedArgs) {
|
||||
super(cmd, param, sender, args, index, passedArgs);
|
||||
}
|
||||
|
||||
@@ -25,18 +25,16 @@ public class JDACommandManager extends CommandManager<
|
||||
> {
|
||||
|
||||
private final JDA jda;
|
||||
|
||||
protected JDACommandCompletions completions;
|
||||
protected JDACommandContexts contexts;
|
||||
protected JDALocales locales;
|
||||
protected Map<String, JDARootCommand> commands = Maps.newHashMap();
|
||||
private Logger logger;
|
||||
private CommandConfig defaultConfig;
|
||||
private CommandConfigProvider configProvider;
|
||||
private CommandPermissionResolver permissionResolver;
|
||||
protected JDACommandCompletions completions;
|
||||
protected JDACommandContexts contexts;
|
||||
protected JDALocales locales;
|
||||
private long botOwner = 0L;
|
||||
|
||||
protected Map<String, JDARootCommand> commands = Maps.newHashMap();
|
||||
|
||||
public JDACommandManager(JDA jda) {
|
||||
this(jda, null);
|
||||
}
|
||||
@@ -78,6 +76,10 @@ public class JDACommandManager extends CommandManager<
|
||||
});
|
||||
}
|
||||
|
||||
public static JDAOptions options() {
|
||||
return new JDAOptions();
|
||||
}
|
||||
|
||||
void initializeBotOwner() {
|
||||
if (botOwner == 0L) {
|
||||
if (jda.getAccountType() == AccountType.BOT) {
|
||||
@@ -94,11 +96,6 @@ public class JDACommandManager extends CommandManager<
|
||||
return botOwner;
|
||||
}
|
||||
|
||||
|
||||
public static JDAOptions options() {
|
||||
return new JDAOptions();
|
||||
}
|
||||
|
||||
public JDA getJDA() {
|
||||
return jda;
|
||||
}
|
||||
@@ -218,7 +215,7 @@ public class JDACommandManager extends CommandManager<
|
||||
|
||||
void dispatchEvent(MessageReceivedEvent event) {
|
||||
Message message = event.getMessage();
|
||||
String msg = message.getContentDisplay();
|
||||
String msg = message.getContentRaw();
|
||||
|
||||
CommandConfig config = getCommandConfig(event);
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ public class JDAOptions {
|
||||
CommandConfigProvider configProvider = null;
|
||||
CommandPermissionResolver permissionResolver = null;
|
||||
|
||||
public JDAOptions() {}
|
||||
public JDAOptions() {
|
||||
}
|
||||
|
||||
public JDAOptions defaultConfig(@NotNull CommandConfig defaultConfig) {
|
||||
this.defaultConfig = defaultConfig;
|
||||
|
||||
@@ -8,12 +8,12 @@ import java.util.List;
|
||||
|
||||
public class JDARootCommand implements RootCommand {
|
||||
|
||||
private JDACommandManager manager;
|
||||
private final String name;
|
||||
boolean isRegistered = false;
|
||||
private JDACommandManager manager;
|
||||
private BaseCommand defCommand;
|
||||
private SetMultimap<String, RegisteredCommand> subCommands = HashMultimap.create();
|
||||
private List<BaseCommand> children = new ArrayList<>();
|
||||
boolean isRegistered = false;
|
||||
|
||||
JDARootCommand(JDACommandManager manager, String name) {
|
||||
this.manager = manager;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package co.aikar.commands.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* The {@link Author} annotation is to define whether the parameter should be the author object from the event or
|
||||
* parsed from the user's input.
|
||||
* <p>
|
||||
* Using this on a User/Member will fetch the author, while if not, it'll parse the input.
|
||||
* The same happens with channels, guilds, and so on.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Author {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package co.aikar.commands.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SelfUser {
|
||||
}
|
||||
Reference in New Issue
Block a user