Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow developers to control Arguments' CommandSyntaxExceptions #370

Closed
wants to merge 86 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
db13e38
Add option to add custom error handling when Argument parse fails
willkroboth Nov 19, 2022
dc89c3a
Merge pull request #366 from willkroboth/dev/dev
willkroboth Nov 19, 2022
25b975d
Add Javadocs to new methods and classes
willkroboth Nov 19, 2022
e601e74
Add sender and input to ArgumentParseExceptionContext
willkroboth Nov 20, 2022
c07603a
Intercept errors during Brigadier's initial parse
willkroboth Nov 20, 2022
66c3284
Attempt serializing custom ArgumentType
willkroboth Nov 25, 2022
6aab75d
Fix ExceptionHandlingArgumentType serialization
willkroboth Nov 26, 2022
17fa93f
Clean up code and add API methods
willkroboth Nov 26, 2022
06aca10
Add InitialParseExceptionArgument to testing arguments
willkroboth Nov 26, 2022
6190486
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Nov 26, 2022
417fccb
Implement InitialParseExceptionArgument for other applicable arguments
willkroboth Nov 26, 2022
bd5fbf8
Reduce code duplication by storing InitialParseExceptionHandlers in a…
willkroboth Nov 26, 2022
d591895
Implement NMS#registerCustomArgumentType
willkroboth Nov 26, 2022
c2de33a
Clean up code
willkroboth Nov 27, 2022
cfc1be4
Make CodeFactor happier (and me a little) by reordering methods in th…
willkroboth Nov 27, 2022
ad65892
Fix 1.16.2-3 & 1.16.4-5 ExceptionHandlingArgumentSerializer method re…
willkroboth Nov 27, 2022
fea28da
Fix 1.13-1.16 method reflection
willkroboth Nov 27, 2022
84e0f39
Make GitHub Actions check NMS Common builds on all versions it is sup…
willkroboth Nov 27, 2022
6fde891
Merge remote-tracking branch 'origin/dev/argument-exceptions' into de…
willkroboth Nov 27, 2022
15f2b3a
Fix 1.17 registerCustomArgumentType
willkroboth Nov 27, 2022
537cf5a
Fix 1.18 registerCustomArgumentType
willkroboth Nov 27, 2022
e5cc28b
Remove unused (and sometimes invalid) imports from NMS_Common
willkroboth Nov 27, 2022
b25b3cd
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Nov 28, 2022
eab3466
Clean up after merge
willkroboth Nov 28, 2022
f5bef67
Add WrapperStringReader to InitialParseExceptionContext
willkroboth Dec 2, 2022
5e1f02d
Add sender, input, and previousArguments to ArgumentParseExceptionCon…
willkroboth Dec 3, 2022
597d609
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Dec 4, 2022
c68c450
Clean up imports after merging
willkroboth Dec 4, 2022
f48dc47
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Dec 19, 2022
ed01fbf
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Dec 20, 2022
df773c9
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Dec 21, 2022
5a7f377
Fix registerCustomArgumentType in 1.19.3
willkroboth Jan 3, 2023
a62ddc3
Add example command for standard testing (Bukkit)
willkroboth Jan 3, 2023
9860822
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 3, 2023
a35913f
Clean up after merge
willkroboth Jan 3, 2023
1b6511a
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 4, 2023
d3d8c34
Fix parameterization of ArgumentParseExceptionHandler
willkroboth Jan 5, 2023
bc6b58f
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 5, 2023
ab3d4e0
Hotfix tab-complete for provided suggestions in 1.19.3
willkroboth Jan 5, 2023
92c7625
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 9, 2023
574b103
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 13, 2023
c8c527b
Fix ArgumentParseExceptionHandlers not getting processed
willkroboth Jan 13, 2023
d8e726b
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 13, 2023
4fe1673
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 16, 2023
d9481cc
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Jan 25, 2023
703e14c
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 1, 2023
faf5c4e
Refreeze COMMAND_ARGUMENT_TYPE registry after adding custom argument …
willkroboth Feb 1, 2023
a187f86
Implement equals, hashCode, toString for WrapperStringReader
willkroboth Feb 1, 2023
08e1592
Add @RequireField for 1.19 NMS
willkroboth Feb 1, 2023
29d82d5
Remove classes for 1.13 and 1.14
willkroboth Feb 1, 2023
5ba3dbd
Fix 1.19 NMS @RequireField annotations
willkroboth Feb 2, 2023
4073c1d
Make Sonarcloud a bit happier
willkroboth Feb 2, 2023
4cae893
Add comments naming obfuscated method names
willkroboth Feb 2, 2023
5b8339e
Add TODOs explaining why Field reflection is not yet checked at compi…
willkroboth Feb 2, 2023
4086694
'fix' (?) javadoc format for added records
willkroboth Feb 2, 2023
54b538d
Implement equals, hashCode, toString for WrapperCommandSyntaxException
willkroboth Feb 2, 2023
245f969
Use VarHandles instead of static Method reflection
willkroboth Feb 2, 2023
37d489b
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 3, 2023
6b27ebf
Switch dev/argument-exceptions to use CommandArguments instead of Obj…
willkroboth Feb 3, 2023
f3af7db
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 19, 2023
97438e0
Add simple InitialParseExceptionArgumentTest
willkroboth Feb 20, 2023
80b828e
Only mock register ExceptionHandlingArgumentType if it is not already…
willkroboth Feb 20, 2023
677ec3c
Fix MethodHandle reflection for 1_18_R1
willkroboth Feb 20, 2023
3ba0782
Fix MethodHandle reflection for all versions
willkroboth Feb 20, 2023
56ecf24
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 20, 2023
13f94ea
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 21, 2023
e40d9bb
Refactor variable names in MethodHandles reflection
willkroboth Feb 21, 2023
b6cc9f7
Merge remote-tracking branch 'origin/dev/argument-exceptions' into de…
willkroboth Feb 21, 2023
c915f7e
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Feb 21, 2023
f5d6c89
Revise reflection in 1_15
willkroboth Feb 22, 2023
1b22e55
Add and use SafeVarHandle#getUnknownInstanceType
willkroboth Feb 22, 2023
0e25e98
Update use of reflection in other ArgumentSerializers
willkroboth Feb 23, 2023
4b42d10
Fix ArgumentSerializer reflection
willkroboth Feb 23, 2023
868985f
Share code between ArgumentSerializers
willkroboth Feb 23, 2023
0a195df
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Mar 9, 2023
9c27823
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Mar 13, 2023
fde031f
Don't include Object in the platform load log
willkroboth Mar 13, 2023
a7570d0
Add simple test for ArgumentParseException
willkroboth Mar 17, 2023
deda323
You gotta register the test command to use it!
willkroboth Mar 17, 2023
2e2c5dc
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Mar 21, 2023
08f5218
Update 1.19.4 NMS
willkroboth Mar 21, 2023
5652dab
Fix frozen field access in 1.19 NMS when testing
willkroboth Mar 21, 2023
b13102c
Fix register method access in 1.19 NMS when testing
willkroboth Mar 23, 2023
a36f3e0
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Mar 24, 2023
977a2c8
Merge branch 'dev/dev' into dev/argument-exceptions
willkroboth Apr 2, 2023
e3d5679
Provide the parametrized type for SafeVarHandles in ArgumentSerializers
willkroboth Apr 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* Class to register commands with the 1.13 command UI
*
*/
public class CommandAPI {
public final class CommandAPI {
// Cannot be instantiated
private CommandAPI() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,8 @@
*******************************************************************************/
package dev.jorel.commandapi;

import java.awt.Component;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
Expand All @@ -51,20 +33,23 @@
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;

import dev.jorel.commandapi.arguments.AbstractArgument;
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
import dev.jorel.commandapi.arguments.CustomProvidedArgument;
import dev.jorel.commandapi.arguments.Literal;
import dev.jorel.commandapi.arguments.MultiLiteral;
import dev.jorel.commandapi.arguments.PreviewInfo;
import dev.jorel.commandapi.arguments.Previewable;
import dev.jorel.commandapi.arguments.*;
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
import dev.jorel.commandapi.executors.CommandArguments;
import dev.jorel.commandapi.executors.ExecutionInfo;
import dev.jorel.commandapi.preprocessor.RequireField;
import dev.jorel.commandapi.wrappers.PreviewableFunction;

import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

/**
* The "brains" behind the CommandAPI.
* Handles command registration
Expand Down Expand Up @@ -151,14 +136,15 @@ public void onDisable() {
platform.onDisable();
CommandAPIHandler.resetInstance();
}

private static void resetInstance() {
CommandAPIHandler.instance = null;
}

public static CommandAPIHandler<?, ?, ?> getInstance() {
if(CommandAPIHandler.instance != null) {
return CommandAPIHandler.instance;
public static <Argument extends AbstractArgument<?, ?, Argument, CommandSender>, CommandSender, Source>
CommandAPIHandler<Argument, CommandSender, Source> getInstance() {
if (CommandAPIHandler.instance != null) {
return (CommandAPIHandler<Argument, CommandSender, Source>) CommandAPIHandler.instance;
} else {
throw new IllegalStateException("Tried to access CommandAPIHandler instance, but it was null! Are you using CommandAPI features before calling CommandAPI#onLoad?");
}
Expand Down Expand Up @@ -293,7 +279,7 @@ CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, Argument[] arg
*/
Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException {
if (value.isListed()) {
return value.parseArgument(cmdCtx, key, previousArgs);
return value.parseArgumentHandleError(cmdCtx, key, previousArgs);
} else {
return null;
}
Expand Down Expand Up @@ -657,11 +643,11 @@ void register(CommandMetaData<CommandSender> meta, final Argument[] args,

platform.postCommandRegistration(resultantNode, aliasNodes);
}

/**
* Checks for duplicate argument node names and logs them as errors in the
* console
*
*
* @param args the list of arguments
* @param humanReadableCommandArgSyntax the human readable command argument
* syntax
Expand Down Expand Up @@ -691,7 +677,7 @@ private boolean checkForDuplicateArgumentNodeNames(Argument[] args, String human
}
return true;
}

private void writeDispatcherToFile() {
File file = CommandAPI.getConfiguration().getDispatcherFile();
if (file != null) {
Expand Down Expand Up @@ -785,12 +771,27 @@ LiteralArgumentBuilder<Source> getLiteralArgumentBuilderArgument(String commandN
}

RequiredArgumentBuilder<Source, ?> requiredArgumentBuilder = RequiredArgumentBuilder
.argument(argument.getNodeName(), argument.getRawType());
.argument(argument.getNodeName(), wrapArgumentType(argument, argument.getRawType()));

return requiredArgumentBuilder.requires(css -> permissionCheck(platform.getCommandSenderFromCommandSource(css),
argument.getArgumentPermission(), argument.getRequirements())).suggests(newSuggestionsProvider);
}

<T> ArgumentType<T> wrapArgumentType(Argument argument, ArgumentType<T> rawType) {
if (argument instanceof ICustomArgument) {
argument = ((ICustomArgument<Argument>) argument).getBaseArgument();
// CustomArgument should set its raw type to baseArgument's raw type, so that is already correct
}

if (!(argument instanceof InitialParseExceptionArgument)) return rawType;

InitialParseExceptionArgument<T, ?> iPEA = (InitialParseExceptionArgument<T, ?>) argument.instance();

Optional<InitialParseExceptionHandler<T>> handler = iPEA.getInitialParseExceptionHandler();
if (handler.isEmpty()) return rawType;
return new ExceptionHandlingArgumentType<>(rawType, handler.get());
}

CommandArguments generatePreviousArguments(CommandContext<Source> context, Argument[] args, String nodeName)
throws CommandSyntaxException {
// Populate Object[], which is our previously filled arguments
Expand Down Expand Up @@ -850,7 +851,7 @@ SuggestionProvider<Source> toSuggestions(Argument theArgument, Argument[] args,
* @param path a list of Strings representing the path (names of command nodes)
* to (and including) the previewable argument
* @return a function that takes in a {@link PreviewInfo} and returns a
* {@link Component}. If such a function is not available, this will
* Component representing the preview. If such a function is not available, this will
* return a function that always returns null.
*/
@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dev.jorel.commandapi;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class SafeStaticOneParameterMethodHandle<ReturnType, ParameterType> {

private final MethodHandle handle;

private SafeStaticOneParameterMethodHandle(MethodHandle handle) {
this.handle = handle;
}

private static <ReturnType, ParameterType> SafeStaticOneParameterMethodHandle<ReturnType, ParameterType> of(Class<?> classType, String fieldName, String mojangMappedFieldName, Class<? super ReturnType> returnType, Class<? super ParameterType> parameterType) throws ReflectiveOperationException {
return new SafeStaticOneParameterMethodHandle<>(MethodHandles.privateLookupIn(classType, MethodHandles.lookup()).findStatic(classType, SafeVarHandle.USING_MOJANG_MAPPINGS ? mojangMappedFieldName : fieldName, MethodType.methodType(returnType, parameterType)));
}

public static <ReturnType, ParameterType> SafeStaticOneParameterMethodHandle<ReturnType, ParameterType> ofOrNull(Class<?> classType, String fieldName, String mojangMappedFieldName, Class<? super ReturnType> returnType, Class<? super ParameterType> parameterType) {
try {
return of(classType, fieldName, mojangMappedFieldName, returnType, parameterType);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
return null;
}
}

public ReturnType invoke(ParameterType parameter) throws Throwable {
return (ReturnType) handle.invoke(parameter);
}

public ReturnType invokeOrNull(ParameterType parameter) {
try {
return invoke(parameter);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public FieldType get(Type instance) {
return (FieldType) handle.get(instance);
}

public FieldType getUnknownInstanceType(Object instance) {
return (FieldType) handle.get(instance);
}

public FieldType getStatic() {
return (FieldType) handle.get(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.jorel.commandapi.AbstractArgumentTree;
import dev.jorel.commandapi.CommandAPIHandler;
import dev.jorel.commandapi.CommandPermission;
import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;

import java.util.ArrayList;
Expand Down Expand Up @@ -94,6 +96,41 @@ public final String getNodeName() {
return this.nodeName;
}

/**
* Parses an argument using {@link Argument#parseArgument(CommandContext, String, CommandArguments)}.
* If the parse fails and a {@link CommandSyntaxException} is thrown, the exception is passed to
* the active {@link ArgumentParseExceptionHandler} for this argument, which can be defined by
* developer using {@link Argument#withArgumentParseExceptionHandler(ArgumentParseExceptionHandler)}.
* This is intended for use by the internals of the CommandAPI and isn't expected to be used outside
* the CommandAPI
*
* @param <CommandSourceStack> the command source type
* @param cmdCtx the context which ran this command
* @param key the name of the argument node
* @param previousArgs a {@link CommandArguments} object holding previous parsed arguments
* @return the parsed object represented by this argument, or the object returned by
* the active {@link ArgumentParseExceptionHandler} if parsing fails
* @throws CommandSyntaxException if parsing fails and
* the active {@link ArgumentParseExceptionHandler} throws an exception as well
*/
public final <CommandSourceStack> T parseArgumentHandleError(CommandContext<CommandSourceStack> cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException {
try {
return parseArgument(cmdCtx, key, previousArgs);
} catch (CommandSyntaxException original) {
try {
return exceptionHandler.handleException(new ArgumentParseExceptionContext<>(
new WrapperCommandSyntaxException(original),
CommandAPIHandler.<Argument, CommandSender, CommandSourceStack>getInstance().getPlatform()
.getCommandSenderFromCommandSource(cmdCtx.getSource()).getSource(),
cmdCtx.getArgument(key, Object.class),
previousArgs
));
} catch (WrapperCommandSyntaxException newException) {
throw newException.getException();
}
}
}

/**
* Parses an argument, returning the specific Bukkit object that the argument
* represents. This is intended for use by the internals of the CommandAPI and
Expand Down Expand Up @@ -323,6 +360,31 @@ public final Impl combineWith(Argument... combinedArguments) {
return instance();
}

////////////////////////
// Exception Handling //
///////////////////////

private ArgumentParseExceptionHandler<T, CommandSender> exceptionHandler = context -> { throw context.exception(); };

/**
* Sets the {@link ArgumentParseExceptionHandler} this Argument should use when it fails to parse its input.
*
* @param exceptionHandler The new {@link ArgumentParseExceptionHandler} this argument should use
* @return this current argument
*/
public final Impl withArgumentParseExceptionHandler(ArgumentParseExceptionHandler<T, CommandSender> exceptionHandler) {
this.exceptionHandler = exceptionHandler;
return instance();
}

/**
* Returns the {@link ArgumentParseExceptionHandler} this argument is using
* @return The {@link ArgumentParseExceptionHandler} this argument is using
*/
public final ArgumentParseExceptionHandler<T, CommandSender> getArgumentParseExceptionHandler() {
return this.exceptionHandler;
}

///////////
// Other //
///////////
Expand Down Expand Up @@ -357,4 +419,4 @@ public void copyPermissionsAndRequirements(Argument argument) {
public String toString() {
return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dev.jorel.commandapi.arguments;

import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;
import dev.jorel.commandapi.executors.CommandArguments;

/**
* A record containing information on why an Argument failed to parse.
*
* @param exception The CommandSyntaxException that was thrown when the Argument failed to parse
* @param sender The CommandSender who sent the command that caused the exception
* @param input The raw object returned by the initial Brigadier parse for the Argument
* @param previousArguments - a {@link CommandArguments} object holding previously declared (and parsed) arguments. This can
* be used as if it were arguments in a command executor method
*/
public record ArgumentParseExceptionContext<CommandSender>(
/**
* @param exception The CommandSyntaxException that was thrown when the Argument failed to parse
*/
WrapperCommandSyntaxException exception,
/**
* @param sender The CommandSender who sent the command that caused the exception
*/
CommandSender sender,
/**
* @param input The raw object returned by the initial Brigadier parse for the Argument
*/
Object input,
/**
* @param previousArguments - a {@link CommandArguments} object holding previously declared (and parsed) arguments.
* This can be used as if it were arguments in a command executor method
*/
CommandArguments previousArguments) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.jorel.commandapi.arguments;

import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException;

/**
* A FunctionalInterface for defining custom behavior when an Argument fails to parse.
* See {@link ArgumentParseExceptionHandler#handleException(ArgumentParseExceptionContext)}
*
* @param <T> The class of the object returned by the Argument this object is handling
* @param <CommandSender> The CommandSender class being used
*/
@FunctionalInterface
public interface ArgumentParseExceptionHandler<T, CommandSender> {
/**
* A method that handles when an Argument fails to parse.
* It can either return an object or throw a different exception.
*
* @param context a {@link ArgumentParseExceptionContext} record that holds information
* about why and when the Argument failed to parse
* @return A new object in place of the failed parse
* @throws WrapperCommandSyntaxException A new exception to pass on
*/
T handleException(ArgumentParseExceptionContext<CommandSender> context) throws WrapperCommandSyntaxException;
}
Loading