From 2274144e09a6d74269b31576ae404332615d249e Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:00:18 -0700 Subject: [PATCH] Add basic RegistryEntryParser --- .../paper/parser/RegistryEntryParser.java | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 cloud-paper/src/main/java/org/incendo/cloud/paper/parser/RegistryEntryParser.java diff --git a/cloud-paper/src/main/java/org/incendo/cloud/paper/parser/RegistryEntryParser.java b/cloud-paper/src/main/java/org/incendo/cloud/paper/parser/RegistryEntryParser.java new file mode 100644 index 00000000..ef4b420a --- /dev/null +++ b/cloud-paper/src/main/java/org/incendo/cloud/paper/parser/RegistryEntryParser.java @@ -0,0 +1,190 @@ +// +// MIT License +// +// Copyright (c) 2024 Incendo +// +// 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 org.incendo.cloud.paper.parser; + +import io.leangen.geantyref.TypeFactory; +import io.leangen.geantyref.TypeToken; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apiguardian.api.API; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.immutables.value.Value; +import org.incendo.cloud.bukkit.BukkitCaptionKeys; +import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; +import org.incendo.cloud.caption.CaptionVariable; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.exception.parsing.ParserException; +import org.incendo.cloud.internal.ImmutableImpl; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.parser.MappedArgumentParser; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +@API(status = API.Status.EXPERIMENTAL) +public final class RegistryEntryParser + implements ArgumentParser>, SuggestionProvider, + MappedArgumentParser> { + + /** + * Creates a {@link RegistryEntryParser}. + * + * @param registryKey registry key + * @param elementType registry element type + * @param command sender type + * @param registry element type + * @return the created parser + * @since 2.0.0 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + @API(status = API.Status.STABLE, since = "2.0.0") + public static @NonNull ParserDescriptor> registryEntryParser( + final RegistryKey registryKey, + final TypeToken elementType + ) { + return ParserDescriptor.of( + new RegistryEntryParser<>(registryKey), + (TypeToken) TypeToken.get(TypeFactory.parameterizedClass(RegistryEntry.class, elementType.getType())) + ); + } + + private final ParserDescriptor keyParser; + private final RegistryKey registryKey; + + /** + * Create a new {@link RegistryEntryParser}. + * + * @param registryKey registry key + */ + public RegistryEntryParser(final RegistryKey registryKey) { + this.keyParser = NamespacedKeyParser.namespacedKeyParser(); + this.registryKey = registryKey; + } + + @Override + public @NonNull ArgumentParseResult> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull CommandInput commandInput + ) { + return this.keyParser.parser().parse(commandContext, commandInput).flatMapSuccess(key -> { + final Registry registry = RegistryAccess.registryAccess().getRegistry(this.registryKey); + + final E value = registry.get(key); + if (value == null) { + return ArgumentParseResult.failure(new ParseException(key.asString(), commandContext)); + } + + return ArgumentParseResult.success(RegistryEntryImpl.of(value, key)); + }); + } + + @Override + public @NonNull ArgumentParser baseParser() { + return this.keyParser.parser(); + } + + /** + * Exception when there is no registry entry for the provided key. + */ + public static final class ParseException extends ParserException { + private final String input; + + /** + * Creates a new {@link ParseException}. + * + * @param input input string + * @param context command context + */ + public ParseException( + final @NonNull String input, + final @NonNull CommandContext context + ) { + super( + RegistryEntryParser.class, + context, + BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_WORLD, + CaptionVariable.of("input", input) + ); + this.input = input; + } + + /** + * Get the input provided by the sender + * + * @return Input + */ + public @NonNull String input() { + return this.input; + } + } + + @Override + public @NonNull CompletableFuture> suggestionsFuture( + final @NonNull CommandContext commandContext, + final @NonNull CommandInput input + ) { + final List completions = new ArrayList<>(); + final Registry registry = RegistryAccess.registryAccess().getRegistry(this.registryKey); + registry.stream() + .map(registry::getKeyOrThrow) + .forEach(key -> { + if (input.hasRemainingInput() && key.getNamespace().equals(NamespacedKey.MINECRAFT_NAMESPACE)) { + completions.add(Suggestion.suggestion(key.getKey())); + } + completions.add(Suggestion.suggestion(key.getNamespace() + ':' + key.getKey())); + }); + return CompletableFuture.completedFuture(completions); + } + + /** + * Holds a registry value and it's key. + * + * @param value type + */ + @ImmutableImpl + @Value.Immutable + public interface RegistryEntry { + /** + * Returns the value. + * + * @return the value + */ + E value(); + + /** + * Returns the key. + * + * @return the key + */ + NamespacedKey key(); + } +}