diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java new file mode 100644 index 00000000..3cd71547 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/compound/FlagArgument.java @@ -0,0 +1,56 @@ +// +// 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.compound; + +import cloud.commandframework.types.tuples.DynamicTuple; +import cloud.commandframework.types.tuples.Tuple; +import io.leangen.geantyref.TypeToken; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.function.Function; + +/** + * Container for flag parsing logic. This should not be be used directly. + * Internally, a flag argument is a special case of a {@link CompoundArgument}. + * + * @param Command sender type + */ +public class FlagArgument extends CompoundArgument { + + FlagArgument(final @NonNull Tuple names, + final @NonNull Tuple parserTuple, + final @NonNull Tuple types, + final @NonNull Function<@NonNull DynamicTuple, @NonNull DynamicTuple> mapper, + final @NonNull TypeToken valueType) { + super(false, + "flags", + names, + parserTuple, + types, + mapper, + DynamicTuple::of, + valueType); + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java new file mode 100644 index 00000000..ea934f90 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/CommandFlag.java @@ -0,0 +1,175 @@ +// +// 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.flags; + +import cloud.commandframework.Description; +import cloud.commandframework.arguments.CommandArgument; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A flag is an optional command argument that may have an associated parser, + * and is identified by its name. Essentially, it's a mixture of a command literal + * and an optional variable command argument. + * + * @param Command argument type. {@link Void} is used when no argument is present. + */ +@SuppressWarnings("unused") +public final class CommandFlag { + + private final @NonNull String name; + private final @NonNull String @NonNull [] aliases; + private final @NonNull Description description; + + private final @Nullable CommandArgument commandArgument; + + private CommandFlag(@NonNull final String name, + @NonNull final String @NonNull [] aliases, + @NonNull final Description description, + @Nullable final CommandArgument commandArgument) { + this.name = name; + this.aliases = aliases; + this.description = description; + this.commandArgument = commandArgument; + } + + /** + * Create a new flag builder + * + * @param name Flag name + * @return Flag builder + */ + public static @NonNull Builder newBuilder(@NonNull final String name) { + return new Builder<>(name); + } + + /** + * Get the flag name + * + * @return Flag name + */ + public @NonNull String getName() { + return this.name; + } + + /** + * Get all flag aliases. This does not include the flag name + * + * @return Flag aliases + */ + public @NonNull Collection<@NonNull String> getAliases() { + return Arrays.asList(this.aliases); + } + + /** + * Get the flag description + *

+ * Flag description + */ + public @NonNull Description getDescription() { + return this.description; + } + + /** + * Get the command argument, if it exists + * + * @return Command argument, or {@code null} + */ + public @Nullable CommandArgument getCommandArgument() { + return this.commandArgument; + } + + @Override + public String toString() { + return String.format("--%s", this.name); + } + + + public static final class Builder { + + private final String name; + private final String[] aliases; + private final Description description; + private final CommandArgument commandArgument; + + private Builder(@NonNull final String name, + @NonNull final String[] aliases, + @NonNull final Description description, + @Nullable final CommandArgument commandArgument) { + this.name = name; + this.aliases = aliases; + this.description = description; + this.commandArgument = commandArgument; + } + + private Builder(@NonNull final String name) { + this(name, new String[0], Description.empty(), null); + } + + /** + * Create a new builder instance using the given flag aliases + * + * @param aliases Flag aliases + * @return New builder instance + */ + public Builder withAliases(@NonNull final String... aliases) { + return new Builder<>(this.name, aliases, this.description, this.commandArgument); + } + + /** + * Create a new builder instance using the given flag description + * + * @param description Flag description + * @return New builder instance + */ + public Builder withDescription(@NonNull final Description description) { + return new Builder<>(this.name, this.aliases, description, this.commandArgument); + } + + /** + * Create a new builder instance using the given command argument + * + * @param argument Command argument + * @param New argument type + * @return New builder instance + */ + public Builder withArgument(@NonNull final CommandArgument argument) { + return new Builder<>(this.name, this.aliases, this.description, argument); + } + + /** + * Build a new command flag instance + * + * @return Constructed instance + */ + public @NonNull CommandFlag build() { + return new CommandFlag<>(this.name, this.aliases, this.description, this.commandArgument); + } + + } + +} 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 new file mode 100644 index 00000000..964b4adc --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/flags/FlagContext.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.flags; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Flag value mappings + */ +public class FlagContext { + + /** + * Dummy object stored as a flag value when the flag has no associated parser + */ + public static final Object FLAG_PRESENCE_VALUE = new Object(); + + private final Map flagValues; + + private FlagContext() { + this.flagValues = new HashMap<>(); + } + + /** + * Create a new flag context instance + * + * @return Constructed instance + */ + public static @NonNull FlagContext create() { + return new FlagContext(); + } + + /** + * Indicate that a presence flag was supplied + * + * @param flag Flag instance + */ + public void addPresenceFlag(@NonNull final CommandFlag flag) { + this.flagValues.put(flag.getName(), FLAG_PRESENCE_VALUE); + } + + /** + * Store a value associated with a value flag + * + * @param flag Value flag + * @param value Flag value + * @param Value type + */ + public void addValueFlag(@NonNull final CommandFlag flag, @NonNull final T value) { + this.flagValues.put(flag.getName(), value); + } + + /** + * Check whether or not a 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, + * else {@code false} + */ + public boolean isPresent(@NonNull final String flag) { + final Object value = this.flagValues.get(flag); + return FLAG_PRESENCE_VALUE.equals(value); + } + + /** + * Get a flag value + * + * @param name Flag name + * @param defaultValue Default value + * @param Value type + * @return Stored value, or the supplied default value + */ + public T getValue(@NonNull final String name, @NonNull final T defaultValue) { + final Object value = this.flagValues.get(name); + if (value == null) { + return defaultValue; + } + @SuppressWarnings("unchecked") final T casted = (T) value; + return casted; + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java b/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java index e92798d9..750b1dca 100644 --- a/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java +++ b/cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java @@ -24,6 +24,7 @@ package cloud.commandframework.context; import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.flags.FlagContext; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collections; @@ -39,6 +40,7 @@ import java.util.Optional; public final class CommandContext { private final Map, ArgumentTiming> argumentTimings = new HashMap<>(); + private final FlagContext flagContext = FlagContext.create(); private final Map internalStorage = new HashMap<>(); private final C commandSender; private final boolean suggestions; @@ -164,6 +166,15 @@ public final class CommandContext { return Collections.unmodifiableMap(this.argumentTimings); } + /** + * Get the associated {@link FlagContext} instance + * + * @return Flag context + */ + public @NonNull FlagContext flags() { + return this.flagContext; + } + /** * Used to track performance metrics related to command parsing. This is attached diff --git a/cloud-core/src/main/java/cloud/commandframework/types/tuples/DynamicTuple.java b/cloud-core/src/main/java/cloud/commandframework/types/tuples/DynamicTuple.java new file mode 100644 index 00000000..7e0a69d8 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/types/tuples/DynamicTuple.java @@ -0,0 +1,61 @@ +// +// 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.types.tuples; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Dynamic sized tuple backed by a {@code Object[]} + */ +public final class DynamicTuple implements Tuple { + + private final Object[] internalArray; + + private DynamicTuple(@NonNull final Object @NonNull [] internalArray) { + this.internalArray = internalArray; + } + + /** + * Create a new dynamic tuple, containing the given elements + * + * @param elements Elements that should be contained in the tuple + * @return Created tuple, preserving the order of the given elements + */ + public static @NonNull DynamicTuple of(@NonNull final Object... elements) { + return new DynamicTuple(elements); + } + + @Override + public final int getSize() { + return this.internalArray.length; + } + + @Override + public @NonNull Object @NonNull [] toArray() { + final @NonNull Object @NonNull [] newArray = new Object[this.internalArray.length]; + System.arraycopy(this.internalArray, 0, newArray, 0, this.internalArray.length); + return newArray; + } + +}