Add support for suspending functions
This also changes CommandExecutionHandler to return futures instead. The old method is still supported, and the new future-returning method will delegate to the old non-returning method.
This commit is contained in:
parent
c16ee8049b
commit
7bb5e63dc7
12 changed files with 556 additions and 249 deletions
|
|
@ -48,6 +48,9 @@ import cloud.commandframework.extra.confirmation.CommandConfirmationManager;
|
|||
import cloud.commandframework.meta.CommandMeta;
|
||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
|
|
@ -85,6 +88,8 @@ public final class AnnotationParser<C> {
|
|||
@NonNull Queue<@NonNull String>, @NonNull ArgumentParseResult<Boolean>>>> preprocessorMappers;
|
||||
private final Map<Class<? extends Annotation>, BiFunction<? extends Annotation, Command.Builder<C>, Command.Builder<C>>>
|
||||
builderModifiers;
|
||||
private final Map<Predicate<Method>, Function<MethodCommandExecutionHandler.CommandMethodContext<C>,
|
||||
MethodCommandExecutionHandler<C>>> commandMethodFactories;
|
||||
private final Class<C> commandSenderClass;
|
||||
private final MetaFactory metaFactory;
|
||||
private final FlagExtractor flagExtractor;
|
||||
|
|
@ -110,6 +115,7 @@ public final class AnnotationParser<C> {
|
|||
this.annotationMappers = new HashMap<>();
|
||||
this.preprocessorMappers = new HashMap<>();
|
||||
this.builderModifiers = new HashMap<>();
|
||||
this.commandMethodFactories = new HashMap<>();
|
||||
this.flagExtractor = new FlagExtractor(manager);
|
||||
this.registerAnnotationMapper(CommandDescription.class, d ->
|
||||
ParserParameters.single(StandardParameters.DESCRIPTION, d.value()));
|
||||
|
|
@ -180,6 +186,29 @@ public final class AnnotationParser<C> {
|
|||
return getMethodOrClassAnnotation(method, clazz) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command manager that was used to create this parser
|
||||
*
|
||||
* @return Command manager
|
||||
*/
|
||||
public @NonNull CommandManager<C> manager() {
|
||||
return this.manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new command execution method factory. This allows for the registration of
|
||||
* custom command method execution strategies.
|
||||
*
|
||||
* @param predicate The predicate that decides whether or not to apply the custom execution handler to the given method
|
||||
* @param function The function that produces the command execution handler
|
||||
*/
|
||||
public void registerCommandExecutionMethodFactory(
|
||||
final @NonNull Predicate<@NonNull Method> predicate,
|
||||
final @NonNull Function<MethodCommandExecutionHandler.CommandMethodContext<C>, MethodCommandExecutionHandler<C>> function
|
||||
) {
|
||||
this.commandMethodFactories.put(predicate, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a builder modifier for a specific annotation. The builder modifiers are
|
||||
* allowed to act on a {@link Command.Builder} after all arguments have been added
|
||||
|
|
@ -265,12 +294,6 @@ public final class AnnotationParser<C> {
|
|||
if (!method.isAccessible()) {
|
||||
method.setAccessible(true);
|
||||
}
|
||||
if (method.getReturnType() != Void.TYPE) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"@CommandMethod annotated method '%s' has non-void return type",
|
||||
method.getName()
|
||||
));
|
||||
}
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"@CommandMethod annotated method '%s' is static! @CommandMethod annotated methods should not be static.",
|
||||
|
|
@ -460,13 +483,25 @@ public final class AnnotationParser<C> {
|
|||
builder = builder.senderType(senderType);
|
||||
}
|
||||
try {
|
||||
/* Construct the handler */
|
||||
final CommandExecutionHandler<C> commandExecutionHandler = new MethodCommandExecutionHandler<>(
|
||||
final MethodCommandExecutionHandler.CommandMethodContext<C> context = new MethodCommandExecutionHandler.CommandMethodContext<>(
|
||||
instance,
|
||||
commandArguments,
|
||||
method,
|
||||
this.getParameterInjectorRegistry()
|
||||
);
|
||||
|
||||
/* Create the command execution handler */
|
||||
CommandExecutionHandler<C> commandExecutionHandler = new MethodCommandExecutionHandler<>(context);
|
||||
for (final Map.Entry<Predicate<Method>, Function<MethodCommandExecutionHandler.CommandMethodContext<C>, MethodCommandExecutionHandler<C>>> entry :
|
||||
commandMethodFactories.entrySet()) {
|
||||
if (entry.getKey().test(method)) {
|
||||
commandExecutionHandler = entry.getValue().apply(context);
|
||||
|
||||
/* Once we have our custom handler, we stop */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder = builder.handler(commandExecutionHandler);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Failed to construct command execution handler", e);
|
||||
|
|
|
|||
|
|
@ -40,38 +40,61 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
||||
/**
|
||||
* A command execution handler that invokes a method.
|
||||
*
|
||||
* @param <C> Command sender type.
|
||||
*/
|
||||
public class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
||||
|
||||
private final CommandMethodContext<C> context;
|
||||
private final Parameter[] parameters;
|
||||
private final MethodHandle methodHandle;
|
||||
private final Map<String, CommandArgument<C, ?>> commandArguments;
|
||||
private final ParameterInjectorRegistry<C> injectorRegistry;
|
||||
private final AnnotationAccessor annotationAccessor;
|
||||
|
||||
MethodCommandExecutionHandler(
|
||||
final @NonNull Object instance,
|
||||
final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments,
|
||||
final @NonNull Method method,
|
||||
final @NonNull ParameterInjectorRegistry<C> injectorRegistry
|
||||
/**
|
||||
* Constructs a new method command execution handler
|
||||
*
|
||||
* @param context The context
|
||||
*/
|
||||
public MethodCommandExecutionHandler(
|
||||
final @NonNull CommandMethodContext<C> context
|
||||
) throws Exception {
|
||||
this.commandArguments = commandArguments;
|
||||
method.setAccessible(true);
|
||||
this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance);
|
||||
this.parameters = method.getParameters();
|
||||
this.injectorRegistry = injectorRegistry;
|
||||
this.annotationAccessor = AnnotationAccessor.of(method);
|
||||
this.context = context;
|
||||
this.methodHandle = MethodHandles.lookup().unreflect(context.method).bindTo(context.instance);
|
||||
this.parameters = context.method.getParameters();
|
||||
this.annotationAccessor = AnnotationAccessor.of(context.method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void execute(final @NonNull CommandContext<C> commandContext) {
|
||||
final List<Object> arguments = new ArrayList<>(this.parameters.length);
|
||||
final FlagContext flagContext = commandContext.flags();
|
||||
/* Invoke the command method */
|
||||
try {
|
||||
this.methodHandle.invokeWithArguments(createParameterValues(
|
||||
commandContext,
|
||||
commandContext.flags(),
|
||||
true)
|
||||
);
|
||||
} catch (final Error e) {
|
||||
throw e;
|
||||
} catch (final Throwable throwable) {
|
||||
throw new CommandExecutionException(throwable, commandContext);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bind parameters to context */
|
||||
protected List<Object> createParameterValues(
|
||||
final CommandContext<C> commandContext,
|
||||
final FlagContext flagContext,
|
||||
final boolean throwOnMissing
|
||||
) {
|
||||
final List<Object> arguments = new ArrayList<>(this.parameters.length);
|
||||
for (final Parameter parameter : this.parameters) {
|
||||
if (parameter.isAnnotationPresent(Argument.class)) {
|
||||
final Argument argument = parameter.getAnnotation(Argument.class);
|
||||
final CommandArgument<C, ?> commandArgument = this.commandArguments.get(argument.value());
|
||||
final CommandArgument<C, ?> commandArgument = this.context.commandArguments.get(argument.value());
|
||||
if (commandArgument.isRequired()) {
|
||||
arguments.add(commandContext.get(argument.value()));
|
||||
} else {
|
||||
|
|
@ -89,14 +112,14 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
|||
if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) {
|
||||
arguments.add(commandContext.getSender());
|
||||
} else {
|
||||
final Optional<?> value = this.injectorRegistry.getInjectable(
|
||||
final Optional<?> value = this.context.injectorRegistry.getInjectable(
|
||||
parameter.getType(),
|
||||
commandContext,
|
||||
AnnotationAccessor.of(AnnotationAccessor.of(parameter), this.annotationAccessor)
|
||||
);
|
||||
if (value.isPresent()) {
|
||||
arguments.add(value.get());
|
||||
} else {
|
||||
} else if (throwOnMissing) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unknown command parameter '%s' in method '%s'",
|
||||
parameter.getName(),
|
||||
|
|
@ -106,15 +129,106 @@ class MethodCommandExecutionHandler<C> implements CommandExecutionHandler<C> {
|
|||
}
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
/* Invoke the command method */
|
||||
try {
|
||||
this.methodHandle.invokeWithArguments(arguments);
|
||||
} catch (final Error e) {
|
||||
throw e;
|
||||
} catch (final Throwable throwable) {
|
||||
throw new CommandExecutionException(throwable, commandContext);
|
||||
/**
|
||||
* Returns the command method context
|
||||
*
|
||||
* @return The context
|
||||
*/
|
||||
public @NonNull CommandMethodContext<C> context() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all parameters passed to the method
|
||||
*
|
||||
* @return The parameters
|
||||
*/
|
||||
public final @NonNull Parameter @NonNull [] parameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compiled method handle for the command method.
|
||||
*
|
||||
* @return The method handle
|
||||
*/
|
||||
public final @NonNull MethodHandle methodHandle() {
|
||||
return this.methodHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* The annotation accessor for the command method
|
||||
*
|
||||
* @return Annotation accessor
|
||||
*/
|
||||
public final AnnotationAccessor annotationAccessor() {
|
||||
return this.annotationAccessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for command methods
|
||||
*
|
||||
* @param <C> Command sender type
|
||||
*/
|
||||
public static class CommandMethodContext<C> {
|
||||
|
||||
private final Object instance;
|
||||
private final Map<String, CommandArgument<C, ?>> commandArguments;
|
||||
private final Method method;
|
||||
private final ParameterInjectorRegistry<C> injectorRegistry;
|
||||
|
||||
CommandMethodContext(
|
||||
final @NonNull Object instance,
|
||||
final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments,
|
||||
final @NonNull Method method,
|
||||
final @NonNull ParameterInjectorRegistry<C> injectorRegistry
|
||||
) {
|
||||
this.instance = instance;
|
||||
this.commandArguments = commandArguments;
|
||||
this.method = method;
|
||||
this.method.setAccessible(true);
|
||||
this.injectorRegistry = injectorRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance that owns the command method
|
||||
*
|
||||
* @return The instance
|
||||
*/
|
||||
public @NonNull Object instance() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The command method
|
||||
*
|
||||
* @return The method
|
||||
*/
|
||||
public final @NonNull Method method() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* The compiled command arguments
|
||||
*
|
||||
* @return Compiled command arguments
|
||||
*/
|
||||
public final @NonNull Map<@NonNull String, @NonNull CommandArgument<C, ?>> commandArguments() {
|
||||
return this.commandArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* The injector registry
|
||||
*
|
||||
* @return Injector registry
|
||||
*/
|
||||
public final @NonNull ParameterInjectorRegistry<C> injectorRegistry() {
|
||||
return this.injectorRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue