From 6b690811f1f8b0fd3fac7646d2b30c2aabdae01e Mon Sep 17 00:00:00 2001 From: Zach Levis Date: Sun, 3 Jan 2021 18:12:32 -0800 Subject: [PATCH] core: Add a way to map the output of argument parsers --- .../arguments/parser/ArgumentParseResult.java | 62 +++++++++- .../arguments/parser/ArgumentParser.java | 14 +++ .../parser/MappedArgumentParser.java | 106 ++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParseResult.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParseResult.java index 4c88ba5f..66df30ba 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParseResult.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParseResult.java @@ -26,6 +26,7 @@ package cloud.commandframework.arguments.parser; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Optional; +import java.util.function.Function; /** * Result of the parsing done by a {@link ArgumentParser} @@ -66,6 +67,26 @@ public abstract class ArgumentParseResult { */ public abstract @NonNull Optional getParsedValue(); + /** + * If this result is successful, transform the output value. + * + * @param mapper the transformation + * @param the result type + * @return a new result if successful, otherwise a failure + * @since 1.4.0 + */ + public abstract @NonNull ArgumentParseResult mapParsedValue(Function mapper); + + /** + * If this result is successful, transform the output value, returning another parse result. + * + * @param mapper the transformation + * @param the result type + * @return a new result if successful, otherwise a failure + * @since 1.4.0 + */ + public abstract @NonNull ArgumentParseResult flatMapParsedValue(Function> mapper); + /** * Get the failure reason, if it exists * @@ -73,6 +94,15 @@ public abstract class ArgumentParseResult { */ public abstract @NonNull Optional getFailure(); + /** + * If this result is a failure, transform the exception. + * + * @param mapper the exception transformation + * @return if this is a failure, a transformed result, otherwise this + * @since 1.4.0 + */ + public abstract @NonNull ArgumentParseResult mapFailure(Function mapper); + private static final class ParseSuccess extends ArgumentParseResult { @@ -90,13 +120,27 @@ public abstract class ArgumentParseResult { return Optional.of(this.value); } + @Override + public @NonNull ArgumentParseResult mapParsedValue(final Function mapper) { + return new ParseSuccess<>(mapper.apply(this.value)); + } + + @Override + public @NonNull ArgumentParseResult flatMapParsedValue(final Function> mapper) { + return mapper.apply(this.value); + } + @Override public @NonNull Optional getFailure() { return Optional.empty(); } - } + @Override + public @NonNull ArgumentParseResult mapFailure(final Function mapper) { + return this; + } + } private static final class ParseFailure extends ArgumentParseResult { @@ -114,11 +158,27 @@ public abstract class ArgumentParseResult { return Optional.empty(); } + @Override + @SuppressWarnings("unchecked") + public @NonNull ArgumentParseResult mapParsedValue(final Function mapper) { + return (ArgumentParseResult) this; + } + + @Override + @SuppressWarnings("unchecked") + public @NonNull ArgumentParseResult flatMapParsedValue(final Function> mapper) { + return (ArgumentParseResult) this; + } + @Override public @NonNull Optional getFailure() { return Optional.of(this.failure); } + @Override + public @NonNull ArgumentParseResult mapFailure(final Function mapper) { + return new ParseFailure<>(mapper.apply(this.failure)); + } } } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java index 645294b7..75bcc1c1 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/ArgumentParser.java @@ -29,6 +29,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collections; import java.util.List; import java.util.Queue; +import java.util.function.BiFunction; + +import static java.util.Objects.requireNonNull; /** * Parser that parses strings into values of a specific type @@ -89,6 +92,17 @@ public interface ArgumentParser { return Collections.emptyList(); } + /** + * Create a derived argument parser preserving all properties of this parser, but converting the output type. + * + * @param mapper the mapper to apply + * @param the result type + * @return a derived parser. + */ + default @NonNull ArgumentParser map(final BiFunction, T, ArgumentParseResult> mapper) { + return new MappedArgumentParser<>(this, requireNonNull(mapper, "mapper")); + } + /** * Check whether or not this argument parser is context free. A context free * parser will not use the provided command context, and so supports impromptu parsing diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java new file mode 100644 index 00000000..ccca7b6a --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/MappedArgumentParser.java @@ -0,0 +1,106 @@ +// +// MIT License +// +// Copyright (c) 2020 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.arguments.parser; + +import cloud.commandframework.context.CommandContext; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Queue; +import java.util.function.BiFunction; + +final class MappedArgumentParser implements ArgumentParser { + private final ArgumentParser base; + private final BiFunction, I, ArgumentParseResult> mapper; + + MappedArgumentParser( + final ArgumentParser base, + final BiFunction, I, ArgumentParseResult> mapper + ) { + this.base = base; + this.mapper = mapper; + } + + @Override + public @NonNull ArgumentParseResult<@NonNull O> parse( + @NonNull final CommandContext<@NonNull C> commandContext, + @NonNull final Queue<@NonNull String> inputQueue + ) { + final ArgumentParseResult<@NonNull I> baseResult = this.base.parse(commandContext, inputQueue); + return baseResult.flatMapParsedValue(value -> this.mapper.apply(commandContext, value)); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + return this.base.suggestions(commandContext, input); + } + + @Override + public @NonNull ArgumentParser map(final BiFunction, O, ArgumentParseResult> mapper) { + return new MappedArgumentParser<>( + this.base, + (ctx, original) -> this.mapper.apply(ctx, original).flatMapParsedValue(value -> mapper.apply(ctx, value)) + ); + } + + @Override + public boolean isContextFree() { + return this.base.isContextFree(); + } + + @Override + public int getRequestedArgumentCount() { + return this.base.getRequestedArgumentCount(); + } + + @Override + public int hashCode() { + return 31 + this.base.hashCode() + + 7 * this.mapper.hashCode(); + } + + @Override + public boolean equals(final @Nullable Object other) { + if (!(other instanceof MappedArgumentParser)) { + return false; + } + + final MappedArgumentParser that = (MappedArgumentParser) other; + return this.base.equals(that.base) + && this.mapper.equals(that.mapper); + } + + @Override + public String toString() { + return "MappedArgumentParser{" + + "base=" + this.base + ',' + + "mapper=" + this.mapper + '}'; + } + +}