diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java index 5cce3062..1b199a1a 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java @@ -26,6 +26,7 @@ package cloud.commandframework.bukkit; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.standard.UUIDArgument; import cloud.commandframework.brigadier.CloudBrigadierManager; +import cloud.commandframework.bukkit.argument.NamespacedKeyArgument; import cloud.commandframework.bukkit.internal.CommandBuildContextSupplier; import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes; import cloud.commandframework.bukkit.parsers.BlockPredicateArgument; @@ -85,6 +86,9 @@ public final class BukkitBrigadierMapper { } catch (final IllegalArgumentException ignore) { // < 1.16 } + /* Map NamespacedKey */ + this.mapSimpleNMS(new TypeToken>() { + }, "resource_location", true); /* Map Enchantment */ this.mapSimpleNMS(new TypeToken>() { }, "item_enchantment"); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java index 958a9c5b..615f36d4 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionKeys.java @@ -89,6 +89,27 @@ public final class BukkitCaptionKeys { */ public static final Caption ARGUMENT_PARSE_FAILURE_LOCATION_MIXED_LOCAL_ABSOLUTE = of( "argument.parse.failure.location.mixed_local_absolute"); + /** + * Variables: {input} + * + * @since 1.7.0 + */ + public static final Caption ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE = + of("argument.parse.failure.namespacedkey.namespace"); + /** + * Variables: {input} + * + * @since 1.7.0 + */ + public static final Caption ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY = + of("argument.parse.failure.namespacedkey.key"); + /** + * Variables: {input} + * + * @since 1.7.0 + */ + public static final Caption ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE = + of("argument.parse.failure.namespacedkey.need_namespace"); private BukkitCaptionKeys() { } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java index b38e6d59..2fe0fd5f 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCaptionRegistry.java @@ -83,6 +83,28 @@ public class BukkitCaptionRegistry extends SimpleCaptionRegistry { */ public static final String ARGUMENT_PARSE_FAILURE_LOCATION_MIXED_LOCAL_ABSOLUTE = "Cannot mix local and absolute coordinates. (either all coordinates use '^' or none do)"; + /** + * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE} + * + * @since 1.7.0 + */ + public static final String ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE = + "Invalid namespace '{input}'. Must be [a-z0-9._-]"; + /** + * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY} + * + * @since 1.7.0 + */ + public static final String ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY = + "Invalid key '{input}'. Must be [a-z0-9/._-]"; + /** + * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY} + * + * @since 1.7.0 + */ + public static final String ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE = + "Invalid input '{input}', requires an explicit namespace."; + protected BukkitCaptionRegistry() { super(); @@ -134,6 +156,18 @@ public class BukkitCaptionRegistry extends SimpleCaptionRegistry { BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_LOCATION_MIXED_LOCAL_ABSOLUTE, (caption, sender) -> ARGUMENT_PARSE_FAILURE_LOCATION_MIXED_LOCAL_ABSOLUTE ); + this.registerMessageFactory( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE + ); + this.registerMessageFactory( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY + ); + this.registerMessageFactory( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE, + (caption, sender) -> ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE + ); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java index c8c72384..d114c6cf 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java @@ -26,13 +26,18 @@ package cloud.commandframework.bukkit; import cloud.commandframework.CloudCapability; import cloud.commandframework.CommandManager; import cloud.commandframework.CommandTree; +import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.brigadier.BrigadierManagerHolder; import cloud.commandframework.brigadier.CloudBrigadierManager; +import cloud.commandframework.bukkit.annotation.specifier.DefaultNamespace; +import cloud.commandframework.bukkit.annotation.specifier.RequireExplicitNamespace; +import cloud.commandframework.bukkit.argument.NamespacedKeyArgument; import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; import cloud.commandframework.bukkit.data.ProtoItemStack; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; import cloud.commandframework.bukkit.parsers.BlockPredicateArgument; import cloud.commandframework.bukkit.parsers.EnchantmentArgument; import cloud.commandframework.bukkit.parsers.ItemStackArgument; @@ -162,6 +167,18 @@ public class BukkitCommandManager extends CommandManager implements Brigad this.getParserRegistry().registerParserSupplier(TypeToken.get(MultiplePlayerSelector.class), parserParameters -> new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>()); + if (CraftBukkitReflection.classExists("org.bukkit.NamespacedKey")) { + this.registerParserSupplierFor(NamespacedKeyArgument.class); + this.getParserRegistry().registerAnnotationMapper( + RequireExplicitNamespace.class, + (annotation, type) -> ParserParameters.single(BukkitParserParameters.REQUIRE_EXPLICIT_NAMESPACE, true) + ); + this.getParserRegistry().registerAnnotationMapper( + DefaultNamespace.class, + (annotation, type) -> ParserParameters.single(BukkitParserParameters.DEFAULT_NAMESPACE, annotation.value()) + ); + } + /* Register MC 1.13+ parsers */ if (this.hasCapability(CloudBukkitCapabilities.BRIGADIER)) { this.registerParserSupplierFor(ItemStackPredicateArgument.class); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitParserParameters.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitParserParameters.java new file mode 100644 index 00000000..1651fea1 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitParserParameters.java @@ -0,0 +1,65 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit; + +import cloud.commandframework.arguments.parser.ParserParameter; +import io.leangen.geantyref.TypeToken; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * {@link ParserParameter} keys for cloud-bukkit. + * + * @since 1.7.0 + */ +public final class BukkitParserParameters { + + private BukkitParserParameters() { + } + + /** + * Sets to require explicit namespaces for {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument} + * (i.e. 'test' will be rejected but 'test:test' will pass). + * + * @since 1.7.0 + */ + public static final ParserParameter REQUIRE_EXPLICIT_NAMESPACE = + create("require_explicit_namespace", TypeToken.get(Boolean.class)); + + /** + * Sets a custom default namespace for {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument}. + * Without this annotation the default is {@link org.bukkit.NamespacedKey#MINECRAFT}. + * + * @since 1.7.0 + */ + public static final ParserParameter DEFAULT_NAMESPACE = + create("default_namespace", TypeToken.get(String.class)); + + private static @NonNull ParserParameter create( + final @NonNull String key, + final @NonNull TypeToken expectedType + ) { + return new ParserParameter<>(key, expectedType); + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/package-info.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/package-info.java new file mode 100644 index 00000000..db1c4886 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/package-info.java @@ -0,0 +1,6 @@ +/** + * Annotations for cloud-bukkit. + * + * @since 1.7.0 + */ +package cloud.commandframework.bukkit.annotation; diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/DefaultNamespace.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/DefaultNamespace.java new file mode 100644 index 00000000..e8ee673d --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/DefaultNamespace.java @@ -0,0 +1,49 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.annotation.specifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Annotation used to set a custom namespace for {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument}. + * Without this annotation, the default namespace is {@link org.bukkit.NamespacedKey#MINECRAFT}. + * + * @since 1.7.0 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface DefaultNamespace { + + /** + * Default namespace string. + * + * @return default namespace + * @since 1.7.0 + */ + @NonNull String value(); +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/RequireExplicitNamespace.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/RequireExplicitNamespace.java new file mode 100644 index 00000000..ff6a3c24 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/RequireExplicitNamespace.java @@ -0,0 +1,41 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.annotation.specifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to set {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument} to + * require explicit namespaces. + * + * @since 1.7.0 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequireExplicitNamespace { + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/package-info.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/package-info.java new file mode 100644 index 00000000..ff21a25d --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/annotation/specifier/package-info.java @@ -0,0 +1,6 @@ +/** + * Specifier annotations for cloud-bukkit. + * + * @since 1.7.0 + */ +package cloud.commandframework.bukkit.annotation.specifier; diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/NamespacedKeyArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/NamespacedKeyArgument.java new file mode 100644 index 00000000..d8897807 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/NamespacedKeyArgument.java @@ -0,0 +1,370 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.argument; + +import cloud.commandframework.ArgumentDescription; +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCaptionKeys; +import cloud.commandframework.bukkit.BukkitCommandManager; +import cloud.commandframework.bukkit.BukkitParserParameters; +import cloud.commandframework.captions.Caption; +import cloud.commandframework.captions.CaptionVariable; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.exceptions.parsing.ParserException; +import io.leangen.geantyref.TypeToken; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.function.BiFunction; +import org.bukkit.NamespacedKey; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * cloud argument type that parses {@link NamespacedKey}s + * + * @param sender type + * @since 1.7.0 + */ +public final class NamespacedKeyArgument extends CommandArgument { + + private NamespacedKeyArgument( + final boolean required, + final @NonNull String name, + final @NonNull String defaultValue, + final @Nullable BiFunction<@NonNull CommandContext, @NonNull String, + @NonNull List<@NonNull String>> suggestionsProvider, + final @NonNull ArgumentDescription defaultDescription, + final boolean requireExplicitNamespace, + final String defaultNamespace + ) { + super( + required, + name, + new Parser<>(requireExplicitNamespace, defaultNamespace), + defaultValue, + NamespacedKey.class, + suggestionsProvider, + defaultDescription + ); + } + + /** + * Create a new {@link Builder}. + * + * @param name argument name + * @param sender type + * @return builder instance + * @since 1.7.0 + */ + public static @NonNull Builder builder(final @NonNull String name) { + return new NamespacedKeyArgument.Builder<>(name); + } + + /** + * Create a new required {@link NamespacedKeyArgument}. + * + * @param name argument name + * @param sender type + * @return argument instance + * @since 1.7.0 + */ + public static @NonNull NamespacedKeyArgument of(final @NonNull String name) { + return NamespacedKeyArgument.builder(name).asRequired().build(); + } + + /** + * Create a new optional {@link NamespacedKeyArgument}. + * + * @param name argument name + * @param sender type + * @return argument instance + * @since 1.7.0 + */ + public static @NonNull NamespacedKeyArgument optional(final @NonNull String name) { + return NamespacedKeyArgument.builder(name).asOptional().build(); + } + + /** + * Create a new optional {@link NamespacedKeyArgument} using the provided default value. + * + * @param name argument name + * @param defulatValue default name + * @param sender type + * @return argument instance + * @since 1.7.0 + */ + public static @NonNull NamespacedKeyArgument optional( + final @NonNull String name, + final @NonNull NamespacedKey defulatValue + ) { + return NamespacedKeyArgument.builder(name).asOptionalWithDefault(defulatValue).build(); + } + + + /** + * Builder for {@link NamespacedKeyArgument}. + * + * @param sender type + * @since 1.7.0 + */ + public static final class Builder extends CommandArgument.TypedBuilder> { + + private boolean requireExplicitNamespace = false; + private String defaultNamespace = NamespacedKey.MINECRAFT; + + private Builder(final @NonNull String name) { + super(NamespacedKey.class, name); + } + + /** + * Set to require explicit namespaces (i.e. 'test' will be rejected but 'test:test' will pass). + * + * @return this builder + * @since 1.7.0 + */ + public Builder requireExplicitNamespace() { + return this.requireExplicitNamespace(true); + } + + /** + * Sets whether to require explicit namespaces (i.e. 'test' will be rejected but 'test:test' will pass). + * + * @param requireExplicitNamespace whether to require explicit namespaces + * @return this builder + * @since 1.7.0 + */ + public Builder requireExplicitNamespace(final boolean requireExplicitNamespace) { + this.requireExplicitNamespace = requireExplicitNamespace; + return this; + } + + /** + * Sets a custom default namespace. By default, it is {@link NamespacedKey#MINECRAFT}. + * + * @param defaultNamespace default namespace + * @return this builder + * @since 1.7.0 + */ + public Builder defaultNamespace(final String defaultNamespace) { + this.defaultNamespace = defaultNamespace; + return this; + } + + /** + * Sets the command argument to be optional, with the specified default value. + * + * @param defaultValue default value + * @return this builder + * @see CommandArgument.Builder#asOptionalWithDefault(String) + * @since 1.7.0 + */ + public Builder asOptionalWithDefault(final NamespacedKey defaultValue) { + return this.asOptionalWithDefault(defaultValue.getNamespace() + ':' + defaultValue.getKey()); + } + + @Override + public @NonNull NamespacedKeyArgument build() { + return new NamespacedKeyArgument<>( + this.isRequired(), + this.getName(), + this.getDefaultValue(), + this.getSuggestionsProvider(), + this.getDefaultDescription(), + this.requireExplicitNamespace, + this.defaultNamespace + ); + } + + } + + /** + * Parser for {@link NamespacedKey}. + * + * @param sender type + * @since 1.7.0 + */ + public static final class Parser implements ArgumentParser { + + private final boolean requireExplicitNamespace; + private final String defaultNamespace; + + /** + * Create a new {@link Parser}. + * + * @param requireExplicitNamespace whether to require an explicit namespace + * @param defaultNamespace default namespace + * @since 1.7.0 + */ + public Parser( + final boolean requireExplicitNamespace, + final String defaultNamespace + ) { + this.requireExplicitNamespace = requireExplicitNamespace; + this.defaultNamespace = defaultNamespace; + } + + @SuppressWarnings("deprecation") + @Override + public @NonNull ArgumentParseResult parse( + final @NonNull CommandContext commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + final String input = inputQueue.peek(); + final String[] split = input.split(":"); + final int maxSemi = split.length > 1 ? 1 : 0; + if (input.length() - input.replace(":", "").length() > maxSemi) { + // Wrong number of ':' + return ArgumentParseResult.failure(new NamespacedKeyParseException( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY, input, commandContext + )); + } + try { + final NamespacedKey ret; + if (split.length == 1) { + if (this.requireExplicitNamespace) { + // Did not provide explicit namespace when option was enabled + return ArgumentParseResult.failure(new NamespacedKeyParseException( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NEED_NAMESPACE, input, commandContext + )); + } + ret = new NamespacedKey(this.defaultNamespace, split[0]); + } else if (split.length == 2) { + ret = new NamespacedKey(split[0], split[1]); + } else { + // Too many parts, ie not:valid:input + return ArgumentParseResult.failure(new NamespacedKeyParseException( + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY, input, commandContext + )); + } + + // Success! + inputQueue.remove(); + return ArgumentParseResult.success(ret); + } catch (final IllegalArgumentException ex) { + final Caption caption = ex.getMessage().contains("namespace") // stupid but works + ? BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_NAMESPACE + // this will also get used if someone puts >256 chars + : BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_NAMESPACED_KEY_KEY; + return ArgumentParseResult.failure(new NamespacedKeyParseException(caption, input, commandContext)); + } + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + final List ret = new ArrayList<>(); + ret.add(this.defaultNamespace + ":"); + if (!input.contains(":") && !input.isEmpty()) { + ret.add(input + ":"); + } + return ret; + } + + } + + + /** + * Exception used when {@link Parser} fails. + * + * @since 1.7.0 + */ + public static final class NamespacedKeyParseException extends ParserException { + + private static final long serialVersionUID = -482592639358941441L; + private final String input; + private final Caption caption; + + /** + * Creates a new {@link NamespacedKeyParseException}. + * + * @param caption caption + * @param input input + * @param context command context + * @since 1.7.0 + */ + public NamespacedKeyParseException( + final @NonNull Caption caption, + final @NonNull String input, + final @NonNull CommandContext context + ) { + super( + Parser.class, + context, + caption, + CaptionVariable.of("input", input) + ); + this.caption = caption; + this.input = input; + } + + /** + * Get the input + * + * @return Input + */ + public @NonNull String getInput() { + return this.input; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || this.getClass() != o.getClass()) { + return false; + } + final NamespacedKeyParseException that = (NamespacedKeyParseException) o; + return this.input.equals(that.input) && this.caption.equals(that.caption); + } + + @Override + public int hashCode() { + return Objects.hash(this.input, this.caption); + } + + } + + /** + * Called reflectively by {@link BukkitCommandManager}. + * + * @param commandManager command manager + * @param sender type + */ + @SuppressWarnings("unused") + private static void registerParserSupplier(final @NonNull BukkitCommandManager commandManager) { + commandManager.getParserRegistry() + .registerParserSupplier(TypeToken.get(NamespacedKey.class), params -> new Parser<>( + params.has(BukkitParserParameters.REQUIRE_EXPLICIT_NAMESPACE), + params.get(BukkitParserParameters.DEFAULT_NAMESPACE, NamespacedKey.MINECRAFT) + )); + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/package-info.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/package-info.java new file mode 100644 index 00000000..0892a9ea --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/argument/package-info.java @@ -0,0 +1,6 @@ +/** + * Command argument types for the Bukkit platform. + * + * @since 1.7.0 + */ +package cloud.commandframework.bukkit.argument; diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java index ffe8bd86..1c65115b 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java @@ -44,6 +44,7 @@ import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArrayArgument; import cloud.commandframework.bukkit.BukkitCommandManager; import cloud.commandframework.bukkit.CloudBukkitCapabilities; +import cloud.commandframework.bukkit.argument.NamespacedKeyArgument; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.data.ProtoItemStack; import cloud.commandframework.bukkit.parsers.EnchantmentArgument; @@ -89,6 +90,7 @@ import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.enchantments.Enchantment; @@ -393,6 +395,8 @@ public final class ExamplePlugin extends JavaPlugin { new Mc113(this.manager).registerCommands(); } + this.registerNamespacedKeyUsingCommand(); + // // Create a Bukkit-like command // @@ -457,6 +461,28 @@ public final class ExamplePlugin extends JavaPlugin { })); } + private void registerNamespacedKeyUsingCommand() { + boolean nsk = true; + try { + Class.forName("org.bukkit.NamespacedKey"); + } catch (final ClassNotFoundException e) { + nsk = false; + } + + if (!nsk) { + return; + } + + this.manager.command(this.manager.commandBuilder("example") + .literal("namespacedkey") + .argument(NamespacedKeyArgument.of("key")) + .handler(ctx -> { + final NamespacedKey namespacedKey = ctx.get("key"); + final String key = namespacedKey.getNamespace() + ":" + namespacedKey.getKey(); + ctx.getSender().sendMessage("The key you typed is '" + key + "'."); + })); + } + @CommandMethod("example|e|ex help [query]") @CommandDescription("Help menu") public void commandHelp(