diff --git a/CHANGELOG.md b/CHANGELOG.md index 107c43c6..9612c24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `@Suggestions` annotated methods + - Type safe meta system ### Changed - Moved the parser injector registry into CommandManager and added injection to CommandContext +### Deprecated + - String keyed command meta + ## [1.2.0] - 2020-12-07 ### Added diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java index e07a9ca3..071660cf 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -307,7 +307,7 @@ public final class AnnotationParser { final SimpleCommandMeta.Builder metaBuilder = SimpleCommandMeta.builder() .with(this.metaFactory.apply(method)); if (methodOrClassHasAnnotation(method, Confirmation.class)) { - metaBuilder.with(CommandConfirmationManager.CONFIRMATION_REQUIRED_META, "true"); + metaBuilder.with(CommandConfirmationManager.META_CONFIRMATION_REQUIRED, true); } @SuppressWarnings("rawtypes") diff --git a/cloud-core/src/main/java/cloud/commandframework/Command.java b/cloud-core/src/main/java/cloud/commandframework/Command.java index 2fbd1064..f81a87ac 100644 --- a/cloud-core/src/main/java/cloud/commandframework/Command.java +++ b/cloud-core/src/main/java/cloud/commandframework/Command.java @@ -269,7 +269,7 @@ public class Command { * @return {@code true} if the command is hidden, {@code false} if not */ public boolean isHidden() { - return this.getCommandMeta().getOrDefault("hidden", "true").equals("true"); + return this.getCommandMeta().getOrDefault(CommandMeta.HIDDEN, false); } @@ -337,7 +337,9 @@ public class Command { * @param key Meta key * @param value Meta value * @return New builder instance using the inserted meta key-value pair + * @deprecated for removal since 1.2.0, use the typesafe variant at {@link #meta(CommandMeta.Key, Object)} instead. */ + @Deprecated public @NonNull Builder meta(final @NonNull String key, final @NonNull String value) { final CommandMeta commandMeta = SimpleCommandMeta.builder().with(this.commandMeta).with(key, value).build(); return new Builder<>( @@ -351,6 +353,28 @@ public class Command { ); } + /** + * Add command meta to the internal command meta map + * + * @param Meta value type + * @param key Meta key + * @param value Meta value + * @return New builder instance using the inserted meta key-value pair + * @since 1.3.0 + */ + public @NonNull Builder meta(final CommandMeta.@NonNull Key key, final @NonNull V value) { + final CommandMeta commandMeta = SimpleCommandMeta.builder().with(this.commandMeta).with(key, value).build(); + return new Builder<>( + this.commandManager, + commandMeta, + this.senderType, + this.commandArguments, + this.commandExecutionHandler, + this.commandPermission, + this.flags + ); + } + /** * Supply a command manager instance to the builder. This will be used when attempting to * retrieve command argument parsers, in the case that they're needed. This @@ -746,7 +770,7 @@ public class Command { * @return New builder instance that indicates that the constructed command should be hidden */ public @NonNull Builder hidden() { - return this.meta("hidden", "true"); + return this.meta(CommandMeta.HIDDEN, true); } /** diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandHelpHandler.java b/cloud-core/src/main/java/cloud/commandframework/CommandHelpHandler.java index 9530c629..55d29c42 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandHelpHandler.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandHelpHandler.java @@ -25,6 +25,7 @@ package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.StaticArgument; +import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -55,7 +56,7 @@ public final class CommandHelpHandler { final List> syntaxHints = new ArrayList<>(); for (final Command command : this.commandManager.getCommands()) { final List> arguments = command.getArguments(); - final String description = command.getCommandMeta().getOrDefault("description", ""); + final String description = command.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, ""); syntaxHints.add(new VerboseHelpEntry<>( command, this.commandManager.getCommandSyntaxFormatter() @@ -163,7 +164,7 @@ public final class CommandHelpHandler { final List> syntaxHints = new ArrayList<>(); for (final Command command : availableCommands) { final List> arguments = command.getArguments(); - final String description = command.getCommandMeta().getOrDefault("description", ""); + final String description = command.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, ""); syntaxHints.add(new VerboseHelpEntry<>( command, this.commandManager.getCommandSyntaxFormatter() @@ -350,8 +351,8 @@ public final class CommandHelpHandler { private VerboseHelpTopic(final @NonNull Command command) { this.command = command; - final String shortDescription = command.getCommandMeta().getOrDefault("description", "No description"); - this.description = command.getCommandMeta().getOrDefault("long-description", shortDescription); + final String shortDescription = command.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, "No description"); + this.description = command.getCommandMeta().getOrDefault(CommandMeta.LONG_DESCRIPTION, shortDescription); } /** diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java index 02eeeac2..b8c23077 100644 --- a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotatedElementAccessor.java @@ -43,7 +43,7 @@ final class AnnotatedElementAccessor implements AnnotationAccessor { @Override public @Nullable A annotation( - @NonNull final Class clazz + final @NonNull Class clazz ) { try { return element.getAnnotation(clazz); diff --git a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java index 86381574..091b137d 100644 --- a/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java +++ b/cloud-core/src/main/java/cloud/commandframework/annotations/AnnotationAccessor.java @@ -87,7 +87,7 @@ public interface AnnotationAccessor { final class NullAnnotationAccessor implements AnnotationAccessor { @Override - public @Nullable A annotation(@NonNull final Class clazz) { + public @Nullable A annotation(final @NonNull Class clazz) { return null; } diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java index bfe592bc..7b036502 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java @@ -63,8 +63,8 @@ public final class DelegatingCommandSuggestionEngine implements CommandSugges @Override public @NonNull List<@NonNull String> getSuggestions( - @NonNull final CommandContext context, - @NonNull final String input + final @NonNull CommandContext context, + final @NonNull String input ) { final @NonNull LinkedList<@NonNull String> inputQueue = new CommandInputTokenizer(input).tokenize(); /* Store a copy of the input queue in the context */ diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/StandardParserRegistry.java b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/StandardParserRegistry.java index fbbda6b9..d4f6b900 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/parser/StandardParserRegistry.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/parser/StandardParserRegistry.java @@ -232,15 +232,15 @@ public final class StandardParserRegistry implements ParserRegistry { @Override public void registerSuggestionProvider( - @NonNull final String name, - @NonNull final BiFunction<@NonNull CommandContext, @NonNull String, @NonNull List> suggestionsProvider + final @NonNull String name, + final @NonNull BiFunction<@NonNull CommandContext, @NonNull String, @NonNull List> suggestionsProvider ) { this.namedSuggestionProviders.put(name.toLowerCase(Locale.ENGLISH), suggestionsProvider); } @Override public @NonNull Optional, @NonNull String, @NonNull List>> getSuggestionProvider( - @NonNull final String name + final @NonNull String name ) { final BiFunction<@NonNull CommandContext, @NonNull String, @NonNull List> suggestionProvider = this.namedSuggestionProviders.get(name.toLowerCase(Locale.ENGLISH)); diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java index b8fb3de0..156d8c56 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/preprocessor/RegexPreprocessor.java @@ -87,7 +87,7 @@ public final class RegexPreprocessor implements BiFunction<@NonNull CommandCo @Override public @NonNull ArgumentParseResult apply( - @NonNull final CommandContext context, @NonNull final Queue<@NonNull String> strings + final @NonNull CommandContext context, final @NonNull Queue<@NonNull String> strings ) { final String head = strings.peek(); if (head == null) { diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumArgument.java index e37f9417..01e6555c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/EnumArgument.java @@ -70,7 +70,7 @@ public class EnumArgument> extends CommandArgument { * @return Created builder */ public static > EnumArgument.@NonNull Builder newBuilder( - @NonNull final Class enumClass, + final @NonNull Class enumClass, final @NonNull String name ) { return new EnumArgument.Builder<>(name, enumClass); diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/StringArrayArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/StringArrayArgument.java index 141c7b50..ee769df1 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/StringArrayArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/StringArrayArgument.java @@ -107,8 +107,8 @@ public final class StringArrayArgument extends CommandArgument { @Override public @NonNull ArgumentParseResult parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String[] result = new String[inputQueue.size()]; for (int i = 0; i < result.length; i++) { diff --git a/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java b/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java index a7bed067..bdb21547 100644 --- a/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java +++ b/cloud-core/src/main/java/cloud/commandframework/captions/SimpleCaptionRegistry.java @@ -140,8 +140,8 @@ public class SimpleCaptionRegistry implements FactoryDelegatingCaptionRegistr @Override public final @NonNull String getCaption( - @NonNull final Caption caption, - @NonNull final C sender + final @NonNull Caption caption, + final @NonNull C sender ) { final BiFunction messageFactory = this.messageFactories.get(caption); if (messageFactory == null) { diff --git a/cloud-core/src/main/java/cloud/commandframework/extra/confirmation/CommandConfirmationManager.java b/cloud-core/src/main/java/cloud/commandframework/extra/confirmation/CommandConfirmationManager.java index ec29f21b..b47dcf8d 100644 --- a/cloud-core/src/main/java/cloud/commandframework/extra/confirmation/CommandConfirmationManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/extra/confirmation/CommandConfirmationManager.java @@ -27,6 +27,7 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.execution.CommandExecutionHandler; import cloud.commandframework.execution.postprocessor.CommandPostprocessingContext; import cloud.commandframework.execution.postprocessor.CommandPostprocessor; +import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.services.types.ConsumerService; import cloud.commandframework.types.tuples.Pair; @@ -54,8 +55,27 @@ public class CommandConfirmationManager { /** * Meta data stored for commands that require confirmation + * + * @deprecated for removal since 1.3.0. Use {@link #META_CONFIRMATION_REQUIRED} instead. */ + @Deprecated public static final String CONFIRMATION_REQUIRED_META = "__REQUIRE_CONFIRMATION__"; + + private static final CommandMeta.Key LEGACY_CONFIRMATION_META = CommandMeta.Key.of( + String.class, + CONFIRMATION_REQUIRED_META + ); + + /** + * Meta data stored for commands that require confirmation + * + * @since 1.3.0 + */ + public static final CommandMeta.Key META_CONFIRMATION_REQUIRED = CommandMeta.Key.of( + Boolean.class, + "cloud:require_confirmation", + meta -> meta.get(LEGACY_CONFIRMATION_META).map(Boolean::valueOf).orElse(null) + ); private static final int MAXIMUM_PENDING_SIZE = 100; private final Consumer> notifier; @@ -120,7 +140,7 @@ public class CommandConfirmationManager { * @return Builder instance */ public SimpleCommandMeta.@NonNull Builder decorate(final SimpleCommandMeta.@NonNull Builder builder) { - return builder.with(CONFIRMATION_REQUIRED_META, "true"); + return builder.with(META_CONFIRMATION_REQUIRED, true); } /** @@ -158,8 +178,7 @@ public class CommandConfirmationManager { public void accept(final @NonNull CommandPostprocessingContext context) { if (!context.getCommand() .getCommandMeta() - .getOrDefault(CONFIRMATION_REQUIRED_META, "false") - .equals("true")) { + .getOrDefault(META_CONFIRMATION_REQUIRED, false)) { return; } /* Add it to the "queue" */ diff --git a/cloud-core/src/main/java/cloud/commandframework/meta/CommandMeta.java b/cloud-core/src/main/java/cloud/commandframework/meta/CommandMeta.java index 5c6de6c7..59ebd99e 100644 --- a/cloud-core/src/main/java/cloud/commandframework/meta/CommandMeta.java +++ b/cloud-core/src/main/java/cloud/commandframework/meta/CommandMeta.java @@ -24,10 +24,16 @@ package cloud.commandframework.meta; import cloud.commandframework.Command; +import io.leangen.geantyref.GenericTypeReflector; +import io.leangen.geantyref.TypeToken; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Map; import java.util.Optional; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; /** * Object that is associated with a {@link Command}. @@ -37,6 +43,15 @@ import java.util.Optional; */ public abstract class CommandMeta { + private static final Key LEGACY_HIDDEN = Key.of(String.class, "hidden"); + public static final Key DESCRIPTION = Key.of(String.class, "description"); + public static final Key LONG_DESCRIPTION = Key.of(String.class, "long-description"); + public static final Key HIDDEN = Key.of( + Boolean.class, + "cloud:hidden", + meta -> Boolean.valueOf(meta.getOrDefault(LEGACY_HIDDEN, "false")) + ); + /** * Create a new simple command meta builder * @@ -56,7 +71,9 @@ public abstract class CommandMeta { * * @param key Key * @return Optional that may contain the associated value + * @deprecated for removal since 1.3.0, see typesafe variant at {@link #get(Key)} instead */ + @Deprecated public abstract @NonNull Optional getValue(@NonNull String key); /** @@ -65,14 +82,157 @@ public abstract class CommandMeta { * @param key Key * @param defaultValue Default value * @return Value, or default value + * @deprecated for removal since 1.3.0, see typesafe variant at {@link #getOrDefault(Key, Object)} instead */ + @Deprecated public abstract @NonNull String getOrDefault(@NonNull String key, @NonNull String defaultValue); + /** + * Get the value associated with a key. + * + * @param Value type + * @param key Key + * @return Optional that may contain the associated value + * @since 1.3.0 + */ + public abstract @NonNull Optional get(@NonNull Key key); + + /** + * Get the value if it exists, else return the default value. + * + * @param Value type + * @param key Key + * @param defaultValue Default value + * @return Value, or default value + * @since 1.3.0 + */ + public abstract @NonNull V getOrDefault(@NonNull Key key, @NonNull V defaultValue); + /** * Get a copy of the meta map * * @return Copy of meta map + * @deprecated for removal since 1.3.0, use {@link #getAllValues()} instead. */ + @Deprecated public abstract @NonNull Map<@NonNull String, @NonNull String> getAll(); + /** + * Get a copy of the meta map, without type information. + * + * @return Copy of meta map + * @since 1.3.0 + */ + public abstract @NonNull Map<@NonNull String, @NonNull ?> getAllValues(); + + /** + * A key into the metadata map. + * + * @param value type + * @since 1.3.0 + */ + public interface Key { + + /** + * Create a new metadata key. + * + * @param type the value type + * @param key the name for the key + * @param the value type + * @return a new key + */ + static @NonNull Key of(final @NonNull Class type, final @NonNull String key) { + if (GenericTypeReflector.isMissingTypeParameters(type)) { + throw new IllegalArgumentException("Raw type " + type + " is prohibited"); + } + + return new SimpleKey<>( + TypeToken.get(requireNonNull(type, "type")), + requireNonNull(key, "key"), + null + ); + } + + /** + * Create a new metadata key. + * + * @param type the value type + * @param key the name for the key + * @param the value type + * @return a new key + */ + static @NonNull Key of(final @NonNull TypeToken type, final @NonNull String key) { + return new SimpleKey<>( + requireNonNull(type, "type"), + requireNonNull(key, "key"), + null + ); + } + + /** + * Create a new metadata key. + * + * @param type the value type + * @param key the name for the key + * @param fallbackDerivation A function that will be called if no value is present for the key + * @param the value type + * @return a new key + */ + static @NonNull Key of( + final @NonNull Class type, + final @NonNull String key, + final @NonNull Function<@NonNull CommandMeta, @Nullable T> fallbackDerivation) { + return new SimpleKey<>( + TypeToken.get(requireNonNull(type, "type")), + requireNonNull(key, "key"), + fallbackDerivation + ); + } + + /** + * Create a new metadata key. + * + * @param type the value type + * @param key the name for the key + * @param fallbackDerivation A function that will be called if no value is present for the key + * @param the value type + * @return a new key + */ + static @NonNull Key of( + final @NonNull TypeToken type, + final @NonNull String key, + final @NonNull Function<@NonNull CommandMeta, @Nullable T> fallbackDerivation + ) { + return new SimpleKey<>( + requireNonNull(type, "type"), + requireNonNull(key, "key"), + fallbackDerivation + ); + } + + /** + * Get a representation of the type of value this key holds. + * + * @return the value type + */ + @NonNull TypeToken getValueType(); + + /** + * Get the name of this key + * + * @return the key type + */ + @NonNull String getName(); + + /** + * Get a function that can be used to compute a fallback based on existing meta. + * + *

This function will only be used if no value is directly for the key.

+ * + * @return the fallback derivation + */ + @Nullable Function<@NonNull CommandMeta, @Nullable V> getFallbackDerivation(); + + } + } diff --git a/cloud-core/src/main/java/cloud/commandframework/meta/SimpleCommandMeta.java b/cloud-core/src/main/java/cloud/commandframework/meta/SimpleCommandMeta.java index 3e4414f0..d4f6758f 100644 --- a/cloud-core/src/main/java/cloud/commandframework/meta/SimpleCommandMeta.java +++ b/cloud-core/src/main/java/cloud/commandframework/meta/SimpleCommandMeta.java @@ -23,6 +23,7 @@ // package cloud.commandframework.meta; +import io.leangen.geantyref.GenericTypeReflector; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collections; @@ -30,6 +31,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; /** * A simple immutable string-string map containing command meta @@ -37,12 +39,22 @@ import java.util.Optional; @SuppressWarnings("unused") public class SimpleCommandMeta extends CommandMeta { - private final Map metaMap; + private final Map metaMap; + @Deprecated protected SimpleCommandMeta(final @NonNull Map<@NonNull String, @NonNull String> metaMap) { this.metaMap = Collections.unmodifiableMap(metaMap); } + protected SimpleCommandMeta(final SimpleCommandMeta source) { + this.metaMap = source.metaMap; + } + + // Constructor needs an extra flag to distinguish it from the old one (for reified generics) + SimpleCommandMeta(final @NonNull Map<@NonNull String, @NonNull Object> metaMap, final boolean unusedMarkerForNew) { + this.metaMap = Collections.unmodifiableMap(metaMap); + } + /** * Create a new meta builder * @@ -62,35 +74,78 @@ public class SimpleCommandMeta extends CommandMeta { } @Override + @Deprecated public final @NonNull Optional getValue(final @NonNull String key) { - return Optional.ofNullable(this.metaMap.get(key)); + final Object result = this.metaMap.get(key); + if (result != null && !(result instanceof String)) { + throw new IllegalArgumentException("Key '" + key + "' has been used for a new typed command meta and contains a " + + "non-string value!"); + } + return Optional.ofNullable((String) result); } @Override + @Deprecated public final @NonNull String getOrDefault(final @NonNull String key, final @NonNull String defaultValue) { return this.getValue(key).orElse(defaultValue); } @Override + @SuppressWarnings("unchecked") + public final @NonNull Optional get(final @NonNull Key key) { + final Object value = this.metaMap.get(key.getName()); + if (value == null) { + // Attempt to use a fallback legacy type + if (key.getFallbackDerivation() != null) { + return Optional.ofNullable(key.getFallbackDerivation().apply(this)); + } + + return Optional.empty(); + } + if (!GenericTypeReflector.isSuperType(key.getValueType().getType(), value.getClass())) { + throw new IllegalArgumentException("Conflicting argument types between key type of " + + key.getValueType().getType() + " and value type of " + value.getClass()); + } + + return Optional.of((V) value); + } + + @Override + public final @NonNull V getOrDefault(final @NonNull Key key, final @NonNull V defaultValue) { + return this.get(key).orElse(defaultValue); + } + + @Override + @Deprecated public final @NonNull Map<@NonNull String, @NonNull String> getAll() { + return this.metaMap.entrySet() + .stream().filter(ent -> ent.getValue() instanceof String) + .collect(Collectors., String, String>toMap( + Map.Entry::getKey, + ent -> ent.getValue().toString() + )); + } + + @Override + public final @NonNull Map<@NonNull String, @NonNull ?> getAllValues() { return new HashMap<>(this.metaMap); } @Override - public final boolean equals(final Object o) { - if (this == o) { + public final boolean equals(final Object other) { + if (this == other) { return true; } - if (o == null || getClass() != o.getClass()) { + if (other == null || getClass() != other.getClass()) { return false; } - final SimpleCommandMeta that = (SimpleCommandMeta) o; - return Objects.equals(metaMap, that.metaMap); + final SimpleCommandMeta that = (SimpleCommandMeta) other; + return Objects.equals(this.metaMap, that.metaMap); } @Override public final int hashCode() { - return Objects.hashCode(metaMap); + return Objects.hashCode(this.metaMap); } /** @@ -98,7 +153,7 @@ public class SimpleCommandMeta extends CommandMeta { */ public static final class Builder { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); private Builder() { } @@ -110,7 +165,11 @@ public class SimpleCommandMeta extends CommandMeta { * @return Builder instance */ public @NonNull Builder with(final @NonNull CommandMeta commandMeta) { - commandMeta.getAll().forEach(this::with); + if (commandMeta instanceof SimpleCommandMeta) { + this.map.putAll(((SimpleCommandMeta) commandMeta).metaMap); + } else { + this.map.putAll(commandMeta.getAllValues()); + } return this; } @@ -120,7 +179,9 @@ public class SimpleCommandMeta extends CommandMeta { * @param key Key * @param value Value * @return Builder instance + * @deprecated For removal since 1.3.0, use {@link #with(Key, Object) the typesafe alternative} instead */ + @Deprecated public @NonNull Builder with( final @NonNull String key, final @NonNull String value @@ -129,13 +190,30 @@ public class SimpleCommandMeta extends CommandMeta { return this; } + /** + * Store a new key-value pair in the meta map + * + * @param Value type + * @param key Key + * @param value Value + * @return Builder instance + * @since 1.3.0 + */ + public @NonNull Builder with( + final @NonNull Key key, + final @NonNull V value + ) { + this.map.put(key.getName(), value); + return this; + } + /** * Construct a new meta instance * * @return Meta instance */ public @NonNull SimpleCommandMeta build() { - return new SimpleCommandMeta(this.map); + return new SimpleCommandMeta(this.map, false); } } diff --git a/cloud-core/src/main/java/cloud/commandframework/meta/SimpleKey.java b/cloud-core/src/main/java/cloud/commandframework/meta/SimpleKey.java new file mode 100644 index 00000000..b8b863b3 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/meta/SimpleKey.java @@ -0,0 +1,84 @@ +// +// 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.meta; + +import io.leangen.geantyref.GenericTypeReflector; +import io.leangen.geantyref.TypeToken; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.function.Function; + +final class SimpleKey implements CommandMeta.Key { + + private final @NonNull TypeToken valueType; + private final @NonNull String name; + private final @Nullable Function<@NonNull CommandMeta, @Nullable V> derivationFunction; + + SimpleKey( + final @NonNull TypeToken valueType, + final @NonNull String name, + final @Nullable Function<@NonNull CommandMeta, @Nullable V> derivationFunction + ) { + this.valueType = valueType; + this.name = name; + this.derivationFunction = derivationFunction; + } + + @Override + public @NonNull TypeToken getValueType() { + return this.valueType; + } + + @Override + public @NonNull String getName() { + return this.name; + } + + @Override + public @Nullable Function<@NonNull CommandMeta, @Nullable V> getFallbackDerivation() { + return this.derivationFunction; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + final SimpleKey that = (SimpleKey) other; + return this.valueType.equals(that.valueType) + && this.name.equals(that.name); + } + + @Override + public int hashCode() { + return 7 * GenericTypeReflector.hashCode(this.valueType.getAnnotatedType()) + + 31 * this.name.hashCode(); + } + +} diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java index 8960887f..a1c41c81 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java @@ -25,6 +25,7 @@ package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.standard.IntegerArgument; +import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.types.tuples.Pair; import org.junit.jupiter.api.Assertions; @@ -43,14 +44,14 @@ class CommandHelpHandlerTest { @BeforeAll static void setup() { manager = new TestCommandManager(); - final SimpleCommandMeta meta1 = SimpleCommandMeta.builder().with("description", "Command with only literals").build(); + final SimpleCommandMeta meta1 = SimpleCommandMeta.builder().with(CommandMeta.DESCRIPTION, "Command with only literals").build(); manager.command(manager.commandBuilder("test", meta1).literal("this").literal("thing").build()); - final SimpleCommandMeta meta2 = SimpleCommandMeta.builder().with("description", "Command with variables").build(); + final SimpleCommandMeta meta2 = SimpleCommandMeta.builder().with(CommandMeta.DESCRIPTION, "Command with variables").build(); manager.command(manager.commandBuilder("test", meta2).literal("int"). argument(IntegerArgument.of("int"), Description.of("A number")).build()); manager.command(manager.commandBuilder("vec") - .meta("description", "Takes in a vector") + .meta(CommandMeta.DESCRIPTION, "Takes in a vector") .argumentPair("vec", Pair.of("x", "y"), Pair.of(Double.class, Double.class), Description.of("Vector") ) diff --git a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/PircBotXCommandManager.java b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/PircBotXCommandManager.java index ae2b3f82..00b5a74b 100644 --- a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/PircBotXCommandManager.java +++ b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/PircBotXCommandManager.java @@ -119,8 +119,8 @@ public class PircBotXCommandManager extends CommandManager { @Override public final boolean hasPermission( - @NonNull final C sender, - @NonNull final String permission + final @NonNull C sender, + final @NonNull String permission ) { return this.permissionFunction.apply(sender, permission); } @@ -140,7 +140,7 @@ public class PircBotXCommandManager extends CommandManager { return this.commandPrefix; } - @NonNull final Function getUserMapper() { + final @NonNull Function getUserMapper() { return this.userMapper; } diff --git a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/arguments/UserArgument.java b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/arguments/UserArgument.java index 8cae8c68..9e632bca 100644 --- a/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/arguments/UserArgument.java +++ b/cloud-irc/cloud-pircbotx/src/main/java/cloud/commandframework/pircbotx/arguments/UserArgument.java @@ -128,8 +128,8 @@ public final class UserArgument extends CommandArgument { @Override public @NonNull ArgumentParseResult<@NonNull User> parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String input = inputQueue.peek(); if (input == null) { diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java index e60f8622..55167ec8 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommand.java @@ -31,6 +31,7 @@ import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; +import cloud.commandframework.meta.CommandMeta; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginIdentifiableCommand; @@ -63,7 +64,7 @@ final class BukkitCommand extends org.bukkit.command.Command implements Plugi ) { super( label, - cloudCommand.getCommandMeta().getOrDefault("description", ""), + cloudCommand.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, ""), "", aliases ); @@ -163,7 +164,7 @@ final class BukkitCommand extends org.bukkit.command.Command implements Plugi @Override public String getDescription() { - return this.cloudCommand.getCommandMeta().getOrDefault("description", ""); + return this.cloudCommand.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, ""); } @Override 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 b67ce76b..03471df6 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 @@ -215,6 +215,7 @@ public class BukkitCommandManager extends CommandManager implements Brigad * @return Meta data */ @Override + @SuppressWarnings("deprecation") public @NonNull BukkitCommandMeta createDefaultCommandMeta() { return BukkitCommandMetaBuilder.builder().withDescription("").build(); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMeta.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMeta.java index 96721a58..00d63ce8 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMeta.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMeta.java @@ -26,6 +26,12 @@ package cloud.commandframework.bukkit; import cloud.commandframework.meta.SimpleCommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; +/** + * Bukkit-specific command metadata holder. + * + * @deprecated for removal since 1.3.0. Use the standard {@link SimpleCommandMeta instead}. + */ +@Deprecated public class BukkitCommandMeta extends SimpleCommandMeta { /** @@ -34,7 +40,7 @@ public class BukkitCommandMeta extends SimpleCommandMeta { * @param simpleCommandMeta Simple command meta data instance that gets mirrored */ public BukkitCommandMeta(final @NonNull SimpleCommandMeta simpleCommandMeta) { - super(simpleCommandMeta.getAll()); + super(simpleCommandMeta); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMetaBuilder.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMetaBuilder.java index b599da22..8d666ac1 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMetaBuilder.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandMetaBuilder.java @@ -24,8 +24,15 @@ package cloud.commandframework.bukkit; import cloud.commandframework.meta.CommandMeta; +import cloud.commandframework.meta.SimpleCommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; +/** + * Command meta builder with bukkit-specific parameters. + * + * @deprecated for removal since 1.3.0, use plain {@link SimpleCommandMeta.Builder} instead. + */ +@Deprecated public final class BukkitCommandMetaBuilder { private BukkitCommandMetaBuilder() { @@ -73,7 +80,7 @@ public final class BukkitCommandMetaBuilder { * @return Meta instance */ public @NonNull BukkitCommandMeta build() { - return new BukkitCommandMeta(CommandMeta.simple().with("description", this.description).build()); + return new BukkitCommandMeta(CommandMeta.simple().with(CommandMeta.DESCRIPTION, this.description).build()); } } diff --git a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerArgument.java b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerArgument.java index 388f9aa9..1675ab11 100644 --- a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerArgument.java +++ b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/PlayerArgument.java @@ -139,8 +139,8 @@ public final class PlayerArgument extends CommandArgument { @Override public @NonNull ArgumentParseResult<@NonNull ProxiedPlayer> parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String input = inputQueue.peek(); if (input == null) { diff --git a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/ServerArgument.java b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/ServerArgument.java index d7eae973..2928c5e8 100644 --- a/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/ServerArgument.java +++ b/cloud-minecraft/cloud-bungee/src/main/java/cloud/commandframework/bungee/arguments/ServerArgument.java @@ -137,8 +137,8 @@ public final class ServerArgument extends CommandArgument { @Override public @NonNull ArgumentParseResult<@NonNull ServerInfo> parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String input = inputQueue.peek(); if (input == null) { diff --git a/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java b/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java index 4bf7ef6e..f4a1cf7e 100644 --- a/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java +++ b/cloud-minecraft/cloud-cloudburst/src/main/java/cloud/commandframework/cloudburst/CloudburstCommand.java @@ -31,6 +31,7 @@ import cloud.commandframework.exceptions.InvalidCommandSenderException; import cloud.commandframework.exceptions.InvalidSyntaxException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.exceptions.NoSuchCommandException; +import cloud.commandframework.meta.CommandMeta; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.server.command.CommandSender; import org.cloudburstmc.server.command.PluginCommand; @@ -61,7 +62,7 @@ final class CloudburstCommand extends PluginCommand { super(manager.getOwningPlugin(), CommandData.builder(label) .addAliases(aliases.toArray(new String[0])) .addPermission(cloudCommand.getCommandPermission().toString()) - .setDescription(cloudCommand.getCommandMeta().getOrDefault("description", "")) + .setDescription(cloudCommand.getCommandMeta().getOrDefault(CommandMeta.DESCRIPTION, "")) .build()); this.command = command; this.manager = manager; diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerArgument.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerArgument.java index a87e11fb..a2766a11 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerArgument.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/PlayerArgument.java @@ -137,8 +137,8 @@ public final class PlayerArgument extends CommandArgument { @Override public @NonNull ArgumentParseResult<@NonNull Player> parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String input = inputQueue.peek(); if (input == null) { diff --git a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerArgument.java b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerArgument.java index 5cab7ad3..819ee4b5 100644 --- a/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerArgument.java +++ b/cloud-minecraft/cloud-velocity/src/main/java/cloud/commandframework/velocity/arguments/ServerArgument.java @@ -133,8 +133,8 @@ public final class ServerArgument extends CommandArgument parse( - @NonNull final CommandContext<@NonNull C> commandContext, - @NonNull final Queue<@NonNull String> inputQueue + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue ) { final String input = inputQueue.peek(); if (input == null) { 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 2ed2ecfc..390d5559 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,7 +44,6 @@ import cloud.commandframework.arguments.standard.EnumArgument; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArrayArgument; import cloud.commandframework.bukkit.BukkitCommandManager; -import cloud.commandframework.bukkit.BukkitCommandMetaBuilder; import cloud.commandframework.bukkit.CloudBukkitCapabilities; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.parsers.EnchantmentArgument; @@ -178,9 +177,9 @@ public final class ExamplePlugin extends JavaPlugin { // @CommandMethod // final Function commandMetaFunction = p -> - BukkitCommandMetaBuilder.builder() + CommandMeta.simple() // This will allow you to decorate commands with descriptions - .withDescription(p.get(StandardParameters.DESCRIPTION, "No description")) + .with(CommandMeta.DESCRIPTION, p.get(StandardParameters.DESCRIPTION, "No description")) .build(); this.annotationParser = new AnnotationParser<>( /* Manager */ this.manager, diff --git a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/ExampleBot.java b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/ExampleBot.java index 4609303e..592d9936 100644 --- a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/ExampleBot.java +++ b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/ExampleBot.java @@ -50,7 +50,7 @@ public final class ExampleBot { * * @param args Arguments to start the bot with (NOT used) */ - public static void main(@NonNull final String[] args) { + public static void main(final @NonNull String[] args) { SimplixInstaller .instance() .register(ExampleBot.class); diff --git a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/application/ExampleApplication.java b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/application/ExampleApplication.java index 0c7b5a49..a503ef26 100644 --- a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/application/ExampleApplication.java +++ b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/application/ExampleApplication.java @@ -56,9 +56,9 @@ public class ExampleApplication { */ @Inject public ExampleApplication( - @NonNull final DiscordApiComponent discordApiComponent, - @NonNull final CommandsComponent commandsComponent, - @NonNull final ScheduledExecutorService executorService + final @NonNull DiscordApiComponent discordApiComponent, + final @NonNull CommandsComponent commandsComponent, + final @NonNull ScheduledExecutorService executorService ) throws Exception { this.discordApiComponent = discordApiComponent; diff --git a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/CommandsComponent.java b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/CommandsComponent.java index c6571e6a..0b3c6691 100644 --- a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/CommandsComponent.java +++ b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/CommandsComponent.java @@ -53,7 +53,7 @@ public class CommandsComponent { * @param discordApiComponent Instance of the {@link DiscordApiComponent} for registering the command listener */ @Inject - public CommandsComponent(@NonNull final DiscordApiComponent discordApiComponent) { + public CommandsComponent(final @NonNull DiscordApiComponent discordApiComponent) { this.discordApiComponent = discordApiComponent; } diff --git a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/DiscordApiComponent.java b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/DiscordApiComponent.java index 3cbaa7c8..6e752053 100644 --- a/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/DiscordApiComponent.java +++ b/examples/example-javacord/src/main/java/cloud/commandframework/examples/javacord/components/DiscordApiComponent.java @@ -55,7 +55,7 @@ public class DiscordApiComponent { * * @param token The Discord Bot token */ - public final void login(@NonNull final String token) { + public final void login(final @NonNull String token) { LOG.info("Logging in to Discord..."); this.api = this.builder.setToken(token).login().join();