From d681ba58407fdc20579bf2ea50a547c2b42a6fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= <4096670+Citymonstret@users.noreply.github.com> Date: Thu, 26 May 2022 06:53:54 +0200 Subject: [PATCH] feat: annotation string processors (#353) adds a system for processing strings found in annotations before they're used by AnnotationParser implements #347 Also, because we're using "-Werror", the code won't actually build (and thus tests won't work) using JDK18. To remedy this, a bunch of @SuppressWarnings("serial")s has been added to the exceptions. We don't serialize exceptions, and they're in fact non-serializable because of their members, so this is the appropriate solution (well, the better solution would be to make them serializable, but that's outside the scope of this PR). --- CHANGELOG.md | 3 + .../kotlin/cloud.base-conventions.gradle.kts | 1 + .../annotations/AnnotationParser.java | 85 ++++++++--- .../annotations/FlagExtractor.java | 30 ++-- .../MethodCommandExecutionHandler.java | 23 ++- .../PatternReplacingStringProcessor.java | 76 ++++++++++ .../PropertyReplacingStringProcessor.java | 69 +++++++++ .../annotations/StringProcessor.java | 65 +++++++++ .../annotations/NoOpStringProcessorTest.java | 49 +++++++ .../PatternReplacingStringProcessorTest.java | 92 ++++++++++++ .../PropertyReplacingStringProcessorTest.java | 102 ++++++++++++++ .../annotations/TestCommandManager.java | 2 +- .../feature/StringProcessingTest.java | 133 ++++++++++++++++++ .../preprocessor/RegexPreprocessor.java | 1 + .../arguments/standard/ByteArgument.java | 1 + .../arguments/standard/DoubleArgument.java | 1 + .../arguments/standard/FloatArgument.java | 1 + .../arguments/standard/IntegerArgument.java | 1 + .../arguments/standard/LongArgument.java | 1 + .../arguments/standard/ShortArgument.java | 1 + .../exceptions/AmbiguousNodeException.java | 2 +- .../exceptions/CommandExecutionException.java | 1 + .../exceptions/CommandParseException.java | 2 +- .../InvalidCommandSenderException.java | 1 + .../exceptions/NoCommandInLeafException.java | 2 +- .../exceptions/NoPermissionException.java | 2 +- .../exceptions/parsing/ParserException.java | 1 + gradle/libs.versions.yml | 5 + 28 files changed, 715 insertions(+), 38 deletions(-) create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/PatternReplacingStringProcessor.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/PropertyReplacingStringProcessor.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/StringProcessor.java create mode 100644 cloud-annotations/src/test/java/cloud/commandframework/annotations/NoOpStringProcessorTest.java create mode 100644 cloud-annotations/src/test/java/cloud/commandframework/annotations/PatternReplacingStringProcessorTest.java create mode 100644 cloud-annotations/src/test/java/cloud/commandframework/annotations/PropertyReplacingStringProcessorTest.java create mode 100644 cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/StringProcessingTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fb64960d..5ddfae22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Annotations: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353)) + ### Fixed - Fix missing caption registration for the regex caption ([#351](https://github.com/Incendo/cloud/pull/351)) diff --git a/build-logic/src/main/kotlin/cloud.base-conventions.gradle.kts b/build-logic/src/main/kotlin/cloud.base-conventions.gradle.kts index f58f6747..ffab9b46 100644 --- a/build-logic/src/main/kotlin/cloud.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/cloud.base-conventions.gradle.kts @@ -114,6 +114,7 @@ dependencies { testImplementation(libs.jupiterEngine) testImplementation(libs.mockitoCore) testImplementation(libs.mockitoKotlin) + testImplementation(libs.mockitoJupiter) testImplementation(libs.truth) testImplementation(libs.truthJava8) errorprone(libs.errorproneCore) 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 7094dd17..35343af0 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -96,6 +96,8 @@ public final class AnnotationParser { private final MetaFactory metaFactory; private final FlagExtractor flagExtractor; + private StringProcessor stringProcessor; + /** * Construct a new annotation parser * @@ -118,12 +120,12 @@ public final class AnnotationParser { this.preprocessorMappers = new HashMap<>(); this.builderModifiers = new HashMap<>(); this.commandMethodFactories = new HashMap<>(); - this.flagExtractor = new FlagExtractor(manager); + this.flagExtractor = new FlagExtractor(manager, this); this.registerAnnotationMapper(CommandDescription.class, d -> - ParserParameters.single(StandardParameters.DESCRIPTION, d.value())); + ParserParameters.single(StandardParameters.DESCRIPTION, this.processString(d.value()))); this.registerPreprocessorMapper(Regex.class, annotation -> RegexPreprocessor.of( - annotation.value(), - Caption.of(annotation.failureCaption()) + this.processString(annotation.value()), + Caption.of(this.processString(annotation.failureCaption())) )); this.getParameterInjectorRegistry().registerInjector( String[].class, @@ -131,6 +133,7 @@ public final class AnnotationParser { ? null : context.getRawInput().toArray(new String[0]) ); + this.stringProcessor = StringProcessor.noOp(); } @SuppressWarnings("unchecked") @@ -273,6 +276,48 @@ public final class AnnotationParser { return this.manager.parameterInjectorRegistry(); } + /** + * Returns the string processor used by this parser. + * + * @return the string processor + * @since 1.7.0 + */ + public @NonNull StringProcessor stringProcessor() { + return this.stringProcessor; + } + + /** + * Replaces the string processor of this parser. + * + * @param stringProcessor the new string processor + * @since 1.7.0 + */ + public void stringProcessor(final @NonNull StringProcessor stringProcessor) { + this.stringProcessor = stringProcessor; + } + + /** + * Processes the {@code input} string and returns the processed result. + * + * @param input the input string + * @return the processed string + * @since 1.7.0 + */ + public @NonNull String processString(final @NonNull String input) { + return this.stringProcessor().processString(input); + } + + /** + * Processes the input {@code strings} and returns the processed result. + * + * @param strings the input strings + * @return the processed strings + * @since 1.7.0 + */ + public @NonNull String[] processStrings(final @NonNull String[] strings) { + return Arrays.stream(strings).map(this::processString).toArray(String[]::new); + } + /** * Scan a class instance of {@link CommandMethod} annotations and attempt to * compile them into {@link Command} instances @@ -336,7 +381,7 @@ public final class AnnotationParser { } try { this.manager.getParserRegistry().registerSuggestionProvider( - suggestions.value(), + this.processString(suggestions.value()), new MethodSuggestionsProvider<>(instance, method) ); } catch (final Exception e) { @@ -367,15 +412,16 @@ public final class AnnotationParser { )); } try { + final String suggestions = this.processString(parser.suggestions()); final BiFunction, String, List> suggestionsProvider; - if (parser.suggestions().isEmpty()) { + if (suggestions.isEmpty()) { suggestionsProvider = (context, input) -> Collections.emptyList(); } else { - suggestionsProvider = this.manager.getParserRegistry().getSuggestionProvider(parser.suggestions()) + suggestionsProvider = this.manager.getParserRegistry().getSuggestionProvider(suggestions) .orElseThrow(() -> new NullPointerException( String.format( "Cannot find the suggestions provider with name '%s'", - parser.suggestions() + suggestions ) )); } @@ -386,14 +432,15 @@ public final class AnnotationParser { ); final Function> parserFunction = parameters -> methodArgumentParser; - if (parser.name().isEmpty()) { + final String name = this.processString(parser.name()); + if (name.isEmpty()) { this.manager.getParserRegistry().registerParserSupplier( TypeToken.get(method.getGenericReturnType()), parserFunction ); } else { this.manager.getParserRegistry().registerNamedParserSupplier( - parser.name(), + name, parserFunction ); } @@ -410,12 +457,12 @@ public final class AnnotationParser { ) { final AnnotationAccessor classAnnotations = AnnotationAccessor.of(instance.getClass()); final CommandMethod classCommandMethod = classAnnotations.annotation(CommandMethod.class); - final String syntaxPrefix = classCommandMethod == null ? "" : (classCommandMethod.value() + " "); + final String syntaxPrefix = classCommandMethod == null ? "" : (this.processString(classCommandMethod.value()) + " "); final Collection> commands = new ArrayList<>(); for (final CommandMethodPair commandMethodPair : methodPairs) { final CommandMethod commandMethod = commandMethodPair.getCommandMethod(); final Method method = commandMethodPair.getMethod(); - final String syntax = syntaxPrefix + commandMethod.value(); + final String syntax = syntaxPrefix + this.processString(commandMethod.value()); final List tokens = this.syntaxParser.apply(syntax); /* Determine command name */ final String commandToken = syntax.split(" ")[0].split("\\|")[0]; @@ -438,9 +485,10 @@ public final class AnnotationParser { final Map, String> argumentDescriptions = new HashMap<>(); /* Go through all annotated parameters and build up the argument tree */ for (final ArgumentParameterPair argumentPair : arguments) { + final String argumentName = this.processString(argumentPair.argumentName()); final CommandArgument argument = this.buildArgument( method, - this.findSyntaxFragment(tokens, argumentPair.argumentName()), + this.findSyntaxFragment(tokens, argumentName), argumentPair ); commandArguments.put(argument.getName(), argument); @@ -482,7 +530,7 @@ public final class AnnotationParser { final CommandPermission commandPermission = getMethodOrClassAnnotation(method, CommandPermission.class); if (commandPermission != null) { - builder = builder.permission(commandPermission.value()); + builder = builder.permission(this.processString(commandPermission.value())); } if (commandMethod.requiredSender() != Object.class) { @@ -496,7 +544,7 @@ public final class AnnotationParser { instance, commandArguments, method, - this.getParameterInjectorRegistry() + this /* annotationParser */ ); /* Create the command execution handler */ @@ -542,7 +590,7 @@ public final class AnnotationParser { /* Check if we need to construct a proxy */ if (method.isAnnotationPresent(ProxiedBy.class)) { final ProxiedBy proxyAnnotation = method.getAnnotation(ProxiedBy.class); - final String proxy = proxyAnnotation.value(); + final String proxy = this.processString(proxyAnnotation.value()); if (proxy.contains(" ")) { throw new IllegalArgumentException("@ProxiedBy proxies may only contain single literals"); } @@ -605,16 +653,17 @@ public final class AnnotationParser { ))); } /* Check whether or not the corresponding method parameter actually exists */ + final String argumentName = this.processString(argumentPair.argumentName()); if (syntaxFragment == null || syntaxFragment.getArgumentMode() == ArgumentMode.LITERAL) { throw new IllegalArgumentException(String.format( "Invalid command argument '%s' in method '%s': " - + "Missing syntax mapping", argumentPair.argumentName(), method.getName())); + + "Missing syntax mapping", argumentName, method.getName())); } final Argument argument = argumentPair.getArgument(); /* Create the argument builder */ @SuppressWarnings("rawtypes") final CommandArgument.Builder argumentBuilder = CommandArgument.ofType( parameter.getType(), - argumentPair.argumentName() + argumentName ); /* Set the argument requirement status */ if (syntaxFragment.getArgumentMode() == ArgumentMode.OPTIONAL) { diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java index 4166c2cb..4dfc1da3 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/FlagExtractor.java @@ -45,9 +45,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNull CommandFlag>> { private final CommandManager commandManager; + private final AnnotationParser annotationParser; - FlagExtractor(final @NonNull CommandManager commandManager) { + FlagExtractor( + final @NonNull CommandManager commandManager, + final @NonNull AnnotationParser annotationParser + ) { this.commandManager = commandManager; + this.annotationParser = annotationParser; } @Override @@ -59,11 +64,12 @@ final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNu continue; } final Flag flag = parameter.getAnnotation(Flag.class); + final String flagName = this.annotationParser.processString(flag.value()); final CommandFlag.Builder builder = this.commandManager - .flagBuilder(flag.value()) - .withDescription(ArgumentDescription.of(flag.description())) - .withAliases(flag.aliases()) - .withPermission(Permission.of(flag.permission())); + .flagBuilder(this.annotationParser.processString(flagName)) + .withDescription(ArgumentDescription.of(this.annotationParser.processString(flag.description()))) + .withAliases(this.annotationParser.processStrings(flag.aliases())) + .withPermission(Permission.of(this.annotationParser.processString(flag.permission()))); if (parameter.getType().equals(boolean.class)) { flags.add(builder.build()); } else { @@ -71,28 +77,30 @@ final class FlagExtractor implements Function<@NonNull Method, Collection<@NonNu final Collection annotations = Arrays.asList(parameter.getAnnotations()); final ParserRegistry registry = this.commandManager.getParserRegistry(); final ArgumentParser parser; - if (flag.parserName().isEmpty()) { + final String parserName = this.annotationParser.processString(flag.parserName()); + if (parserName.isEmpty()) { parser = registry.createParser(token, registry.parseAnnotations(token, annotations)) .orElse(null); } else { - parser = registry.createParser(flag.parserName(), registry.parseAnnotations(token, annotations)) + parser = registry.createParser(parserName, registry.parseAnnotations(token, annotations)) .orElse(null); } if (parser == null) { throw new IllegalArgumentException( String.format("Cannot find parser for type '%s' for flag '%s' in method '%s'", - parameter.getType().getCanonicalName(), flag.value(), method.getName() + parameter.getType().getCanonicalName(), flagName, method.getName() )); } final BiFunction> suggestionProvider; - if (!flag.suggestions().isEmpty()) { - suggestionProvider = registry.getSuggestionProvider(flag.suggestions()).orElse(null); + final String suggestions = this.annotationParser.processString(flag.suggestions()); + if (!suggestions.isEmpty()) { + suggestionProvider = registry.getSuggestionProvider(suggestions).orElse(null); } else { suggestionProvider = null; } final CommandArgument.Builder argumentBuilder0 = CommandArgument.ofType( parameter.getType(), - flag.value() + flagName ); final CommandArgument.Builder argumentBuilder = argumentBuilder0.asRequired() .manager(this.commandManager) diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java index 83d3fc62..12854e44 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/MethodCommandExecutionHandler.java @@ -51,6 +51,7 @@ public class MethodCommandExecutionHandler implements CommandExecutionHandler private final Parameter[] parameters; private final MethodHandle methodHandle; private final AnnotationAccessor annotationAccessor; + private final AnnotationParser annotationParser; /** * Constructs a new method command execution handler @@ -65,6 +66,7 @@ public class MethodCommandExecutionHandler implements CommandExecutionHandler this.methodHandle = MethodHandles.lookup().unreflect(context.method).bindTo(context.instance); this.parameters = context.method.getParameters(); this.annotationAccessor = AnnotationAccessor.of(context.method); + this.annotationParser = context.annotationParser(); } /** @@ -123,10 +125,11 @@ public class MethodCommandExecutionHandler implements CommandExecutionHandler } } else if (parameter.isAnnotationPresent(Flag.class)) { final Flag flag = parameter.getAnnotation(Flag.class); + final String flagName = this.annotationParser.processString(flag.value()); if (parameter.getType() == boolean.class) { - arguments.add(flagContext.isPresent(flag.value())); + arguments.add(flagContext.isPresent(flagName)); } else { - arguments.add(flagContext.getValue(flag.value(), null)); + arguments.add(flagContext.getValue(flagName, null)); } } else { if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) { @@ -205,18 +208,20 @@ public class MethodCommandExecutionHandler implements CommandExecutionHandler private final Map> commandArguments; private final Method method; private final ParameterInjectorRegistry injectorRegistry; + private final AnnotationParser annotationParser; CommandMethodContext( final @NonNull Object instance, final @NonNull Map<@NonNull String, @NonNull CommandArgument<@NonNull C, @NonNull ?>> commandArguments, final @NonNull Method method, - final @NonNull ParameterInjectorRegistry injectorRegistry + final @NonNull AnnotationParser annotationParser ) { this.instance = instance; this.commandArguments = commandArguments; this.method = method; this.method.setAccessible(true); - this.injectorRegistry = injectorRegistry; + this.injectorRegistry = annotationParser.getParameterInjectorRegistry(); + this.annotationParser = annotationParser; } /** @@ -259,6 +264,16 @@ public class MethodCommandExecutionHandler implements CommandExecutionHandler return this.injectorRegistry; } + /** + * The annotation parser + * + * @return Annotation parser + * @since 1.7.0 + */ + public @NonNull AnnotationParser annotationParser() { + return this.annotationParser; + } + } } diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/PatternReplacingStringProcessor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/PatternReplacingStringProcessor.java new file mode 100644 index 00000000..9e75dfee --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/PatternReplacingStringProcessor.java @@ -0,0 +1,76 @@ +// +// 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.annotations; + +import java.util.function.Function; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * {@link StringProcessor} that replaces matches of a given {@link Pattern}. + * + * @since 1.7.0 + */ +public class PatternReplacingStringProcessor implements StringProcessor { + + private final Pattern pattern; + private final Function replacementProvider; + + /** + * Creates a new property replacing string processor. + * + * @param pattern the pattern to search for + * @param replacementProvider function generating the replacement strings + */ + public PatternReplacingStringProcessor( + final @NonNull Pattern pattern, + final @NonNull Function<@NonNull MatchResult, @Nullable String> replacementProvider + ) { + this.pattern = pattern; + this.replacementProvider = replacementProvider; + } + + /** + * {@inheritDoc} + */ + @Override + public @NonNull String processString( + @NonNull final String input + ) { + final Matcher matcher = this.pattern.matcher(input); + + // Kind of copied from the JDK 9+ implementation of "Matcher#replaceFirst" + final StringBuffer stringBuffer = new StringBuffer(); + while (matcher.find()) { + final String replacement = this.replacementProvider.apply(matcher); + matcher.appendReplacement(stringBuffer, replacement == null ? "$0" : replacement); + } + matcher.appendTail(stringBuffer); + + return stringBuffer.toString(); + } +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/PropertyReplacingStringProcessor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/PropertyReplacingStringProcessor.java new file mode 100644 index 00000000..e744aa63 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/PropertyReplacingStringProcessor.java @@ -0,0 +1,69 @@ +// +// 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.annotations; + +import java.util.function.Function; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * {@link PropertyReplacementProvider} that replaces all sub-strings with the format + * {@code ${some.property}} with a function-generated string. + * + * @since 1.7.0 + */ +public class PropertyReplacingStringProcessor extends PatternReplacingStringProcessor { + + public static final Pattern PROPERTY_REGEX = Pattern.compile("\\$\\{(\\S+)}"); + + /** + * Creates a new property replacing string processor. + * + * @param replacementProvider function generating the replacement strings + */ + public PropertyReplacingStringProcessor( + final @NonNull Function<@NonNull String, @Nullable String> replacementProvider + ) { + super(PROPERTY_REGEX, new PropertyReplacementProvider(replacementProvider)); + } + + + private static final class PropertyReplacementProvider implements Function<@NonNull MatchResult, @Nullable String> { + + private final Function replacementProvider; + + private PropertyReplacementProvider( + final @NonNull Function replacementProvider + ) { + this.replacementProvider = replacementProvider; + } + + @Override + public @Nullable String apply(final @NonNull MatchResult matchResult) { + return this.replacementProvider.apply(matchResult.group(1)); + } + } +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/StringProcessor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/StringProcessor.java new file mode 100644 index 00000000..a9966126 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/StringProcessor.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.annotations; + +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Processor that intercepts all cloud annotation strings. + * + * @since 1.7.0 + */ +@FunctionalInterface +public interface StringProcessor { + + /** + * Returns a string processor that simply returns the input string. + * + * @return no-op string processor + */ + static @NonNull StringProcessor noOp() { + return new NoOpStringProcessor(); + } + + /** + * Processes the {@code input} string and returns the processed result. + *

+ * This should always return a non-{@code null} result. If the input string + * isn't applicable to the processor implementation, the original string should + * be returned. + * + * @param input the input string + * @return the processed string + */ + @NonNull String processString(@NonNull String input); + + + final class NoOpStringProcessor implements StringProcessor { + + @Override + public @NonNull String processString(final @NonNull String input) { + return input; + } + } +} diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/NoOpStringProcessorTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/NoOpStringProcessorTest.java new file mode 100644 index 00000000..1c72e595 --- /dev/null +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/NoOpStringProcessorTest.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.annotations; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +class NoOpStringProcessorTest { + + @Test + void ProcessString_AnyInput_ReturnsOriginalInput() { + // Will force the input string to be scrambled 10 times. + for (int i = 0; i < 10; i++) { + // Arrange + final StringProcessor stringProcessor = StringProcessor.noOp(); + final String input = ThreadLocalRandom.current().ints().mapToObj(Integer::toString).limit(32).collect(Collectors.joining()); + + // Act + final String output = stringProcessor.processString(input); + + // Assert + assertThat(input).isEqualTo(output); + } + } +} diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/PatternReplacingStringProcessorTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/PatternReplacingStringProcessorTest.java new file mode 100644 index 00000000..1c5bf303 --- /dev/null +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/PatternReplacingStringProcessorTest.java @@ -0,0 +1,92 @@ +// +// 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.annotations; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.function.Function; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PatternReplacingStringProcessorTest { + + private static final Pattern TEST_PATTERN = Pattern.compile("\\[(\\S+)]"); + + private PatternReplacingStringProcessor patternReplacingStringProcessor; + + @Mock + private Function replacementProvider; + + @BeforeEach + void setup() { + this.patternReplacingStringProcessor = new PatternReplacingStringProcessor( + TEST_PATTERN, + this.replacementProvider + ); + } + + @Test + void ProcessString_MatchingInput_ReplacesGroups() { + // Arrange + when(this.replacementProvider.apply(any())).thenAnswer(iom -> iom.getArgument(0, MatchResult.class).group(1)); + + final String input = "[hello] [world]!"; + + // Act + final String output = this.patternReplacingStringProcessor.processString(input); + + // Act + assertThat(output).isEqualTo("hello world!"); + + verify(this.replacementProvider, times(2)).apply(notNull()); + verifyNoMoreInteractions(this.replacementProvider); + } + + @Test + void ProcessString_NullReplacement_InputPreserved() { + // Arrange + final String input = "[input] ..."; + + // Act + final String output = this.patternReplacingStringProcessor.processString(input); + + // Act + assertThat(output).isEqualTo(input); + + verify(this.replacementProvider).apply(notNull()); + verifyNoMoreInteractions(this.replacementProvider); + } +} diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/PropertyReplacingStringProcessorTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/PropertyReplacingStringProcessorTest.java new file mode 100644 index 00000000..1b85c2ba --- /dev/null +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/PropertyReplacingStringProcessorTest.java @@ -0,0 +1,102 @@ +// +// 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.annotations; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.function.Function; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PropertyReplacingStringProcessorTest { + + private PropertyReplacingStringProcessor propertyReplacingStringProcessor; + + @Mock + private Function propertyProvider; + + @BeforeEach + void setup() { + this.propertyReplacingStringProcessor = new PropertyReplacingStringProcessor(this.propertyProvider); + } + + @Test + void ProcessString_KnownProperty_ReplacesWithValue() { + // Arrange + when(this.propertyProvider.apply(anyString())).thenAnswer(iom -> "transformed: " + iom.getArgument(0, String.class)); + + final String input = "${hello.world}"; + + // Act + final String output = this.propertyReplacingStringProcessor.processString(input); + + // Assert + assertThat(output).isEqualTo("transformed: hello.world"); + + verify(this.propertyProvider).apply("hello.world"); + verifyNoMoreInteractions(this.propertyProvider); + } + + @Test + void ProcessString_MultipleProperties_ReplacesAll() { + // Arrange + when(this.propertyProvider.apply(anyString())).thenAnswer(iom -> iom.getArgument(0, String.class)); + + final String input = "${cats} are cute, and so are ${dogs}!"; + + // Act + final String output = this.propertyReplacingStringProcessor.processString(input); + + // Assert + assertThat(output).isEqualTo("cats are cute, and so are dogs!"); + + verify(this.propertyProvider).apply("cats"); + verify(this.propertyProvider).apply("dogs"); + verifyNoMoreInteractions(this.propertyProvider); + } + + @Test + void ProcessString_NullProperty_InputPreserved() { + // Arrange + final String input = "${input} ..."; + + // Act + final String output = this.propertyReplacingStringProcessor.processString(input); + + // Act + assertThat(output).isEqualTo(input); + + verify(this.propertyProvider).apply(notNull()); + verifyNoMoreInteractions(this.propertyProvider); + } +} diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/TestCommandManager.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/TestCommandManager.java index fb6e6577..ac8723bd 100644 --- a/cloud-annotations/src/test/java/cloud/commandframework/annotations/TestCommandManager.java +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/TestCommandManager.java @@ -30,7 +30,7 @@ import cloud.commandframework.meta.SimpleCommandMeta; public class TestCommandManager extends CommandManager { - protected TestCommandManager() { + public TestCommandManager() { super(CommandExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()); } diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/StringProcessingTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/StringProcessingTest.java new file mode 100644 index 00000000..3a751c35 --- /dev/null +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/feature/StringProcessingTest.java @@ -0,0 +1,133 @@ +// +// 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.annotations.feature; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import cloud.commandframework.Command; +import cloud.commandframework.annotations.AnnotationParser; +import cloud.commandframework.annotations.Argument; +import cloud.commandframework.annotations.CommandDescription; +import cloud.commandframework.annotations.CommandMethod; +import cloud.commandframework.annotations.CommandPermission; +import cloud.commandframework.annotations.Flag; +import cloud.commandframework.annotations.PropertyReplacingStringProcessor; +import cloud.commandframework.annotations.TestCommandManager; +import cloud.commandframework.annotations.TestCommandSender; +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.compound.FlagArgument; +import cloud.commandframework.arguments.flags.CommandFlag; +import cloud.commandframework.arguments.parser.StandardParameters; +import cloud.commandframework.meta.CommandMeta; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class StringProcessingTest { + + private AnnotationParser annotationParser; + private TestCommandManager commandManager; + + @BeforeEach + void setup() { + this.commandManager = new TestCommandManager(); + this.annotationParser = new AnnotationParser<>( + this.commandManager, + TestCommandSender.class, + p -> CommandMeta.simple() + .with(CommandMeta.DESCRIPTION, p.get(StandardParameters.DESCRIPTION, "No description")) + .build() + ); + } + + @Test + @DisplayName("Tests @CommandMethod, @CommandPermission, @CommandDescription, @Argument & @Flag") + @SuppressWarnings("unchecked") + void testStringProcessing() { + // Arrange + final String testProperty = ThreadLocalRandom.current() + .ints() + .mapToObj(Integer::toString) + .limit(32) + .collect(Collectors.joining()); + final String testFlagName = ThreadLocalRandom.current() + .ints() + .mapToObj(Integer::toString) + .limit(32) + .collect(Collectors.joining()); + this.annotationParser.stringProcessor( + new PropertyReplacingStringProcessor( + s -> ImmutableMap.of( + "property.test", testProperty, + "property.arg", "argument", + "property.flag", testFlagName + ).get(s) + ) + ); + final TestClassA testClassA = new TestClassA(); + + // Act + this.annotationParser.parse(testClassA); + + // Assert + final List> commands = new ArrayList<>(this.commandManager.getCommands()); + assertThat(commands).hasSize(1); + + final Command command = commands.get(0); + assertThat(command.toString()).isEqualTo(String.format("%s argument flags", testProperty)); + assertThat(command.getCommandPermission().toString()).isEqualTo(testProperty); + assertThat(command.getCommandMeta().get(CommandMeta.DESCRIPTION)).hasValue(testProperty); + + final List> arguments = command.getArguments(); + assertThat(arguments).hasSize(3); + + final FlagArgument flagArgument = (FlagArgument) arguments.get(2); + assertThat(flagArgument).isNotNull(); + + final List> flags = new ArrayList<>(flagArgument.getFlags()); + assertThat(flags).hasSize(1); + assertThat(flags.get(0).getName()).isEqualTo(testFlagName); + } + + + private static class TestClassA { + + @CommandDescription("${property.test}") + @CommandPermission("${property.test}") + @CommandMethod("${property.test} ") + public void commandA( + final TestCommandSender sender, + @Argument("${property.arg}") final String arg, + @Flag("${property.flag}") final String flag + ) { + } + } +} 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 1aa0cc26..aa8b7bbf 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 @@ -112,6 +112,7 @@ public final class RegexPreprocessor implements BiFunction<@NonNull CommandCo /** * Exception thrown when input fails regex matching in {@link RegexPreprocessor} */ + @SuppressWarnings("serial") public static final class RegexValidationException extends IllegalArgumentException { private static final long serialVersionUID = 747826566058072233L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteArgument.java index dfee6c01..fd45fef3 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ByteArgument.java @@ -290,6 +290,7 @@ public final class ByteArgument extends CommandArgument { /** * Byte parse exception */ + @SuppressWarnings("serial") public static final class ByteParseException extends NumberParseException { private static final long serialVersionUID = -4724241304872989208L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DoubleArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DoubleArgument.java index 4f93d236..f95a59ff 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DoubleArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/DoubleArgument.java @@ -279,6 +279,7 @@ public final class DoubleArgument extends CommandArgument { } + @SuppressWarnings("serial") public static final class DoubleParseException extends NumberParseException { private static final long serialVersionUID = 1764554911581976586L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/FloatArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/FloatArgument.java index 8afc891e..08742c8f 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/FloatArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/FloatArgument.java @@ -274,6 +274,7 @@ public final class FloatArgument extends CommandArgument { } + @SuppressWarnings("serial") public static final class FloatParseException extends NumberParseException { private static final long serialVersionUID = -1162983846751812292L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java index fc71a589..65f4b38d 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/IntegerArgument.java @@ -337,6 +337,7 @@ public final class IntegerArgument extends CommandArgument { } + @SuppressWarnings("serial") public static final class IntegerParseException extends NumberParseException { private static final long serialVersionUID = -6933923056628373853L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongArgument.java index 9d123a23..717c25dd 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/LongArgument.java @@ -282,6 +282,7 @@ public final class LongArgument extends CommandArgument { } + @SuppressWarnings("serial") public static final class LongParseException extends NumberParseException { private static final long serialVersionUID = 4366856282301198232L; diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortArgument.java b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortArgument.java index ec30166d..e8eee7dc 100644 --- a/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortArgument.java +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/standard/ShortArgument.java @@ -279,6 +279,7 @@ public final class ShortArgument extends CommandArgument { } + @SuppressWarnings("serial") public static final class ShortParseException extends NumberParseException { private static final long serialVersionUID = -478674263339091032L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/AmbiguousNodeException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/AmbiguousNodeException.java index b06e18fa..880263d3 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/AmbiguousNodeException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/AmbiguousNodeException.java @@ -36,7 +36,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * is being inserted into a {@link CommandTree} and an ambiguity * is detected. */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "serial"}) public final class AmbiguousNodeException extends IllegalStateException { private static final long serialVersionUID = -200207173805584709L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java index 50d83cae..e99c6a5d 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandExecutionException.java @@ -32,6 +32,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * @since 1.2.0 */ +@SuppressWarnings("serial") public class CommandExecutionException extends IllegalArgumentException { private static final long serialVersionUID = -4785446899438294661L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandParseException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandParseException.java index 1feca25a..bd09fc7b 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandParseException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/CommandParseException.java @@ -31,7 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; /** * Exception thrown when parsing user input into a command */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "serial"}) public class CommandParseException extends IllegalArgumentException { private static final long serialVersionUID = -2415981126382517435L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/InvalidCommandSenderException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/InvalidCommandSenderException.java index 02e80fe3..c72b9e0c 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/InvalidCommandSenderException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/InvalidCommandSenderException.java @@ -32,6 +32,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** * Exception thrown when an invalid command sender tries to execute a command */ +@SuppressWarnings("serial") public final class InvalidCommandSenderException extends CommandParseException { private static final long serialVersionUID = 7372142477529875598L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/NoCommandInLeafException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/NoCommandInLeafException.java index 0b025f11..644c4875 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/NoCommandInLeafException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/NoCommandInLeafException.java @@ -31,7 +31,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; * Thrown when a {@link CommandArgument} * that is registered as a leaf node, does not contain an owning {@link Command} */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "serial"}) public final class NoCommandInLeafException extends IllegalStateException { private static final long serialVersionUID = 3373529875213310821L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/NoPermissionException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/NoPermissionException.java index 465e7822..0b502919 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/NoPermissionException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/NoPermissionException.java @@ -33,7 +33,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; * Exception thrown when a command sender misses a permission required * to execute a {@link Command} */ -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "serial"}) public class NoPermissionException extends CommandParseException { private static final long serialVersionUID = 7103413337750692843L; diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java index 65e0568a..9b684b3e 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java @@ -29,6 +29,7 @@ import cloud.commandframework.context.CommandContext; import java.util.Arrays; import org.checkerframework.checker.nullness.qual.NonNull; +@SuppressWarnings("serial") public class ParserException extends IllegalArgumentException { private static final long serialVersionUID = -4409795575435072170L; diff --git a/gradle/libs.versions.yml b/gradle/libs.versions.yml index 501e6097..edf63e57 100644 --- a/gradle/libs.versions.yml +++ b/gradle/libs.versions.yml @@ -52,6 +52,7 @@ versions: jupiterEngine : 5.8.2 mockitoCore : 4.1.0 mockitoKotlin : 4.0.0 + mockitoJupiter: 4.5.1 truth : 1.1.3 # build-logic @@ -206,6 +207,10 @@ dependencies: group: org.mockito.kotlin name: mockito-kotlin version: { ref: mockitoKotlin } + mockitoJupiter: + group: org.mockito + name: mockito-junit-jupiter + version: { ref: mockitoJupiter } truth: group: com.google.truth name: truth