diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf8faeb..63e140b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added access to the CloudBrigadierManager from Brigadier-enabled command managers - - Added parameter injectors + - Added parameter injectors (cloud-annotations) - Store currently parsing command argument in the command context - Added a method to CloudBrigadierManager to enable or disable Brigadier native suggestions for specific argument types - Added a method to get the failure reason of SelectorParseExceptions + - Added some methods to FlagContext to work with flag values as optionals + - Allow for use of named suggestion providers with `@Flag`s (cloud-annotations) ### Changed - Allow for use of `@Completions` annotation with argument types other than String diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Argument.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Argument.java index e9fc1aef..398cdc8e 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Argument.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Argument.java @@ -23,6 +23,8 @@ // package cloud.commandframework.annotations; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -41,14 +43,14 @@ public @interface Argument { * * @return Argument name */ - String value(); + @NonNull String value(); /** * Name of the argument parser * * @return Argument name */ - String parserName() default ""; + @NonNull String parserName() default ""; /** * Name of the suggestions provider to use. If the string is left empty, the default @@ -64,20 +66,20 @@ public @interface Argument { * should be used instead * @since 1.1.0 */ - String suggestions() default ""; + @NonNull String suggestions() default ""; /** * Get the default value * * @return Default value */ - String defaultValue() default ""; + @NonNull String defaultValue() default ""; /** * The argument description * * @return Argument description */ - String description() default ""; + @NonNull String description() default ""; } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java index 2dc6cdf9..5e632c5f 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandDescription.java @@ -24,6 +24,7 @@ package cloud.commandframework.annotations; import cloud.commandframework.arguments.parser.StandardParameters; +import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -42,6 +43,6 @@ public @interface CommandDescription { * * @return Command syntax */ - String value() default ""; + @NonNull String value() default ""; } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandMethod.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandMethod.java index 69baf09d..4edc9d9e 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandMethod.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandMethod.java @@ -23,6 +23,8 @@ // package cloud.commandframework.annotations; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -40,13 +42,13 @@ public @interface CommandMethod { * * @return Command syntax */ - String value(); + @NonNull String value(); /** * The required sender * * @return Required sender */ - Class requiredSender() default Object.class; + @NonNull Class requiredSender() default Object.class; } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java index 907f421c..0f341f25 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/CommandPermission.java @@ -23,6 +23,8 @@ // package cloud.commandframework.annotations; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -40,6 +42,6 @@ public @interface CommandPermission { * * @return Command permission */ - String value() default ""; + @NonNull String value() default ""; } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Flag.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Flag.java index de47def5..af9e461a 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/Flag.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/Flag.java @@ -23,15 +23,21 @@ // package cloud.commandframework.annotations; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.BiFunction; /** * Indicates that the parameter should be treated like a {@link cloud.commandframework.arguments.flags.CommandFlag}. - * If the parameter is a {@code boolean} then a presence flag will be created, else a value flag will be created - * and the parser will be resolved the same way as it would for a {@link Argument} + * */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @@ -42,14 +48,14 @@ public @interface Flag { * * @return Flag name */ - String value(); + @NonNull String value(); /** * Flag aliases * * @return Aliases */ - String[] aliases() default ""; + @NonNull String[] aliases() default ""; /** * Name of the parser. Leave empty to use @@ -57,13 +63,29 @@ public @interface Flag { * * @return Parser name */ - String parserName() default ""; + @NonNull String parserName() default ""; + + /** + * Name of the suggestions provider to use. If the string is left empty, the default + * provider for the argument parser will be used. Otherwise, + * the {@link cloud.commandframework.arguments.parser.ParserRegistry} instance in the + * {@link cloud.commandframework.CommandManager} will be queried for a matching suggestion provider. + *

+ * For this to work, the suggestion needs to be registered in the parser registry. To do this, use + * {@link cloud.commandframework.arguments.parser.ParserRegistry#registerSuggestionProvider(String, BiFunction)}. + * The registry instance can be retrieved using {@link cloud.commandframework.CommandManager#getParserRegistry()}. + * + * @return The name of the suggestion provider, or {@code ""} if the default suggestion provider for the argument parser + * should be used instead + * @since 1.2.0 + */ + @NonNull String suggestions() default ""; /** * The argument description * * @return Argument description */ - String description() default ""; + @NonNull String description() default ""; } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java index 18a369db..5054b9b0 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java @@ -37,6 +37,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Collection; import java.util.LinkedList; +import java.util.List; +import java.util.function.BiFunction; import java.util.function.Function; final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNull CommandFlag>> { @@ -78,14 +80,25 @@ final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNu parameter.getType().getCanonicalName(), flag.value(), method.getName() )); } - final CommandArgument.Builder argumentBuilder = CommandArgument.ofType( + final BiFunction> suggestionProvider; + if (!flag.suggestions().isEmpty()) { + suggestionProvider = registry.getSuggestionProvider(flag.suggestions()).orElse(null); + } else { + suggestionProvider = null; + } + final CommandArgument.Builder argumentBuilder0 = CommandArgument.ofType( parameter.getType(), flag.value() ); - final CommandArgument argument = argumentBuilder.asRequired() + final CommandArgument.Builder argumentBuilder = argumentBuilder0.asRequired() .manager(this.commandManager) - .withParser(parser) - .build(); + .withParser(parser); + final CommandArgument argument; + if (suggestionProvider != null) { + argument = argumentBuilder.withSuggestionsProvider(suggestionProvider).build(); + } else { + argument = argumentBuilder.build(); + } flags.add(builder.withArgument(argument).build()); } } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/FlagContext.java b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/FlagContext.java index ecf6d942..560359f8 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/FlagContext.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/FlagContext.java @@ -28,6 +28,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.HashMap; import java.util.Map; +import java.util.Optional; /** * Flag value mappings @@ -78,11 +79,11 @@ public final class FlagContext { } /** - * Check whether or not a flag is present. This will return {@code false} + * Check whether a presence flag is present. This will return {@code false} * for all value flags. * * @param flag Flag name - * @return {@code true} if the flag is presence and the flag is a presence flag, + * @return {@code true} if the flag is a presence flag and is present, * else {@code false} */ public boolean isPresent(final @NonNull String flag) { @@ -90,6 +91,25 @@ public final class FlagContext { return FLAG_PRESENCE_VALUE.equals(value); } + /** + * Get a flag value as an optional. Will be empty if the value is not present. + * + * @param name Flag name + * @param Value type + * @return Optional containing stored value if present + * @since 1.2.0 + */ + public @NonNull Optional getValue( + final @NonNull String name + ) { + final Object value = this.flagValues.get(name); + if (value == null) { + return Optional.empty(); + } + @SuppressWarnings("unchecked") final T casted = (T) value; + return Optional.of(casted); + } + /** * Get a flag value * @@ -102,12 +122,22 @@ public final class FlagContext { final @NonNull String name, final @Nullable T defaultValue ) { - final Object value = this.flagValues.get(name); - if (value == null) { - return defaultValue; - } - @SuppressWarnings("unchecked") final T casted = (T) value; - return casted; + return this.getValue(name).orElse(defaultValue); + } + + /** + * Check whether a flag is present. This will return {@code true} if the flag + * is a presence flag and is present, or if the flag is a value flag and has + * a value provided. + * + * @param name Flag name + * @return {@code true} if the flag is present, else {@code false} + * @since 1.2.0 + */ + public boolean hasFlag( + final @NonNull String name + ) { + return this.getValue(name).isPresent(); } } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ParserRegistry.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ParserRegistry.java index 411418d5..6577c03c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ParserRegistry.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ParserRegistry.java @@ -140,7 +140,7 @@ public interface ParserRegistry { * Get a named suggestion provider, if a suggestion provider with the given name exists in the registry * * @param name Suggestion provider name. The name is case independent. - * @return Optional that either contains the suggestion provider name, or nothing ({@link Optional#empty()}) if no + * @return Optional that either contains the suggestion provider, or is empty if no * suggestion provider is registered with the given name * @see #registerSuggestionProvider(String, BiFunction) Register a suggestion provider * @since 1.1.0 diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java index 113f0005..6f428ab3 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftExceptionHandler.java @@ -253,7 +253,7 @@ public final class MinecraftExceptionHandler { */ INVALID_SENDER, /** - * The sender does not have permission to execute the command (@{@link NoPermissionException}) + * The sender does not have permission to execute the command ({@link NoPermissionException}) */ NO_PERMISSION, /**