From 16623969ad276aab3a9185a61b58e41d9a65f92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Fri, 9 Oct 2020 20:02:28 +0200 Subject: [PATCH] :broom: Clean up CommandManager --- .../commandframework/CommandManager.java | 174 ++++++++++++------ .../arguments/CommandSuggestionEngine.java | 51 +++++ .../DelegatingCommandSuggestionEngine.java | 88 +++++++++ ...egatingCommandSuggestionEngineFactory.java | 62 +++++++ .../internal/CommandInputTokenizer.java | 82 +++++++++ settings.gradle | 2 + 6 files changed, 403 insertions(+), 56 deletions(-) create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/CommandSuggestionEngine.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngineFactory.java create mode 100644 cloud-core/src/main/java/cloud/commandframework/internal/CommandInputTokenizer.java diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java index 7c49931e..aee8054e 100644 --- a/cloud-core/src/main/java/cloud/commandframework/CommandManager.java +++ b/cloud-core/src/main/java/cloud/commandframework/CommandManager.java @@ -24,7 +24,9 @@ package cloud.commandframework; import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.CommandSuggestionEngine; import cloud.commandframework.arguments.CommandSyntaxFormatter; +import cloud.commandframework.arguments.DelegatingCommandSuggestionEngineFactory; import cloud.commandframework.arguments.StandardCommandSyntaxFormatter; import cloud.commandframework.arguments.flags.CommandFlag; import cloud.commandframework.arguments.parser.ArgumentParser; @@ -44,6 +46,7 @@ import cloud.commandframework.execution.postprocessor.CommandPostprocessor; import cloud.commandframework.execution.preprocessor.AcceptingCommandPreprocessor; import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; import cloud.commandframework.execution.preprocessor.CommandPreprocessor; +import cloud.commandframework.internal.CommandInputTokenizer; import cloud.commandframework.internal.CommandRegistrationHandler; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.permission.CommandPermission; @@ -63,7 +66,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.StringTokenizer; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Function; @@ -75,8 +77,6 @@ import java.util.function.Function; */ public abstract class CommandManager { - private static final List SINGLE_EMPTY_SUGGESTION = Collections.unmodifiableList(Collections.singletonList("")); - private final Map, BiConsumer> exceptionHandlers = new HashMap<>(); private final EnumSet managerSettings = EnumSet.of( ManagerSettings.ENFORCE_INTERMEDIARY_PERMISSIONS); @@ -87,6 +87,7 @@ public abstract class CommandManager { private final Collection> commands = new LinkedList<>(); private final CommandExecutionCoordinator commandExecutionCoordinator; private final CommandTree commandTree; + private final CommandSuggestionEngine commandSuggestionEngine; private CommandSyntaxFormatter commandSyntaxFormatter = new StandardCommandSyntaxFormatter<>(); private CommandSuggestionProcessor commandSuggestionProcessor = new FilteringCommandSuggestionProcessor<>(); @@ -105,30 +106,13 @@ public abstract class CommandManager { this.commandTree = CommandTree.newTree(this); this.commandExecutionCoordinator = commandExecutionCoordinator.apply(commandTree); this.commandRegistrationHandler = commandRegistrationHandler; + this.commandSuggestionEngine = new DelegatingCommandSuggestionEngineFactory<>(this).create(); this.servicePipeline.registerServiceType(new TypeToken>() { }, new AcceptingCommandPreprocessor<>()); this.servicePipeline.registerServiceType(new TypeToken>() { }, new AcceptingCommandPostprocessor<>()); } - /** - * Tokenize an input string - * - * @param input Input string - * @return List of tokens - */ - public static @NonNull LinkedList<@NonNull String> tokenize(final @NonNull String input) { - final StringTokenizer stringTokenizer = new StringTokenizer(input, " "); - final LinkedList tokens = new LinkedList<>(); - while (stringTokenizer.hasMoreElements()) { - tokens.add(stringTokenizer.nextToken()); - } - if (input.endsWith(" ")) { - tokens.add(""); - } - return tokens; - } - /** * Execute a command and get a future that completes with the result * @@ -141,7 +125,7 @@ public abstract class CommandManager { final @NonNull String input ) { final CommandContext context = this.commandContextFactory.create(false, commandSender); - final LinkedList inputQueue = tokenize(input); + final LinkedList inputQueue = new CommandInputTokenizer(input).tokenize(); try { if (this.preprocessContext(context, inputQueue) == State.ACCEPTED) { return this.commandExecutionCoordinator.coordinateExecution(context, inputQueue); @@ -167,25 +151,11 @@ public abstract class CommandManager { final @NonNull C commandSender, final @NonNull String input ) { - final CommandContext context = this.commandContextFactory.create(true, commandSender); - final LinkedList inputQueue = tokenize(input); - - final List suggestions; - if (this.preprocessContext(context, inputQueue) == State.ACCEPTED) { - suggestions = this.commandSuggestionProcessor.apply( - new CommandPreprocessingContext<>(context, inputQueue), - this.commandTree.getSuggestions( - context, inputQueue) - ); - } else { - suggestions = Collections.emptyList(); - } - - if (this.getSetting(ManagerSettings.FORCE_SUGGESTION) && suggestions.isEmpty()) { - return SINGLE_EMPTY_SUGGESTION; - } - - return suggestions; + final CommandContext context = this.commandContextFactory.create( + true, + commandSender + ); + return this.commandSuggestionEngine.getSuggestions(context, input); } /** @@ -281,7 +251,14 @@ public abstract class CommandManager { public abstract boolean hasPermission(@NonNull C sender, @NonNull String permission); /** - * Create a new command builder + * Create a new command builder. This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param aliases Command aliases @@ -295,11 +272,25 @@ public abstract class CommandManager { final @NonNull Description description, final @NonNull CommandMeta meta ) { - return Command.newBuilder(name, meta, description, aliases.toArray(new String[0])).manager(this); + return Command.newBuilder( + name, + meta, + description, + aliases.toArray(new String[0]) + ).manager(this); } /** - * Create a new command builder + * Create a new command builder with an empty description. + *

+ * This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param aliases Command aliases @@ -311,11 +302,23 @@ public abstract class CommandManager { final @NonNull Collection aliases, final @NonNull CommandMeta meta ) { - return Command.newBuilder(name, meta, Description.empty(), aliases.toArray(new String[0])).manager(this); + return Command.newBuilder( + name, + meta, + Description.empty(), + aliases.toArray(new String[0]) + ).manager(this); } /** - * Create a new command builder + * Create a new command builder. This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param meta Command meta @@ -329,11 +332,25 @@ public abstract class CommandManager { final @NonNull Description description, final @NonNull String... aliases ) { - return Command.newBuilder(name, meta, description, aliases).manager(this); + return Command.newBuilder( + name, + meta, + description, + aliases + ).manager(this); } /** - * Create a new command builder + * Create a new command builder with an empty description. + *

+ * This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param meta Command meta @@ -345,11 +362,25 @@ public abstract class CommandManager { final @NonNull CommandMeta meta, final @NonNull String... aliases ) { - return Command.newBuilder(name, meta, Description.empty(), aliases).manager(this); + return Command.newBuilder( + name, + meta, + Description.empty(), + aliases + ).manager(this); } /** - * Create a new command builder using a default command meta instance. + * Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}. + *

+ * This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param description Command description @@ -363,11 +394,26 @@ public abstract class CommandManager { final @NonNull Description description, final @NonNull String... aliases ) { - return Command.newBuilder(name, this.createDefaultCommandMeta(), description, aliases).manager(this); + return Command.newBuilder( + name, + this.createDefaultCommandMeta(), + description, + aliases + ).manager(this); } /** - * Create a new command builder using a default command meta instance. + * Create a new command builder using default command meta created by {@link #createDefaultCommandMeta()}, and + * an empty description. + *

+ * This will also register the creating manager in the command + * builder using {@link Command.Builder#manager(CommandManager)}, so that the command + * builder is associated with the creating manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry} + *

+ * This method will not register the command in the manager. To do that, {@link #command(Command.Builder)} + * or {@link #command(Command)} has to be invoked with either the {@link Command.Builder} instance, or the constructed + * {@link Command command} instance * * @param name Command name * @param aliases Command aliases @@ -379,11 +425,20 @@ public abstract class CommandManager { final @NonNull String name, final @NonNull String... aliases ) { - return Command.newBuilder(name, this.createDefaultCommandMeta(), Description.empty(), aliases).manager(this); + return Command.newBuilder( + name, + this.createDefaultCommandMeta(), + Description.empty(), + aliases + ).manager(this); } /** - * Create a new command argument builder + * Create a new command argument builder. + *

+ * This will also invoke {@link CommandArgument.Builder#manager(CommandManager)} + * so that the argument is associated with the calling command manager. This allows for parser inference based on + * the type, with the help of the {@link ParserRegistry parser registry}. * * @param type Argument type * @param name Argument name @@ -433,8 +488,10 @@ public abstract class CommandManager { * @see #preprocessContext(CommandContext, LinkedList) Preprocess a context */ public void registerCommandPreProcessor(final @NonNull CommandPreprocessor processor) { - this.servicePipeline.registerServiceImplementation(new TypeToken>() { - }, processor, + this.servicePipeline.registerServiceImplementation( + new TypeToken>() { + }, + processor, Collections.emptyList() ); } @@ -612,6 +669,7 @@ public abstract class CommandManager { * * @param setting Setting * @return {@code true} if the setting is activated or {@code false} if it's not + * @see #setSetting(ManagerSettings, boolean) Update a manager setting */ public boolean getSetting(final @NonNull ManagerSettings setting) { return this.managerSettings.contains(setting); @@ -622,6 +680,7 @@ public abstract class CommandManager { * * @param setting Setting to set * @param value Value + * @see #getSetting(ManagerSettings) Get a manager setting */ @SuppressWarnings("unused") public void setSetting( @@ -638,6 +697,9 @@ public abstract class CommandManager { /** * Configurable command related settings + * + * @see CommandManager#setSetting(ManagerSettings, boolean) Set a manager setting + * @see CommandManager#getSetting(ManagerSettings) Get a manager setting */ public enum ManagerSettings { /** diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/CommandSuggestionEngine.java b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandSuggestionEngine.java new file mode 100644 index 00000000..7de39901 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/CommandSuggestionEngine.java @@ -0,0 +1,51 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.arguments; + +import cloud.commandframework.context.CommandContext; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +/** + * Handler that produces command suggestions depending on input + * + * @param Command sender type + */ +public interface CommandSuggestionEngine { + + /** + * Get command suggestions for the "next" argument that would yield a correctly + * parsing command input + * + * @param context Request context + * @param input Input provided by the sender + * @return List of suggestions + */ + @NonNull List<@NonNull String> getSuggestions( + @NonNull CommandContext context, + @NonNull String input + ); + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java new file mode 100644 index 00000000..1041dced --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngine.java @@ -0,0 +1,88 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.arguments; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.CommandTree; +import cloud.commandframework.context.CommandContext; +import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext; +import cloud.commandframework.internal.CommandInputTokenizer; +import cloud.commandframework.services.State; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Command suggestion engine that delegates to a {@link cloud.commandframework.CommandTree} + * + * @param Command sender type + */ +public final class DelegatingCommandSuggestionEngine implements CommandSuggestionEngine { + + private static final List SINGLE_EMPTY_SUGGESTION = Collections.unmodifiableList(Collections.singletonList("")); + + private final CommandManager commandManager; + private final CommandTree commandTree; + + /** + * Create a new delegating command suggestion engine + * + * @param commandManager Command manager + * @param commandTree Command tree + */ + DelegatingCommandSuggestionEngine( + final @NonNull CommandManager commandManager, + final @NonNull CommandTree commandTree + ) { + this.commandManager = commandManager; + this.commandTree = commandTree; + } + + @Override + public @NonNull List<@NonNull String> getSuggestions( + @NonNull final CommandContext context, + @NonNull final String input + ) { + final @NonNull LinkedList<@NonNull String> inputQueue = new CommandInputTokenizer(input).tokenize(); + final List suggestions; + if (this.commandManager.preprocessContext(context, inputQueue) == State.ACCEPTED) { + suggestions = this.commandManager.getCommandSuggestionProcessor().apply( + new CommandPreprocessingContext<>(context, inputQueue), + this.commandTree.getSuggestions( + context, + inputQueue + ) + ); + } else { + suggestions = Collections.emptyList(); + } + if (this.commandManager.getSetting(CommandManager.ManagerSettings.FORCE_SUGGESTION) && suggestions.isEmpty()) { + return SINGLE_EMPTY_SUGGESTION; + } + return suggestions; + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngineFactory.java b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngineFactory.java new file mode 100644 index 00000000..0bc6d283 --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/arguments/DelegatingCommandSuggestionEngineFactory.java @@ -0,0 +1,62 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.arguments; + +import cloud.commandframework.CommandManager; +import cloud.commandframework.CommandTree; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Factory that produces {@link DelegatingCommandSuggestionEngine} instances + * + * @param Command sender type + */ +public final class DelegatingCommandSuggestionEngineFactory { + + private final CommandManager commandManager; + private final CommandTree commandTree; + + /** + * Create a new delegating command suggestion engine factory + * + * @param commandManager Command manager + */ + public DelegatingCommandSuggestionEngineFactory(final @NonNull CommandManager commandManager) { + this.commandManager = commandManager; + this.commandTree = commandManager.getCommandTree(); + } + + /** + * Create the engine instance + * + * @return New engine instance + */ + public @NonNull DelegatingCommandSuggestionEngine create() { + return new DelegatingCommandSuggestionEngine<>( + this.commandManager, + this.commandTree + ); + } + +} diff --git a/cloud-core/src/main/java/cloud/commandframework/internal/CommandInputTokenizer.java b/cloud-core/src/main/java/cloud/commandframework/internal/CommandInputTokenizer.java new file mode 100644 index 00000000..5ab2494e --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/internal/CommandInputTokenizer.java @@ -0,0 +1,82 @@ +// +// 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.internal; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.LinkedList; +import java.util.StringTokenizer; + +/** + * Tokenizer that splits command inputs into tokens. This will split the string + * at every blank space. If the input string ends with a blank space, a trailing + * empty string will be added to the token list + */ +public final class CommandInputTokenizer { + + private static final String DELIMITER = " "; + private static final String EMPTY = ""; + + private final StringTokenizerFactory stringTokenizerFactory = new StringTokenizerFactory(); + private final String input; + + /** + * Create a new input tokenizer + * + * @param input Input that is to be turned into tokens + */ + public CommandInputTokenizer(final @NonNull String input) { + this.input = input; + } + + /** + * Turn the input into tokens + * + * @return Linked list containing the tokenized input + */ + public @NonNull LinkedList<@NonNull String> tokenize() { + final StringTokenizer stringTokenizer = stringTokenizerFactory.createStringTokenizer(); + final LinkedList tokens = new LinkedList<>(); + while (stringTokenizer.hasMoreElements()) { + tokens.add(stringTokenizer.nextToken()); + } + if (input.endsWith(DELIMITER)) { + tokens.add(EMPTY); + } + return tokens; + } + + + /** + * Factory class that creates {@link StringTokenizer} instances + */ + private final class StringTokenizerFactory { + + private @NonNull StringTokenizer createStringTokenizer() { + return new StringTokenizer(input, DELIMITER); + } + + } + +} diff --git a/settings.gradle b/settings.gradle index b8eaf0fc..4c1ffa9a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,6 +14,7 @@ include(':cloud-javacord') include(':cloud-jda') include(':example-bukkit') include(':cloud-tasks') +include(':cloud-sponge') project(':cloud-bukkit').projectDir = file('cloud-minecraft/cloud-bukkit') project(':cloud-paper').projectDir = file('cloud-minecraft/cloud-paper') project(':cloud-brigadier').projectDir = file('cloud-minecraft/cloud-brigadier') @@ -24,3 +25,4 @@ project(':cloud-cloudburst').projectDir = file('cloud-minecraft/cloud-cloudburst project(':cloud-javacord').projectDir = file('cloud-discord/cloud-javacord') project(':cloud-jda').projectDir = file('cloud-discord/cloud-jda') project(':example-bukkit').projectDir = file('examples/example-bukkit') +project(':cloud-sponge').projectDir = file('cloud-minecraft/cloud-sponge')