diff --git a/.github/workflows/publish.yml b/.github/workflows/build-and-publish.yml similarity index 59% rename from .github/workflows/publish.yml rename to .github/workflows/build-and-publish.yml index eb18b0c8..088ac21c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -1,12 +1,15 @@ -name: Publish to GitHub Packages +name: Build and upload on: + push: + pull_request: + types: + - opened + - reopened + workflow_dispatch: release: types: - - published - push: - branches: - - new-command-system-1.20.6 + - created env: USERNAME: ${{ github.actor }} @@ -17,13 +20,12 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: write - packages: write + contents: read steps: - name: Get current date run: echo "WLIB_BUILD_DATE=$(date +'%Y-%m-%d_%H-%M-%S')" >> "$GITHUB_ENV" - name: Get snapshot number - run: echo "WLIB_SNAPSHOT_NUMBER=$((${{ github.run_number }}+${{ github.run_attempt }}))" >> $GITHUB_ENV + run: echo "WLIB_SNAPSHOT_NUMBER=$((${{ github.run_number }}+${{ github.run_attempt }}+68))" >> $GITHUB_ENV - uses: actions/checkout@v4 - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -48,5 +50,27 @@ jobs: for file in */build/libs/*.jar; do gh release upload ${{ github.event.release.tag_name }} $file done - - name: Publish + publish: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + if: ${{ github.event_name != 'pull_request' }} + needs: + - build + steps: + - name: Get current date + run: echo "WLIB_BUILD_DATE=$(date +'%Y-%m-%d_%H-%M-%S')" >> "$GITHUB_ENV" + - name: Get snapshot number + run: echo "WLIB_SNAPSHOT_NUMBER=$((${{ github.run_number }}+${{ github.run_attempt }}+68))" >> $GITHUB_ENV + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: temurin + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Clean, Build and Publish run: ./gradlew publish + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index aae6a066..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build and upload - -on: - push: - pull_request: - types: - - opened - - reopened - workflow_dispatch: - -env: - USERNAME: ${{ github.actor }} - TOKEN: ${{ secrets.GITHUB_TOKEN }} - WLIB_SNAPSHOT: ${{ github.event_name != 'release' }} - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Get current date - run: echo "WLIB_BUILD_DATE=$(date +'%Y-%m-%d_%H-%M-%S')" >> "$GITHUB_ENV" - - name: Get snapshot number - run: echo "WLIB_SNAPSHOT_NUMBER=$((${{ github.run_number }}+${{ github.run_attempt }}))" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: 21 - distribution: temurin - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - name: Build - run: ./gradlew build - - name: Upload results - uses: actions/upload-artifact@v4 - with: - path: "**/build/libs/*.jar" - name: WLib-${{ env.WLIB_BUILD_DATE }} - if-no-files-found: error \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0e100001..9137fbef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **/build/ .idea/ .gradle/ +/build.properties diff --git a/build.gradle b/build.gradle index 014ee24d..14cae67c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { - id("com.gradleup.shadow") version "8.3.2" + id("com.gradleup.shadow") version "9.2.2" id 'maven-publish' + id("systems.manifold.manifold-gradle-plugin") version "0.0.2-alpha" apply false } subprojects { @@ -9,7 +10,7 @@ subprojects { apply plugin: 'maven-publish' group = 'com.wizardlybump17.wlib' - version = "1.6.8-ncs-1.20.6+" + ("true".equalsIgnoreCase(System.getenv("WLIB_SNAPSHOT")) ? "-SNAPSHOT." + System.getenv("WLIB_SNAPSHOT_NUMBER") : "") + version = "1.6.8-ncs-2-1.20.6+" + ("true".equalsIgnoreCase(System.getenv("WLIB_SNAPSHOT")) ? "-SNAPSHOT." + System.getenv("WLIB_SNAPSHOT_NUMBER") : "") repositories { mavenLocal() @@ -20,9 +21,6 @@ subprojects { } } - sourceCompatibility = '21' - targetCompatibility = '21' - tasks { compileJava { options.encoding = 'UTF-8' @@ -38,26 +36,22 @@ subprojects { java { withSourcesJar() + sourceCompatibility = 21 + targetCompatibility = 21 } } subprojects { dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.3")) - testImplementation("org.junit.jupiter:junit-jupiter:5.10.3") + testImplementation(platform("org.junit:junit-bom:6.0.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.junit.platform:junit-platform-launcher") } test { useJUnitPlatform() } - if (project.name == 'versions' || project.name.matches('v\\d_\\d+_R\\d+')) { - tasks.withType(PublishToMavenRepository).configureEach { - it.enabled = false - } - return - } - publishing { repositories { maven { @@ -72,23 +66,10 @@ subprojects { publications { gpr(MavenPublication) { from(components.java) - pom.withXml { - asNode().dependencies.dependency.each { dep -> - if (dep.artifactId.last().value().last().matches('v\\d_\\d+_R\\d+')) - dep.parent().remove(dep) - } - } } } } - dependencies { - testImplementation( - platform('org.junit:junit-bom:5.10.2'), - 'org.junit.jupiter:junit-jupiter:5.10.2' - ) - } - test { useJUnitPlatform() testLogging { diff --git a/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/MiniMessageUtil.java b/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/MiniMessageUtil.java index 6cbc701a..c0157604 100644 --- a/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/MiniMessageUtil.java +++ b/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/MiniMessageUtil.java @@ -1,15 +1,22 @@ package com.wizardlybump17.wlib.util.bukkit; +import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; +import com.wizardlybump17.wlib.util.StringUtil; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Map; public final class MiniMessageUtil { + private static final @NotNull TextComponent NULL = Component.text("null"); + private MiniMessageUtil() { } @@ -21,18 +28,60 @@ private MiniMessageUtil() { TagResolver[] resolvers = new TagResolver[placeholders.size()]; int resolverIndex = 0; for (Map.Entry entry : placeholders.entrySet()) { - String key = entry.getKey(); + String key = StringUtil.pascalToCamel(entry.getKey()); Object value = entry.getValue(); resolvers[resolverIndex++] = TagResolver.builder() .tag( key, - Tag.inserting(value instanceof Component component ? component : Component.text(String.valueOf(value))) + Tag.inserting(getInsertion(value)) + ) + .build(); + } + return miniMessage.deserialize(message, resolvers); + } + + public static @NotNull Component getMessage(@NotNull String message, @NotNull String placeholderKey, @NotNull Object placeholderValue, @NotNull String prefix, @NotNull Map additionalPlaceholders) { + MiniMessage miniMessage = MiniMessage.miniMessage(); + if (additionalPlaceholders.isEmpty()) + return getMessage(message, Map.of(placeholderKey, placeholderValue)); + + TagResolver[] resolvers = new TagResolver[additionalPlaceholders.size() + 1]; + int resolverIndex = 0; + for (Map.Entry entry : additionalPlaceholders.entrySet()) { + String key = StringUtil.pascalToCamel(entry.getKey()); + Object value = entry.getValue(); + resolvers[resolverIndex++] = TagResolver.builder() + .tag( + prefix + key, + Tag.inserting(getInsertion(value)) ) .build(); } + resolvers[resolverIndex] = TagResolver.builder() + .tag( + prefix + placeholderKey, + Tag.inserting(getInsertion(placeholderValue)) + ) + .build(); return miniMessage.deserialize(message, resolvers); } + private static @NotNull Component getInsertion(@Nullable Object object) { + if (object == null) + return NULL; + + return switch (object) { + case Component component -> component; + case JsonPrimitive jsonPrimitive -> Component.text(jsonPrimitive.getAsString()); + case JsonNull ignored -> NULL; + default -> Component.text(String.valueOf(object)); + }; + } + + public static @NotNull Component getMessage(@NotNull String message, @NotNull String placeholderKey, @NotNull Object placeholderValue, @NotNull Map additionalPlaceholders) { + return getMessage(message, placeholderKey, placeholderValue, "", additionalPlaceholders); + } + public static @NotNull Component getMessage(@NotNull String message) { return getMessage(message, Map.of()); } diff --git a/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/collector/ComponentCollector.java b/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/collector/ComponentCollector.java new file mode 100644 index 00000000..efd535a2 --- /dev/null +++ b/bukkit-utils/src/main/java/com/wizardlybump17/wlib/util/bukkit/collector/ComponentCollector.java @@ -0,0 +1,65 @@ +package com.wizardlybump17.wlib.util.bukkit.collector; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +public class ComponentCollector implements Collector { + + public static final @NotNull ComponentCollector COMMA = new ComponentCollector(Component.text(", ")); + public static final @NotNull ComponentCollector NEW_LINE = new ComponentCollector(Component.text("\n")); + public static final @NotNull ComponentCollector EMPTY = new ComponentCollector(Component.empty()); + + private final @NotNull Component separator; + + public ComponentCollector(@NotNull Component separator) { + this.separator = separator; + } + + @Override + public @NotNull Supplier supplier() { + return () -> new Component[1]; + } + + @Override + public @NotNull BiConsumer accumulator() { + return (array, component) -> { + if (array[0] == null) + array[0] = component; + else + array[0] = array[0].append(separator).append(component); + }; + } + + @Override + public @NotNull BinaryOperator combiner() { + return (left, right) -> { + if (left[0] == null) + left[0] = right[0]; + else + left[0] = left[0].append(separator).append(right[0]); + return left; + }; + } + + @Override + public @NotNull Function finisher() { + return array -> Objects.requireNonNullElseGet(array[0], Component::empty); + } + + @Override + public @NotNull Set characteristics() { + return Set.of(); + } + + public @NotNull Component getSeparator() { + return separator; + } +} diff --git a/bungee/build.gradle b/bungee/build.gradle index 9b89a640..713c008e 100644 --- a/bungee/build.gradle +++ b/bungee/build.gradle @@ -8,7 +8,7 @@ dependencies { compileOnly( 'org.projectlombok:lombok:1.18.32', 'org.jetbrains:annotations:23.0.0', - 'net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT', + 'net.md-5:bungeecord-api:1.21-R0.3', ) annotationProcessor('org.projectlombok:lombok:1.18.32') implementation( diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommand.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommand.java deleted file mode 100644 index 494572cd..00000000 --- a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.wizardlybump17.wlib.bungee.command; - -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.holder.Command; -import com.wizardlybump17.wlib.command.holder.CommandExecutor; - -public class BungeeCommand implements Command { - - private CommandExecutor executor; - - @Override - public void setExecutor(CommandExecutor executor) { - this.executor = executor; - } - - @Override - public CommandExecutor getExecutor() { - return executor; - } - - @Override - public CommandExecutor getDefaultExecutor(CommandManager manager, String name) { - return new BungeeCommandExecutor(manager, name); - } -} diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandExecutor.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandExecutor.java deleted file mode 100644 index cca749bd..00000000 --- a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandExecutor.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.wizardlybump17.wlib.bungee.command; - -import com.wizardlybump17.wlib.bungee.command.sender.BungeeCommandSender; -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.holder.CommandExecutor; -import net.md_5.bungee.api.plugin.Command; - -import java.util.logging.Level; - -public class BungeeCommandExecutor extends Command implements CommandExecutor { - - private final CommandManager manager; - - public BungeeCommandExecutor(CommandManager manager, String name) { - super(name); - this.manager = manager; - } - - @Override - public void execute(com.wizardlybump17.wlib.command.sender.CommandSender sender, String commandName, String[] args) throws CommandException { - manager.execute(sender, commandName + " " + String.join(" ", args)); - } - - @Override - public void execute(net.md_5.bungee.api.CommandSender sender, String[] args) { - try { - execute(new BungeeCommandSender(sender), getName(), args); - } catch (CommandException e) { - manager.getHolder().getLogger().log(Level.SEVERE, "Error while executing a command", e); - } - } -} diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandHolder.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandHolder.java deleted file mode 100644 index 878d093c..00000000 --- a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandHolder.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.wizardlybump17.wlib.bungee.command; - -import com.wizardlybump17.wlib.command.holder.Command; -import com.wizardlybump17.wlib.command.holder.CommandHolder; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.md_5.bungee.api.plugin.Plugin; - -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -@RequiredArgsConstructor -public class BungeeCommandHolder implements CommandHolder { - - private final Plugin plugin; - private final Map commands = new HashMap<>(); - - @Override - public Command getCommand(String name) { - if (commands.containsKey(name)) - return commands.get(name); - BungeeCommand command = new BungeeCommand(); - commands.put(name, command); - return command; - } - - @Override - public Plugin getHandle() { - return plugin; - } - - @Override - public @NonNull Logger getLogger() { - return plugin.getLogger(); - } -} diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandManager.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandManager.java deleted file mode 100644 index bfd1bafa..00000000 --- a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/BungeeCommandManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.wizardlybump17.wlib.bungee.command; - -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.registered.RegisteredCommand; -import net.md_5.bungee.api.plugin.Command; -import net.md_5.bungee.api.plugin.Plugin; -import org.jetbrains.annotations.NotNull; - -public class BungeeCommandManager extends CommandManager { - - public BungeeCommandManager(BungeeCommandHolder holder) { - super(holder); - } - - @Override - public void registerCommands(@NotNull Object @NotNull ... objects) { - super.registerCommands(objects); - for (Object object : objects) { - for (RegisteredCommand command : getCommands(object)) { - Plugin plugin = (Plugin) holder.getHandle(); - plugin.getProxy().getPluginManager().registerCommand(plugin, (Command) holder.getCommand(command.getName()).getExecutor()); - } - } - } -} diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/sender/BungeeCommandSender.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/sender/BungeeCommandSender.java index 702f31eb..f211ce88 100644 --- a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/sender/BungeeCommandSender.java +++ b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/command/sender/BungeeCommandSender.java @@ -1,11 +1,16 @@ package com.wizardlybump17.wlib.bungee.command.sender; +import com.wizardlybump17.wlib.bungee.util.collector.ComponentCollector; import lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ConnectedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @RequiredArgsConstructor @@ -28,6 +33,35 @@ public void sendMessage(String... messages) { handle.sendMessage(TextComponent.fromLegacyText(String.join("\n", messages))); } + @Override + public void sendMessage(@Nullable Object message) { + switch (message) { + case null -> handle.sendMessage(new TextComponent("null")); + case BaseComponent component -> handle.sendMessage(component); + default -> handle.sendMessage(new TextComponent(String.valueOf(message))); + } + } + + @Override + public void sendMessage(@Nullable Object @Nullable ... messages) { + if (messages == null) { + sendMessage((Object) null); + return; + } + + List components = new ArrayList<>(messages.length); + for (Object message : messages) { + switch (message) { + case null -> components.add(new TextComponent("null")); + case BaseComponent component -> components.add(component); + case String string -> components.add(new TextComponent(string)); + default -> components.add(new TextComponent(String.valueOf(message))); + } + } + + handle.sendMessage(components.stream().collect(ComponentCollector.NEW_LINE)); + } + @Override public String getName() { return handle.getName(); diff --git a/bungee/src/main/java/com/wizardlybump17/wlib/bungee/util/collector/ComponentCollector.java b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/util/collector/ComponentCollector.java new file mode 100644 index 00000000..55c83b05 --- /dev/null +++ b/bungee/src/main/java/com/wizardlybump17/wlib/bungee/util/collector/ComponentCollector.java @@ -0,0 +1,72 @@ +package com.wizardlybump17.wlib.bungee.util.collector; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +public class ComponentCollector implements Collector { + + public static final @NotNull ComponentCollector COMMA = new ComponentCollector(new TextComponent(", ")); + public static final @NotNull ComponentCollector NEW_LINE = new ComponentCollector(new TextComponent("\n")); + public static final @NotNull ComponentCollector EMPTY = new ComponentCollector(new TextComponent()); + + private final @NotNull BaseComponent separator; + + public ComponentCollector(@NotNull BaseComponent separator) { + this.separator = separator; + } + + @Override + public @NotNull Supplier supplier() { + return () -> new BaseComponent[1]; + } + + @Override + public @NotNull BiConsumer accumulator() { + return (array, component) -> { + if (array[0] == null) + array[0] = component.duplicate(); + else { + array[0] = array[0].duplicate(); + array[0].addExtra(separator); + array[0].addExtra(component); + } + }; + } + + @Override + public @NotNull BinaryOperator combiner() { + return (left, right) -> { + if (left[0] == null) + left[0] = right[0].duplicate(); + else { + left[0] = left[0].duplicate(); + left[0].addExtra(separator); + left[0].addExtra(right[0].duplicate()); + } + return left; + }; + } + + @Override + public @NotNull Function finisher() { + return array -> Objects.requireNonNullElseGet(array[0], TextComponent::new); + } + + @Override + public @NotNull Set characteristics() { + return Set.of(); + } + + public @NotNull BaseComponent getSeparator() { + return separator.duplicate(); + } +} diff --git a/commands/build.gradle b/commands/build.gradle deleted file mode 100644 index 59d9b615..00000000 --- a/commands/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -dependencies { - compileOnly( - 'org.projectlombok:lombok:1.18.32', - 'org.jetbrains:annotations:23.0.0', - ) - annotationProcessor('org.projectlombok:lombok:1.18.32') - implementation( - project(':objects'), - project(':utils') - ) - implementation("com.github.sisyphsu:dateparser:1.0.11") { - exclude(group: "org.projectlombok", module: "lombok") - } - testCompileOnly("org.jetbrains:annotations:26.0.1") -} - -shadowJar { - relocate("com.github.sisyphsu", "com.wizardlybump17.wlib.libs.com.github.sisyphsu") -} diff --git a/commands/build.gradle.kts b/commands/build.gradle.kts new file mode 100644 index 00000000..10b877a5 --- /dev/null +++ b/commands/build.gradle.kts @@ -0,0 +1,12 @@ +val annotations = "26.0.1" +val gson = "2.13.2" + +dependencies { + implementation("org.jetbrains:annotations:${annotations}") + implementation(project(":objects")) + implementation(project(":utils")) + + implementation("com.google.code.gson:gson:${gson}") + + testImplementation("org.jetbrains:annotations:${annotations}") +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/Argument.java b/commands/src/main/java/com/wizardlybump17/wlib/command/Argument.java deleted file mode 100644 index 71416e57..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/Argument.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command; - -import lombok.AccessLevel; -import lombok.Data; -import lombok.Getter; - -/** - * Class used in the arguments to represent a command argument. - *

It stores useful information like the argument name and the user input

- * @param the argument type - */ -@Data -public class Argument { - - private final String name; - @Getter(AccessLevel.NONE) - private final T value; - private final String input; - - public T value() { - return value; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/Command.java b/commands/src/main/java/com/wizardlybump17/wlib/command/Command.java new file mode 100644 index 00000000..1980bd62 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/Command.java @@ -0,0 +1,277 @@ +package com.wizardlybump17.wlib.command; + +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.exception.InvalidInputException; +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import com.wizardlybump17.wlib.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public class Command implements Comparable { + + public static final @NotNull Comparator COMPARATOR = Comparator + .comparing(Command::getFullCommand); + + private final @NotNull LiteralCommandNode root; + + public Command(@NotNull LiteralCommandNode root) { + this.root = root; + } + + public @NotNull LiteralCommandNode getRoot() { + return root; + } + + public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String input) { + List inputList = getInputList(input); + return execute(sender, inputList); + } + + @SuppressWarnings("unchecked") + public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull List input) { + if (input.isEmpty()) + return CommandResult.insufficientArguments(this); + + List> arguments = new ArrayList<>(); + List> children = List.of(root); + + CommandNode lastNode = null; + int lastInputIndex = 0; + InputParsingException lastParsingError = null; + InvalidInputException lastInputError = null; + + inputLoop: for (int i = 0; i < input.size(); i++) { + String inputString = input.get(i); + lastInputIndex = i; + + for (CommandNode child : children) { + lastNode = child; + + try { + Object result; + if (inputString == null) { + if (child.isValidInput(null)) + result = null; + else + throw new InvalidInputException("Null inputs not accepted by " + child); + } else { + result = child.parseOrInvalid(inputString); + } + + if (!(child instanceof LiteralCommandNode)) + arguments.add(new CommandContext.CommandNodeArgument<>((CommandNode) child, inputString, result)); + + children = child.getChildren(); + + lastParsingError = null; + lastInputError = null; + + continue inputLoop; + } catch (InputParsingException e) { + lastParsingError = e; + } catch (InvalidInputException e) { + lastInputError = e; + } + } + + if (lastParsingError != null) + return CommandResult.parseInputException(lastInputIndex, lastNode, lastParsingError); + if (lastInputError != null) + return CommandResult.outOfRangeInput(lastInputIndex, lastNode); + + return CommandResult.extraArguments(lastInputIndex, lastNode); + } + + CommandNodeExecutor executor = lastNode.getExecutor(); + if (executor == null) + return CommandResult.noCommandNodeExecutor(lastInputIndex, lastNode); + + String nodePermission = lastNode.getPermission(); + if (nodePermission != null && !sender.hasPermission(nodePermission)) + return CommandResult.noPermission(lastInputIndex, lastNode); + + CommandContext context = new CommandContext( + this, + sender, + new CommandContext.CommandNodeArguments(arguments), + lastInputIndex, + lastNode + ); + + try { + CommandResult result = executor.execute(context); + if (result == null) + return CommandResult.exceptionally(lastInputIndex, lastNode, new NullPointerException("The CommandResult can not be null")); + if (result.lastNode() == null) + return CommandResult.genericError(lastInputIndex, lastNode, "The last node can not be null"); + if (result.lastInputIndex() < 0) + return CommandResult.genericError(lastInputIndex, lastNode, "The last input index can not be less than 0"); + return result; + } catch (Throwable throwable) { + return CommandResult.exceptionally(lastInputIndex, lastNode, throwable); + } + } + + public @NotNull List getInputList(@NotNull String original) { + return StringUtil.parseQuotedStrings(original); + } + + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input) throws SuggesterException { + if (input.isEmpty()) + return List.of(root.getName()); + + String currentInput = input.getLast(); + + List suggestions = new ArrayList<>(); + List> children = List.of(root); + + CommandNode lastNode = null; + Throwable lastError = null; + + inputLoop: for (int i = 0; i < input.size(); i++) { + String inputString = input.get(i); + boolean isLastInput = i == input.size() - 1; + + if (isLastInput && inputString != null && inputString.isEmpty()) { + for (CommandNode child : children) { + String permission = child.getPermission(); + if (permission == null || sender.hasPermission(permission)) + suggestions.addAll(getSuggestions0(child, sender, input, "")); + } + if (!children.isEmpty()) + lastNode = children.getLast(); + break; + } + + boolean foundNode = !children.isEmpty(); + for (CommandNode child : children) { + lastNode = child; + + if (!isLastInput) { + try { + if (inputString == null) { + if (!child.isValidInput(null)) + throw new InvalidInputException("Null inputs not accepted by " + child); + continue; + } + + child.parseOrInvalid(inputString); + } catch (InputParsingException | InvalidInputException e) { + lastError = e; + foundNode = false; + continue; + } + } + + lastError = null; + foundNode = true; + + String permission = child.getPermission(); + if (isLastInput && (permission == null || sender.hasPermission(permission))) + suggestions.addAll(getSuggestions0(child, sender, input, currentInput)); + + if (isLastInput) { + continue; + } else { + children = child.getChildren(); + continue inputLoop; + } + } + + if (lastError != null || !foundNode) + return List.of(); + } + + if (lastNode == null) + return List.of(); + + return suggestions; + } + + @SuppressWarnings("unchecked") + private static @NotNull List getSuggestions0(@NotNull CommandNode node, @NotNull CommandSender sender, @NotNull List input, @NotNull String currentInput) throws SuggesterException { + List childSuggestions = node.getSuggestions(sender, input, currentInput); + Suggester suggester = (Suggester) node.getSuggester(); + + if (suggester == null) { + return childSuggestions + .stream() + .map(Object::toString) + .toList(); + } else { + Stream stream = childSuggestions + .stream() + .map(suggester::getStringRepresentation); + if (suggester.needsEscape()) + stream = stream.map(StringUtil::escapeString); + return stream.toList(); + } + } + + public @NotNull Command merge(@NotNull Command other) { + if (other.getClass() != getClass()) + return other.merge(this); + return new Command(root.merge(other.getRoot())); + } + + @Override + public String toString() { + return "Command{" + + "root=" + root + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Command command = (Command) o; + return Objects.equals(root, command.root); + } + + public boolean equalsIgnoreExecutor(@Nullable Object other) { + if (other == null || getClass() != other.getClass()) + return false; + Command command = (Command) other; + return root.equalsIgnoreExecutor(command.root); + } + + @Override + public int hashCode() { + return Objects.hashCode(root); + } + + @Override + public int compareTo(@NotNull Command other) { + return COMPARATOR.compare(this, other); + } + + public @NotNull String getFullCommand() { + return root.getFullCommand(); + } + + public @Nullable CommandNode findNode(@NotNull String name) { + return root.findChild(name); + } + + public @NotNull String getName() { + return root.getName(); + } + + public int getTotalNodes() { + return root.getTotalNodes(); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/CommandManager.java b/commands/src/main/java/com/wizardlybump17/wlib/command/CommandManager.java deleted file mode 100644 index 08bff110..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/CommandManager.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.wizardlybump17.wlib.command; - -import com.wizardlybump17.wlib.command.data.CommandData; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.extractor.CommandExtractor; -import com.wizardlybump17.wlib.command.extractor.DirectCommandExtractor; -import com.wizardlybump17.wlib.command.extractor.MethodCommandExtractor; -import com.wizardlybump17.wlib.command.holder.CommandHolder; -import com.wizardlybump17.wlib.command.registered.RegisteredCommand; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -public class CommandManager { - - private final @NotNull Map> commands = new HashMap<>(); - protected final @NotNull CommandHolder holder; - private final @NotNull Set commandExtractors = new HashSet<>(); - - public CommandManager(@NotNull CommandHolder holder) { - this.holder = holder; - commandExtractors.add(new MethodCommandExtractor()); - commandExtractors.add(new DirectCommandExtractor()); - } - - public void registerCommands(@NotNull Object @NotNull ... objects) { - for (Object object : objects) - for (CommandExtractor extractor : commandExtractors) - registerCommands(extractor.extract(this, holder, object)); - } - - public void registerCommands(@NotNull Collection commands) { - commands.forEach(this::registerCommand); - } - - public void registerCommand(@NotNull RegisteredCommand command) { - command.onRegister(this); - List commands = this.commands.computeIfAbsent(command.getName().toLowerCase(), $ -> new ArrayList<>()); - commands.add(command); - commands.sort(null); - } - - public void unregisterCommands() { - commands.forEach((name, commands) -> { - commands.forEach(command -> command.onUnregister(this)); - commands.clear(); - }); - commands.clear(); - } - - public void execute(@NotNull CommandSender sender, @NotNull String string) throws CommandException { - if (commands.isEmpty()) - return; - - int spaceIndex = string.indexOf(' '); - String name; - if (spaceIndex == -1) - name = string; - else - name = string.substring(0, spaceIndex); - - for (RegisteredCommand registeredCommand : commands.getOrDefault(name, List.of())) { - CommandData command = registeredCommand.getCommand(); - CommandResult result = registeredCommand.execute(sender, string); - - switch (result) { - case PERMISSION_FAIL -> { - String message = command.getPermissionMessage(); - if (message != null) - sender.sendMessage(message); - return; - } - - case INVALID_SENDER -> { - String message = command.getInvalidSenderMessage(); - if (message != null) - sender.sendMessage(message); - return; - } - - case SUCCESS, EXCEPTION -> { - return; - } - } - } - } - - public @NotNull List<@NotNull String> autoComplete(@NotNull CommandSender sender, @NotNull String string) { - return List.of(); - } - - public @NotNull List getCommands(@NotNull String name) { - List commands = this.commands.get(name); - return commands == null ? List.of() : List.copyOf(commands); - } - - public @NotNull List getCommands(@NotNull Object object) { - List result = new ArrayList<>(commands.size()); - for (List commands : this.commands.values()) - for (RegisteredCommand command : commands) - if (command.isOwnedBy(object)) - result.add(command); - return result; - } - - public @NotNull CommandHolder getHolder() { - return holder; - } - - public @NotNull Map> getCommands() { - return Map.copyOf(commands); - } - - public @NotNull Set getCommandExtractors() { - return Set.copyOf(commandExtractors); - } - - public void addCommandExtractor(@NotNull CommandExtractor extractor) { - commandExtractors.add(extractor); - } - - public void removeCommandExtractor(@NotNull CommandExtractor extractor) { - commandExtractors.remove(extractor); - } - - public boolean hasCommandExtractor(@NotNull CommandExtractor extractor) { - return commandExtractors.contains(extractor); - } -} \ No newline at end of file diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/CommandResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/CommandResult.java deleted file mode 100644 index 875696af..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/CommandResult.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.wizardlybump17.wlib.command; - -public enum CommandResult { - - SUCCESS, - ARGS_FAIL, - EXCEPTION, - PERMISSION_FAIL, - METHOD_FAIL, - INVALID_SENDER -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/Command.java b/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/Command.java index b941b23b..bdd69c96 100644 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/Command.java +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/Command.java @@ -1,6 +1,5 @@ package com.wizardlybump17.wlib.command.annotation; -import com.wizardlybump17.wlib.command.sender.CommandSender; import org.jetbrains.annotations.NotNull; import java.lang.annotation.ElementType; @@ -8,85 +7,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - *

An annotation that describes a command.

- *

For a command to be valid the first parameter of the method must implements {@link CommandSender}

- */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Command { - /** - *

How the sender must type the command in order for it to be triggered. - * Example:

command hello world
- * In the example above, the sender must type /command hello world, so it can be triggered. - * To add parameters that depends on the sender input, just put the parameter between <>. - *
- * Example:
command <hello> world

- *

The type of each parameter is defined in the method that have this annotation. - * An example for the command above: - *

-     *  @Command(execution = "command <hello> world")
-     *  public void commandHelloWorld(GenericSender sender, String hello) {
-     *      System.out.println(sender.getName() + " executed the hello world command with the argument " + hello);
-     *  }
-     * 

- * - * @return how the command must be sent to be triggered - * @see com.wizardlybump17.wlib.command.args.reader.ArgsReader - */ - String execution(); + @NotNull String value(); - /** - * @return which permission the sender must have to trigger this command - */ - String permission() default ""; - - /** - *

Used when the {@link CommandSender} does not have the required {@link #permission()}.

- * @return the message to be sent when the {@link CommandSender} does not have the required {@link #permission()} - */ - String permissionMessage() default ""; - - /** - * @return if the {@link #permissionMessage()} is a field in the class that have this annotation - */ - boolean permissionMessageIsField() default false; - - /** - * Sets the priority of this command. If the priority is -1, then the priority check is the same as - *
{@code this.execution().split(" ").length}
- * - * @return the priority of this command - */ - int priority() default -1; - - /** - * Sets the options of this command. - * The Bukkit implementation does nothing with this - * - * @return the options of this command - */ - String[] options() default {}; - - /** - * @return the description of this command - */ - String description() default ""; - - /** - *

Used when the {@link CommandSender} is not valid for this command.

- * @return the message to be sent when the {@link CommandSender} is not valid for this command - */ - String invalidSenderMessage() default ""; - - /** - * @return if the {@link #invalidSenderMessage()} is a field in the class that have this annotation - */ - boolean invalidSenderMessageIsField() default false; - - /** - * @return the type of the {@link CommandSender#getHandle()} that can execute this command - */ - @NotNull Class senderType() default Object.class; + @NotNull String permission() default ""; } diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/Description.java b/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/NonNullInput.java similarity index 71% rename from commands/src/main/java/com/wizardlybump17/wlib/command/Description.java rename to commands/src/main/java/com/wizardlybump17/wlib/command/annotation/NonNullInput.java index f66546fc..1060aab8 100644 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/Description.java +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/annotation/NonNullInput.java @@ -1,4 +1,4 @@ -package com.wizardlybump17.wlib.command; +package com.wizardlybump17.wlib.command.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -7,7 +7,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface Description { - - String value(); +public @interface NonNullInput { } diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsNode.java deleted file mode 100644 index 23d07d47..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsNode.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.wizardlybump17.wlib.command.args; - -import com.wizardlybump17.wlib.command.Argument; -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.args.reader.ArgsReaderException; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Represents a node of a command argument - */ -@Data -@AllArgsConstructor -public class ArgsNode { - - public static final Object EMPTY = new Object(); - - @NotNull - private final String name; - private final boolean userInput; - @Nullable - private final ArgsReader reader; - @Nullable - private final String description; - private final boolean isArgument; - - /** - * Parses the given input - * @param input the input - * @return the parsed object - * @throws ArgsReaderException if the input is invalid - */ - public Object parse(String input) throws ArgsReaderException { - if (reader == null) - return EMPTY; - - Object object = reader.read(input); - if (isArgument) - return new Argument<>(name, object, input); - - return object; - } - - public static @NotNull ArgsNode literal(@NotNull String string) { - return new ArgsNode(string, false, null, null, false); - } - - public static @NotNull ArgsNode userInput(@NotNull String name, @NotNull ArgsReader reader) { - return new ArgsNode(name, true, reader, null, false); - } - - public static @NotNull ArgsNode userInput(@NotNull String name, @NotNull Class type) { - return userInput(name, ArgsReaderRegistry.INSTANCE.getReader(type)); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderRegistry.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderRegistry.java deleted file mode 100644 index 09c8f99d..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderRegistry.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.wizardlybump17.wlib.command.args; - -import com.wizardlybump17.wlib.command.args.reader.*; -import com.wizardlybump17.wlib.object.Registry; - -/** - * A registry for the {@link ArgsReader}. - *

The key is the {@link ArgsReader#getClass()} and the value is the {@link ArgsReader}

- */ -public class ArgsReaderRegistry extends Registry>, ArgsReader> { - - public static final ArgsReaderRegistry INSTANCE = new ArgsReaderRegistry(); - - static { - INSTANCE.add(new StringReader()); - INSTANCE.add(new ByteReader()); - INSTANCE.add(new ByteArrayReader()); - INSTANCE.add(new ShortReader()); - INSTANCE.add(new ShortArrayReader()); - INSTANCE.add(new IntegerReader()); - INSTANCE.add(new IntegerArrayReader()); - INSTANCE.add(new FloatReader()); - INSTANCE.add(new FloatArrayReader()); - INSTANCE.add(new DoubleReader()); - INSTANCE.add(new DoubleArrayReader()); - INSTANCE.add(new BigIntegerArgsReader()); - INSTANCE.add(new BigDecimalArgsReader()); - INSTANCE.add(new UUIDReader()); - INSTANCE.add(new BooleanReader()); - INSTANCE.add(new DateReader()); - } - - private ArgsReaderRegistry() { - } - - @SuppressWarnings("unchecked") - public void add(ArgsReader reader) { - put((Class>) reader.getClass(), reader); - } - - /** - * Gets the first reader that can read the specified type - * @param type the type - * @return the reader - */ - public ArgsReader getReader(Class type) { - for (ArgsReader reader : getMap().values()) - if (reader.getTypes().contains(type)) - return reader; - return null; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderType.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderType.java deleted file mode 100644 index 2e8fc4ca..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/ArgsReaderType.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wizardlybump17.wlib.command.args; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specified the {@link ArgsReader} of the parameter - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface ArgsReaderType { - - /** - * @return the class of the {@link ArgsReader} to use - */ - Class> value(); -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReader.java deleted file mode 100644 index d8a6fa5a..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReader.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -/** - * Used to read and convert the args from string to the specified type - * @param which type the string will be converted to - */ -public abstract class ArgsReader { - - private List> typesCache; - - /** - *

The type that the string will be converted to.

- *

If it is {@code null} then you should use the {@link com.wizardlybump17.wlib.command.args.ArgsReaderType} annotation on your parameter

- * @return the type that the string will be converted to - * @deprecated Use the {@link #getTypes()} instead - */ - @Nullable - @Deprecated - public abstract Class getType(); - - /** - * @return the types that {@code this} {@link ArgsReader} can accept in the method parameter - */ - public @NonNull List> getTypes() { - if (typesCache == null) - typesCache = Collections.singletonList(getType()); - return typesCache; - } - - public abstract T read(String string) throws ArgsReaderException; - - /** - *

Gives the suggestions for the specified sender and current argument (optional).

- * @param sender who is executing the command - * @param current the current argument - * @return a {@link List} of suggestions - */ - @NonNull - public List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return Collections.emptyList(); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReaderException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReaderException.java deleted file mode 100644 index e24704dd..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ArgsReaderException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class ArgsReaderException extends Exception { - - public ArgsReaderException(String message) { - super(message); - } - - public ArgsReaderException(Throwable cause) { - super(cause); - } - - public ArgsReaderException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigDecimalArgsReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigDecimalArgsReader.java deleted file mode 100644 index 638016ba..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigDecimalArgsReader.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import java.math.BigDecimal; - -public class BigDecimalArgsReader extends ArgsReader { - - @Override - public Class getType() { - return BigDecimal.class; - } - - @Override - public BigDecimal read(String string) { - try { - return new BigDecimal(string); - } catch (NumberFormatException e) { - return null; - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigIntegerArgsReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigIntegerArgsReader.java deleted file mode 100644 index bf39e180..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BigIntegerArgsReader.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import java.math.BigInteger; - -public class BigIntegerArgsReader extends ArgsReader { - - @Override - public Class getType() { - return BigInteger.class; - } - - @Override - public BigInteger read(String string) { - try { - return new BigInteger(string); - } catch (NumberFormatException e) { - return null; - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BooleanReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BooleanReader.java deleted file mode 100644 index 539558be..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/BooleanReader.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class BooleanReader extends ArgsReader { - - public static final @NonNull List> TYPES = List.of(boolean.class, Boolean.class); - public static final List SUGGESTIONS = List.of("true", "false"); - - @Override - public Class getType() { - return boolean.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Boolean read(String string) throws ArgsReaderException { - if (string.equalsIgnoreCase("true")) - return true; - if (string.equalsIgnoreCase("false")) - return false; - - return null; - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteArrayReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteArrayReader.java deleted file mode 100644 index ed29be28..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteArrayReader.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class ByteArrayReader extends ArgsReader { - - @Override - public Class getType() { - return byte[].class; - } - - @Override - public byte[] read(String string) throws ArgsReaderException { - try { - final String[] strings = string.split(" "); - - byte[] result = new byte[strings.length]; - for (int i = 0; i < result.length; i++) - result[i] = Byte.parseByte(strings[i]); - return result; - } catch (NumberFormatException e) { - throw new ArgsReaderException("expected a byte array in string form but got " + string); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteReader.java deleted file mode 100644 index 00c9056c..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ByteReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class ByteReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("-128", "0", "127"); - public static final @NonNull List> TYPES = List.of(byte.class, Byte.class); - - @Override - public Class getType() { - return byte.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Byte read(String string) throws ArgsReaderException { - try { - return Byte.parseByte(string); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DateReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DateReader.java deleted file mode 100644 index 70f576eb..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DateReader.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.github.sisyphsu.dateparser.DateParserUtils; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -import java.time.format.DateTimeParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -public class DateReader extends ArgsReader { - - //i could do a dedicated system for this, but this one is simpler and works so idc - public static final @NonNull Map> PROVIDERS = new HashMap<>(); - - static { - PROVIDERS.put("now", Date::new); - PROVIDERS.put("today", () -> getToday().getTime()); - PROVIDERS.put("yesterday", () -> { - Calendar today = getToday(); - today.add(Calendar.DAY_OF_YEAR, -1); - return today.getTime(); - }); - PROVIDERS.put("tomorrow", () -> { - Calendar today = getToday(); - today.add(Calendar.DAY_OF_YEAR, 1); - return today.getTime(); - }); - } - - @Override - public @NonNull Class getType() { - return Date.class; - } - - @Override - public @Nullable Date read(@NonNull String string) { - Supplier provider = PROVIDERS.get(string.toLowerCase()); - if (provider != null) - return provider.get(); - - try { - return DateParserUtils.parseDate(string); - } catch (DateTimeParseException e) { - return null; //i need to rework the command system :C - } - } - - public static @NonNull Calendar getToday() { - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - return calendar; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleArrayReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleArrayReader.java deleted file mode 100644 index 7ebd303f..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleArrayReader.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class DoubleArrayReader extends ArgsReader { - - @Override - public Class getType() { - return double[].class; - } - - @Override - public double[] read(String string) throws ArgsReaderException { - try { - final String[] strings = string.split(" "); - - double[] result = new double[strings.length]; - for (int i = 0; i < result.length; i++) - result[i] = Double.parseDouble(strings[i]); - return result; - } catch (NumberFormatException e) { - throw new ArgsReaderException("expected a double array in string form but got " + string); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleReader.java deleted file mode 100644 index b7c4f356..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/DoubleReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class DoubleReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("1", "1.5", "10", "10.5"); - public static final @NonNull List> TYPES = List.of(double.class, Double.class); - - @Override - public Class getType() { - return double.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Double read(String string) throws ArgsReaderException { - try { - return Double.parseDouble(string); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatArrayReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatArrayReader.java deleted file mode 100644 index bdb28e86..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatArrayReader.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class FloatArrayReader extends ArgsReader { - - @Override - public Class getType() { - return float[].class; - } - - @Override - public float[] read(String string) throws ArgsReaderException { - try { - final String[] strings = string.split(" "); - - float[] result = new float[strings.length]; - for (int i = 0; i < result.length; i++) - result[i] = Float.parseFloat(strings[i]); - return result; - } catch (NumberFormatException e) { - throw new ArgsReaderException("expected a float array in string form but got " + string); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatReader.java deleted file mode 100644 index f90a346a..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/FloatReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class FloatReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("1", "1.5", "10", "10.5"); - public static final @NonNull List> TYPES = List.of(float.class, Float.class); - - @Override - public Class getType() { - return float.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Float read(String string) throws ArgsReaderException { - try { - return Float.parseFloat(string); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerArrayReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerArrayReader.java deleted file mode 100644 index 43f4e59e..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerArrayReader.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class IntegerArrayReader extends ArgsReader { - - @Override - public Class getType() { - return int[].class; - } - - @Override - public int[] read(String string) throws ArgsReaderException { - try { - final String[] strings = string.split(" "); - - int[] result = new int[strings.length]; - for (int i = 0; i < result.length; i++) - result[i] = Integer.parseInt(strings[i]); - return result; - } catch (NumberFormatException e) { - throw new ArgsReaderException("expected a int array in string form but got " + string); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerReader.java deleted file mode 100644 index 5123f358..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/IntegerReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class IntegerReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("1", "10", "100"); - public static final @NonNull List> TYPES = List.of(int.class, Integer.class); - - @Override - public Class getType() { - return int.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Integer read(String string) throws ArgsReaderException { - try { - return Integer.parseInt(string); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortArrayReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortArrayReader.java deleted file mode 100644 index 62315102..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortArrayReader.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -public class ShortArrayReader extends ArgsReader { - - @Override - public Class getType() { - return short[].class; - } - - @Override - public short[] read(String string) throws ArgsReaderException { - try { - final String[] strings = string.split(" "); - - short[] result = new short[strings.length]; - for (int i = 0; i < result.length; i++) - result[i] = Short.parseShort(strings[i]); - return result; - } catch (NumberFormatException e) { - throw new ArgsReaderException("expected a short array in string form but got " + string); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortReader.java deleted file mode 100644 index 14e84005..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/ShortReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class ShortReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("-32768", "0", "32767"); - public static final @NonNull List> TYPES = List.of(short.class, Short.class); - - @Override - public Class getType() { - return short.class; - } - - @Override - public @NonNull List> getTypes() { - return TYPES; - } - - @Override - public Short read(String string) throws ArgsReaderException { - try { - return Short.parseShort(string); - } catch (NumberFormatException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/StringReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/StringReader.java deleted file mode 100644 index 93f2c9c4..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/StringReader.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public class StringReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("dummy", "text"); - - @Override - public Class getType() { - return String.class; - } - - @Override - public String read(String string) { - return string; - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/UUIDReader.java b/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/UUIDReader.java deleted file mode 100644 index d9f4c7db..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/args/reader/UUIDReader.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.wizardlybump17.wlib.command.args.reader; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; -import java.util.UUID; - -public class UUIDReader extends ArgsReader { - - public static final List SUGGESTIONS = List.of("00000000-0000-0000-0000-000000000000"); - - @Override - public Class getType() { - return UUID.class; - } - - @Override - public UUID read(String string) throws ArgsReaderException { - try { - return UUID.fromString(string); - } catch (IllegalArgumentException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleter.java b/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleter.java deleted file mode 100644 index f0145c0d..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleter.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.wizardlybump17.wlib.command.completer; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; - -public interface ArgumentCompleter { - - @NonNull - List complete(@NonNull CommandSender sender, @NonNull String @NonNull [] args); -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleterRegistry.java b/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleterRegistry.java deleted file mode 100644 index 3758074a..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/completer/ArgumentCompleterRegistry.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.wizardlybump17.wlib.command.completer; - -import com.wizardlybump17.wlib.object.Registry; - -public class ArgumentCompleterRegistry extends Registry, ArgumentCompleter> { - - public static final ArgumentCompleterRegistry INSTANCE = new ArgumentCompleterRegistry(); - - private ArgumentCompleterRegistry() { - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/context/CommandContext.java b/commands/src/main/java/com/wizardlybump17/wlib/command/context/CommandContext.java new file mode 100644 index 00000000..82a63e15 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/context/CommandContext.java @@ -0,0 +1,66 @@ +package com.wizardlybump17.wlib.command.context; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.*; + +public record CommandContext(@NotNull Command command, @NotNull CommandSender sender, @NotNull CommandNodeArguments arguments, int lastInputIndex, @NotNull CommandNode lastNode) { + + public record CommandNodeArgument(@NotNull CommandNode node, @Nullable String input, @Nullable T data) { + } + + public static final class CommandNodeArguments { + + private final @NotNull @Unmodifiable Map> arguments; + + public CommandNodeArguments(@NotNull List> arguments) { + Map> argumentsMap = new LinkedHashMap<>(); + for (CommandNodeArgument argument : arguments) + argumentsMap.put(argument.node().getName(), argument); + this.arguments = Collections.unmodifiableMap(argumentsMap); + } + + public boolean hasArgument(@NotNull String key) { + return arguments.containsKey(key); + } + + @SuppressWarnings("unchecked") + public @NotNull Optional> getArgument(@NotNull String key) { + return Optional.ofNullable((CommandNodeArgument) arguments.get(key)); + } + + @SuppressWarnings("unchecked") + public @NotNull Optional getArgumentData(@NotNull String key) { + return (Optional) getArgument(key).map(CommandNodeArgument::data); + } + + public @NotNull @Unmodifiable Map> getArguments() { + return arguments; + } + + @Override + public String toString() { + return "CommandNodeArguments{" + + "arguments=" + arguments + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + CommandNodeArguments arguments1 = (CommandNodeArguments) o; + return Objects.equals(arguments, arguments1.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(arguments); + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/data/AnnotationCommandData.java b/commands/src/main/java/com/wizardlybump17/wlib/command/data/AnnotationCommandData.java deleted file mode 100644 index e45d26f0..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/data/AnnotationCommandData.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.wizardlybump17.wlib.command.data; - -import com.wizardlybump17.wlib.command.annotation.Command; -import com.wizardlybump17.wlib.util.ReflectionUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class AnnotationCommandData extends CommandData { - - private final @NotNull Command annotation; - private final @NotNull Object object; - private final @NotNull Map fieldCache = new HashMap<>(); - - public AnnotationCommandData(@NotNull Command annotation, @NotNull Object object) { - this.annotation = annotation; - this.object = object; - } - - @Override - public @NotNull String getExecution() { - return annotation.execution(); - } - - @Override - public @Nullable String getPermission() { - String permission = annotation.permission(); - return permission.isBlank() ? super.getPermission() : permission; - } - - @Override - public @Nullable String getPermissionMessage() { - return getMessage(annotation.permissionMessage(), annotation.permissionMessageIsField(), super.getPermissionMessage()); - } - - @Override - public int getPriority() { - int priority = annotation.priority(); - return priority == -1 ? super.getPriority() : priority; - } - - @Override - public @Nullable String getDescription() { - String description = annotation.description(); - return description.isBlank() ? super.getDescription() : description; - } - - @Override - public @Nullable String getInvalidSenderMessage() { - return getMessage(annotation.invalidSenderMessage(), annotation.invalidSenderMessageIsField(), super.getInvalidSenderMessage()); - } - - protected @Nullable String getMessage(@NotNull String message, boolean isField, @Nullable String defaultMessage) { - if (isField) { - return ReflectionUtil.getFieldValue( - fieldCache.computeIfAbsent( - message, - $ -> ReflectionUtil.getField(message, object.getClass()) - ), - object - ); - } - return message.isBlank() ? defaultMessage : message; - } - - public @NotNull Command getAnnotation() { - return annotation; - } - - @Override - public @NotNull Class getSenderType() { - return annotation.senderType(); - } - - @Override - public boolean equals(Object object1) { - if (object1 == null || getClass() != object1.getClass()) - return false; - AnnotationCommandData that = (AnnotationCommandData) object1; - return Objects.equals(annotation, that.annotation) && Objects.equals(object, that.object) && Objects.equals(fieldCache, that.fieldCache); - } - - @Override - public int hashCode() { - return Objects.hash(annotation, object, fieldCache); - } - - @Override - public String toString() { - return "AnnotationCommandData{" + - "annotation=" + annotation + - ", object=" + object + - ", fieldCache=" + fieldCache + - '}'; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/data/CommandData.java b/commands/src/main/java/com/wizardlybump17/wlib/command/data/CommandData.java deleted file mode 100644 index 9dd74ef5..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/data/CommandData.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.wizardlybump17.wlib.command.data; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class CommandData { - - /** - *

How the sender must type the command in order for it to be triggered. - * Example:

command hello world
- * In the example above, the sender must type /command hello world, so it can be triggered. - * To add parameters that depends on the sender input, just put the parameter between <>. - *
- * Example:
command <hello> world

- *

The type of each parameter is defined in the method that have this annotation. - * An example for the command above: - *

-     *  @Command(execution = "command <hello> world")
-     *  public void commandHelloWorld(GenericSender sender, String hello) {
-     *      System.out.println(sender.getName() + " executed the hello world command with the argument " + hello);
-     *  }
-     * 

- * - * @return how the command must be sent to be triggered - * @see com.wizardlybump17.wlib.command.args.reader.ArgsReader - */ - public abstract @NotNull String getExecution(); - - /** - * @return which permission the sender must have to trigger this command - */ - public @Nullable String getPermission() { - return null; - } - - /** - *

Used when the {@link CommandSender} does not have the required {@link #getPermission()}.

- * @return the message to be sent when the {@link CommandSender} does not have the required {@link #getPermission()} - */ - public @Nullable String getPermissionMessage() { - return null; - } - - /** - * @return the priority of this command - */ - public int getPriority() { - return getExecution().split(" ").length; - } - - /** - * @return the description of this command - */ - public @Nullable String getDescription() { - return null; - } - - /** - *

Used when the {@link CommandSender} is not valid for this command.

- * @return the message to be sent when the {@link CommandSender} is not valid for this command - */ - public @Nullable String getInvalidSenderMessage() { - return null; - } - - public final @NotNull String getName() { - return getExecution().split(" ")[0]; - } - - /** - * @return the {@link CommandSender#getHandle()} that can execute this command - */ - public @NotNull Class getSenderType() { - return Object.class; - } - - public static @NotNull SimpleCommandData.Builder builder() { - return new SimpleCommandData.Builder(); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/data/SimpleCommandData.java b/commands/src/main/java/com/wizardlybump17/wlib/command/data/SimpleCommandData.java deleted file mode 100644 index 66905c05..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/data/SimpleCommandData.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.wizardlybump17.wlib.command.data; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.function.Supplier; - -public class SimpleCommandData extends CommandData { - - private final @NotNull Supplier execution; - private final @NotNull Supplier permission; - private final @NotNull Supplier permissionMessage; - private final @NotNull Supplier priority; - private final @NotNull Supplier description; - private final @NotNull Supplier invalidSenderMessage; - private final @NotNull Supplier> senderType; - - public SimpleCommandData(@NotNull Supplier execution, @NotNull Supplier permission, @NotNull Supplier permissionMessage, @NotNull Supplier priority, @NotNull Supplier description, @NotNull Supplier invalidSenderMessage, @NotNull Supplier> senderType) { - this.execution = execution; - this.permission = permission; - this.permissionMessage = permissionMessage; - this.priority = priority; - this.description = description; - this.invalidSenderMessage = invalidSenderMessage; - this.senderType = senderType; - } - - public SimpleCommandData(@NotNull String execution, @Nullable String permission, @Nullable String permissionMessage, int priority, @Nullable String description, @Nullable String invalidSenderMessage, @NotNull Class senderType) { - this( - () -> execution, - () -> permission, - () -> permissionMessage, - () -> priority, - () -> description, - () -> invalidSenderMessage, - () -> senderType - ); - } - - @Override - public @NotNull String getExecution() { - return execution.get(); - } - - @Override - public @Nullable String getPermission() { - return permission.get(); - } - - @Override - public @Nullable String getPermissionMessage() { - return permissionMessage.get(); - } - - @Override - public int getPriority() { - return priority.get(); - } - - @Override - public @Nullable String getDescription() { - return description.get(); - } - - @Override - public @Nullable String getInvalidSenderMessage() { - return invalidSenderMessage.get(); - } - - @Override - public @NotNull Class getSenderType() { - return senderType.get(); - } - - public static class Builder { - - Builder() { - } - - private @Nullable Supplier execution; - private @Nullable Supplier permission; - private @Nullable Supplier permissionMessage; - private @Nullable Supplier priority; - private @Nullable Supplier description; - private @Nullable Supplier invalidSenderMessage; - private @Nullable Supplier> senderType; - - public @Nullable String execution() { - return execution == null ? null : execution.get(); - } - - public @NotNull Builder execution(@Nullable String execution) { - this.execution = () -> execution; - return this; - } - - public @NotNull Builder execution(@Nullable Supplier execution) { - this.execution = execution; - return this; - } - - public @Nullable String permission() { - return permission == null ? null : permission.get(); - } - - public @NotNull Builder permission(@Nullable String permission) { - this.permission = () -> permission; - return this; - } - - public @NotNull Builder permission(@Nullable Supplier permission) { - this.permission = permission; - return this; - } - - public @Nullable String permissionMessage() { - return permissionMessage == null ? null : permissionMessage.get(); - } - - public @NotNull Builder permissionMessage(@Nullable String permissionMessage) { - this.permissionMessage = () -> permissionMessage; - return this; - } - - public @NotNull Builder permissionMessage(@Nullable Supplier permissionMessage) { - this.permissionMessage = permissionMessage; - return this; - } - - public @Nullable Integer priority() { - return priority == null ? null : priority.get(); - } - - public @NotNull Builder priority(@Nullable Integer priority) { - this.priority = () -> priority; - return this; - } - - public @NotNull Builder priority(@Nullable Supplier priority) { - this.priority = priority; - return this; - } - - public @Nullable String description() { - return description == null ? null : description.get(); - } - - public @NotNull Builder description(@Nullable String description) { - this.description = () -> description; - return this; - } - - public @NotNull Builder description(@Nullable Supplier description) { - this.description = description; - return this; - } - - public @Nullable String invalidSenderMessage() { - return invalidSenderMessage == null ? null : invalidSenderMessage.get(); - } - - public @NotNull Builder invalidSenderMessage(@Nullable String invalidSenderMessage) { - this.invalidSenderMessage = () -> invalidSenderMessage; - return this; - } - - public @NotNull Builder invalidSenderMessage(@Nullable Supplier invalidSenderMessage) { - this.invalidSenderMessage = invalidSenderMessage; - return this; - } - - public @Nullable Class senderType() { - return senderType == null ? null : senderType.get(); - } - - public @NotNull Builder senderType(@Nullable Class senderType) { - this.senderType = () -> senderType; - return this; - } - - public @NotNull Builder senderType(@Nullable Supplier> senderType) { - this.senderType = senderType; - return this; - } - - public @NotNull CommandData build() { - String execution = execution(); - if (execution == null) - throw new NullPointerException("The execution can not be null"); - - String permission = permission(); - String permissionMessage = permissionMessage(); - - Integer priority = priority(); - if (priority == null) - priority = execution.split(" ").length; - - String description = description(); - String invalidSenderMessage = invalidSenderMessage(); - - Class senderType = senderType(); - if (senderType == null) - senderType = Object.class; - - return new SimpleCommandData( - execution, - permission, - permissionMessage, - priority, - description, - invalidSenderMessage, - senderType - ); - } - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/CommandException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/CommandException.java deleted file mode 100644 index 7c99b73f..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/CommandException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wizardlybump17.wlib.command.exception; - -import org.jetbrains.annotations.NotNull; - -public class CommandException extends Exception { - - public CommandException() { - } - - public CommandException(@NotNull String message) { - super(message); - } - - public CommandException(@NotNull String message, @NotNull Throwable cause) { - super(message, cause); - } - - public CommandException(@NotNull Throwable cause) { - super(cause); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InputParsingException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InputParsingException.java new file mode 100644 index 00000000..9f4c4315 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InputParsingException.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.exception; + +import org.jetbrains.annotations.NotNull; + +public class InputParsingException extends Exception { + + public InputParsingException(@NotNull String message) { + super(message); + } + + public InputParsingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InvalidInputException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InvalidInputException.java new file mode 100644 index 00000000..410fa29e --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/InvalidInputException.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.exception; + +import org.jetbrains.annotations.NotNull; + +public class InvalidInputException extends Exception { + + public InvalidInputException(@NotNull String message) { + super(message); + } + + public InvalidInputException(@NotNull String message, @NotNull Throwable cause) { + super(message, cause); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/SuggesterException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/SuggesterException.java new file mode 100644 index 00000000..92c90364 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/SuggesterException.java @@ -0,0 +1,21 @@ +package com.wizardlybump17.wlib.command.exception; + +import org.jetbrains.annotations.NotNull; + +public class SuggesterException extends Exception { + + public SuggesterException() { + } + + public SuggesterException(@NotNull String message) { + super(message); + } + + public SuggesterException(@NotNull String message, @NotNull Throwable cause) { + super(message, cause); + } + + public SuggesterException(@NotNull Throwable cause) { + super(cause); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/CommandExtractorException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/CommandExtractorException.java new file mode 100644 index 00000000..fb6767aa --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/CommandExtractorException.java @@ -0,0 +1,22 @@ +package com.wizardlybump17.wlib.command.exception.extractor; + +import org.jetbrains.annotations.NotNull; + +public abstract class CommandExtractorException extends Exception { + + public CommandExtractorException() { + super(); + } + + public CommandExtractorException(@NotNull String message) { + super(message); + } + + public CommandExtractorException(@NotNull String message, @NotNull Throwable cause) { + super(message, cause); + } + + public CommandExtractorException(@NotNull Throwable cause) { + super(cause); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/method/MethodCommandNodeFactoryNotFoundException.java b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/method/MethodCommandNodeFactoryNotFoundException.java new file mode 100644 index 00000000..2a3f5195 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/exception/extractor/method/MethodCommandNodeFactoryNotFoundException.java @@ -0,0 +1,15 @@ +package com.wizardlybump17.wlib.command.exception.extractor.method; + +import com.wizardlybump17.wlib.command.exception.extractor.CommandExtractorException; +import org.jetbrains.annotations.NotNull; + +public class MethodCommandNodeFactoryNotFoundException extends CommandExtractorException { + + public MethodCommandNodeFactoryNotFoundException() { + super(); + } + + public MethodCommandNodeFactoryNotFoundException(@NotNull String message) { + super(message); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandExecutor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandExecutor.java deleted file mode 100644 index a0c3720a..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandExecutor.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.wizardlybump17.wlib.command.executor; - -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -public interface CommandExecutor { - - void execute(@NotNull CommandSender sender, @NotNull Map args) throws CommandException; -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandNodeExecutor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandNodeExecutor.java new file mode 100644 index 00000000..60fbdf03 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/executor/CommandNodeExecutor.java @@ -0,0 +1,11 @@ +package com.wizardlybump17.wlib.command.executor; + +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.result.CommandResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface CommandNodeExecutor { + + @Nullable CommandResult execute(@NotNull CommandContext context); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/CommandExtractor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/CommandExtractor.java index 8f1d4373..d6dc28e4 100644 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/CommandExtractor.java +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/CommandExtractor.java @@ -1,13 +1,20 @@ package com.wizardlybump17.wlib.command.extractor; -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.holder.CommandHolder; -import com.wizardlybump17.wlib.command.registered.RegisteredCommand; +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.exception.extractor.CommandExtractorException; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; import org.jetbrains.annotations.NotNull; import java.util.List; public interface CommandExtractor { - @NotNull List extract(@NotNull CommandManager manager, @NotNull CommandHolder holder, @NotNull Object object); + boolean isAccepted(@NotNull Object object); + + @NotNull List extract(@NotNull Object object) throws CommandExtractorException; + + static @NotNull MethodCommandExtractor method(@NotNull MethodCommandNodeFactoryRegistry factoryRegistry) { + return new MethodCommandExtractor(factoryRegistry); + } } diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/DirectCommandExtractor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/DirectCommandExtractor.java deleted file mode 100644 index bb21f6c5..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/DirectCommandExtractor.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.wizardlybump17.wlib.command.extractor; - -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.holder.CommandHolder; -import com.wizardlybump17.wlib.command.registered.RegisteredCommand; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class DirectCommandExtractor implements CommandExtractor { - - @Override - public @NotNull List extract(@NotNull CommandManager manager, @NotNull CommandHolder holder, @NotNull Object object) { - return object instanceof RegisteredCommand command ? List.of(command) : List.of(); - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/MethodCommandExtractor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/MethodCommandExtractor.java deleted file mode 100644 index 354da31d..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/MethodCommandExtractor.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.wizardlybump17.wlib.command.extractor; - -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.annotation.Command; -import com.wizardlybump17.wlib.command.holder.CommandHolder; -import com.wizardlybump17.wlib.command.registered.RegisteredCommand; -import com.wizardlybump17.wlib.command.registered.RegisteredMethodCommand; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -public class MethodCommandExtractor implements CommandExtractor { - - @Override - public @NotNull List extract(@NotNull CommandManager manager, @NotNull CommandHolder holder, @NotNull Object object) { - List commands = new ArrayList<>(); - for (Method method : object.getClass().getDeclaredMethods()) { - if (!method.isAnnotationPresent(Command.class) || method.getParameterCount() == 0 || !CommandSender.class.isAssignableFrom(method.getParameterTypes()[0])) - continue; - - RegisteredMethodCommand command; - try { - command = new RegisteredMethodCommand( - method.getAnnotation(Command.class), - object, - method - ); - } catch (Exception e) { - holder.getLogger().log(Level.SEVERE, "Error while creating a command", e); - continue; - } - - commands.add(command); - com.wizardlybump17.wlib.command.holder.Command holderCommand = holder.getCommand(command.getName()); - if (holderCommand != null) - holderCommand.setExecutor(holderCommand.getDefaultExecutor(manager, command.getName())); - } - return commands; - } -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/MethodCommandExtractor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/MethodCommandExtractor.java new file mode 100644 index 00000000..0b599fae --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/MethodCommandExtractor.java @@ -0,0 +1,184 @@ +package com.wizardlybump17.wlib.command.extractor.method; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.exception.extractor.method.MethodCommandNodeFactoryNotFoundException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.extractor.CommandExtractor; +import com.wizardlybump17.wlib.command.extractor.method.executor.AbstractMethodCommandNodeExecutor; +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; + +public class MethodCommandExtractor implements CommandExtractor { + + private final @NotNull MethodCommandNodeFactoryRegistry factoryRegistry; + + public MethodCommandExtractor(@NotNull MethodCommandNodeFactoryRegistry factoryRegistry) { + this.factoryRegistry = factoryRegistry; + } + + @Override + public boolean isAccepted(@NotNull Object object) { + return true; + } + + public @NotNull MethodCommandNodeFactoryRegistry getFactoryRegistry() { + return factoryRegistry; + } + + @Override + public @NotNull List extract(@NotNull Object object) throws MethodCommandNodeFactoryNotFoundException { + List commands = new ArrayList<>(); + + Class clazz = object.getClass(); + + for (Method method : clazz.getMethods()) { + com.wizardlybump17.wlib.command.annotation.Command annotation = method.getAnnotation(com.wizardlybump17.wlib.command.annotation.Command.class); + if (annotation == null) + continue; + + Parameter[] parameters = method.getParameters(); + Class returnType = method.getReturnType(); + + String[] commandParts = annotation.value().split(" "); + + CommandNode root = null; + + int parameterIndex = parameters.length - 1; + for (int i = commandParts.length - 1; i >= 0; i--) { + String part = commandParts[i]; + + CommandNode oldRoot = root; + root = createNode(factoryRegistry, part, parameters, parameterIndex, root, annotation, object, method); + if (!(root instanceof LiteralCommandNode)) + parameterIndex--; + + if (oldRoot == null) + root = root.withExecutor(createExecutor(object, parameters, returnType, method)); + } + + if (root == null) + throw new IllegalArgumentException(); + + commands.add(new Command((LiteralCommandNode) root)); + } + + return commands; + } + + private static @NotNull CommandNode createNode(@NotNull MethodCommandNodeFactoryRegistry factoryRegistry, @NotNull String part, @NotNull Parameter @NotNull [] parameters, int parameterIndex, @Nullable CommandNode root, @NotNull com.wizardlybump17.wlib.command.annotation.Command annotation, @NotNull Object object, @NotNull Method method) throws MethodCommandNodeFactoryNotFoundException { + CommandNode newNode; + + boolean argument = part.charAt(0) == '<' && part.charAt(part.length() - 1) == '>'; + if (argument) + part = part.substring(1, part.length() - 1); + + if (parameterIndex < 0) { + newNode = new LiteralCommandNode(part, root == null ? List.of() : List.of(root), null, null); + } else { + if (argument) { + Parameter parameter = parameters[parameterIndex]; + Class parameterType = parameter.getType(); + + MethodCommandNodeFactory factory = factoryRegistry.getFactory(parameterType); + if (factory == null) + throw new MethodCommandNodeFactoryNotFoundException("MethodCommandNodeFactory not found for the parameter " + parameter); + + newNode = factory.create(object, method, annotation, parameter, part, root); + } else { + newNode = new LiteralCommandNode(part, root == null ? List.of() : List.of(root), null, null); + } + } + + if (!annotation.permission().isEmpty()) + newNode = newNode.withPermission(annotation.permission()); + + return newNode; + } + + public static @NotNull CommandNodeExecutor createExecutor(@NotNull Object object, @NotNull String methodName, @NotNull Class @NotNull ... parameterTypes) { + try { + Method method = object.getClass().getMethod(methodName, parameterTypes); + return createExecutor(object, method.getParameters(), method.getReturnType(), method); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(e); + } + } + + + /** + *
    + *
  • If empty -> check the return type;
  • + *
  • If CommandSender only -> pass only the command sender and check the return type;
  • + *
  • If CommandContext only -> pass only the command context and check the return type;
  • + *
  • If CommandSender + more parameters -> pass the command sender and the parameters and check the return type;
  • + *
  • If CommandContext + more parameters -> pass the command context and the parameters and check the return type;
  • + *
  • If only parameters -> pass the parameters and check the return type.
  • + *
+ * + * @param object the object containing the method + * @param parameters the parameters of the method + * @param returnType the return type of the method + * @param method the {@link Method} + * @return the appropriate CommandNodeExecutor based on the parameters and return type of the method + */ + private static @NotNull CommandNodeExecutor createExecutor(@NotNull Object object, @NotNull Parameter @NotNull [] parameters, @NotNull Class returnType, @NotNull Method method) { + if (parameters.length == 0) { //no parameters + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.NoArgumentsCommandResultExecutor<>(object, method); + } else { + return new AbstractMethodCommandNodeExecutor.NoArgumentsExecutor<>(object, method); + } + } + + Parameter firstParameter = parameters[0]; + Class firstParameterType = firstParameter.getType(); + + if (parameters.length == 1) { + if (CommandSender.class.isAssignableFrom(firstParameterType)) { //CommandSender only + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.CommandSenderCommandResultExecutor<>(object, method); + } else { //anything else return type + return new AbstractMethodCommandNodeExecutor.CommandSenderExecutor<>(object, method); + } + } else if (CommandContext.class.isAssignableFrom(firstParameterType)) { //CommandContext only + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.CommandContextCommandResultExecutor<>(object, method); + } else { //anything else return type + return new AbstractMethodCommandNodeExecutor.CommandContextExecutor<>(object, method); + } + } + } + + if (CommandSender.class.isAssignableFrom(firstParameterType)) { //CommandSender + more arguments + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.CommandSenderAndArgumentsCommandResultExecutor<>(object, method); + } else { //anything else return type + return new AbstractMethodCommandNodeExecutor.CommandSenderAndArgumentsExecutor<>(object, method); + } + } else if (CommandContext.class.isAssignableFrom(firstParameterType)) { //CommandContext + more arguments + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.CommandContextAndArgumentsCommandResultExecutor<>(object, method); + } else { //anything else return type + return new AbstractMethodCommandNodeExecutor.CommandContextAndArgumentsExecutor<>(object, method); + } + } else { + if (CommandResult.class.isAssignableFrom(returnType)) { //CommandResult return type + return new AbstractMethodCommandNodeExecutor.ArgumentsCommandResultExecutor<>(object, method); + } else { //anything else return type + return new AbstractMethodCommandNodeExecutor.ArgumentsExecutor<>(object, method); + } + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/AbstractMethodCommandNodeExecutor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/AbstractMethodCommandNodeExecutor.java new file mode 100644 index 00000000..f74a27ba --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/AbstractMethodCommandNodeExecutor.java @@ -0,0 +1,342 @@ +package com.wizardlybump17.wlib.command.extractor.method.executor; + +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@ApiStatus.Internal +public abstract sealed class AbstractMethodCommandNodeExecutor implements MethodCommandNodeExecutor { + + private final @NotNull Object object; + private final @NotNull MethodHandle methodHandle; + private final @NotNull Method method; + + public AbstractMethodCommandNodeExecutor(@NotNull Object object, @NotNull Method method) { + this.object = object; + try { + this.methodHandle = MethodHandles.publicLookup().findVirtual(object.getClass(), method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new IllegalArgumentException(e); + } + this.method = method; + } + + @Override + public @NotNull Object object() { + return object; + } + + @Override + public @NotNull MethodHandle methodHandle() { + return methodHandle; + } + + @Override + public @NotNull Method method() { + return method; + } + + @Override + public boolean equals(Object object1) { + if (object1 == null || getClass() != object1.getClass()) + return false; + AbstractMethodCommandNodeExecutor that = (AbstractMethodCommandNodeExecutor) object1; + return Objects.equals(object, that.object) + && Objects.equals(method, that.method); + } + + @Override + public int hashCode() { + return Objects.hash(object, method); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "object=" + object + + ", methodHandle=" + methodHandle + + ", method=" + method + + '}'; + } + + @ApiStatus.Internal + public static final class CommandSenderCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandSenderCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context.sender()); + return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandSenderExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandSenderExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context.sender()); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandContextCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandContextCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context); + return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandContextExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandContextExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandSenderAndArgumentsCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandSenderAndArgumentsCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context.sender()); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + }); + return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandSenderAndArgumentsExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandSenderAndArgumentsExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context.sender()); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + }); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandContextAndArgumentsCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandContextAndArgumentsCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + });return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class CommandContextAndArgumentsExecutor extends AbstractMethodCommandNodeExecutor { + + public CommandContextAndArgumentsExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + parameters.add(context); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + }); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class ArgumentsCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public ArgumentsCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + }); + return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class ArgumentsExecutor extends AbstractMethodCommandNodeExecutor { + + public ArgumentsExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + context.arguments().getArguments().forEach((nodeName, argument) -> { + if (!(argument.node() instanceof LiteralCommandNode)) + parameters.add(argument.data()); + }); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class NoArgumentsExecutor extends AbstractMethodCommandNodeExecutor { + + public NoArgumentsExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + return (CommandResult) CommandResult.successful(context, methodHandle().invokeWithArguments(parameters)); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } + + @ApiStatus.Internal + public static final class NoArgumentsCommandResultExecutor extends AbstractMethodCommandNodeExecutor { + + public NoArgumentsCommandResultExecutor(@NotNull Object object, @NotNull Method method) { + super(object, method); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull CommandResult execute(@NotNull CommandContext context) { + try { + List parameters = new ArrayList<>(); + parameters.add(object()); + return (CommandResult) methodHandle().invokeWithArguments(parameters); + } catch (Throwable throwable) { + return CommandResult.exceptionally(context, throwable); + } + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/MethodCommandNodeExecutor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/MethodCommandNodeExecutor.java new file mode 100644 index 00000000..9b814159 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/executor/MethodCommandNodeExecutor.java @@ -0,0 +1,16 @@ +package com.wizardlybump17.wlib.command.extractor.method.executor; + +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import org.jetbrains.annotations.NotNull; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; + +public sealed interface MethodCommandNodeExecutor extends CommandNodeExecutor permits AbstractMethodCommandNodeExecutor { + + @NotNull Object object(); + + @NotNull MethodHandle methodHandle(); + + @NotNull Method method(); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/MethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/MethodCommandNodeFactory.java new file mode 100644 index 00000000..9ed8be17 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/MethodCommandNodeFactory.java @@ -0,0 +1,34 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +public abstract class MethodCommandNodeFactory { + + public abstract @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root); + + public abstract @NotNull Class @NotNull [] getSupportedTypes(); + + public boolean isSupported(@NotNull Class type) { + if (isStrict()) { + for (Class supportedType : getSupportedTypes()) + if (supportedType.equals(type)) + return true; + return false; + } + + for (Class supportedType : getSupportedTypes()) + if (supportedType.isAssignableFrom(type)) + return true; + return false; + } + + public boolean isStrict() { + return true; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/InstantCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/InstantCommandNode.java new file mode 100644 index 00000000..36030ef2 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/InstantCommandNode.java @@ -0,0 +1,46 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.object; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.List; + +public class InstantCommandNode extends CommandNode { + + public InstantCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable Instant parse(@NotNull String input) throws InputParsingException { + if (input.equalsIgnoreCase("null")) + return null; + try { + return Instant.parse(input); + } catch (DateTimeParseException e) { + throw new InputParsingException("Could not parse as Instant: " + input, e); + } + } + + @Override + public @NotNull InstantCommandNode withChildren(@NotNull List> children) { + return new InstantCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull InstantCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new InstantCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull InstantCommandNode withPermission(@Nullable String permission) { + return new InstantCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/JsonElementMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/JsonElementMethodCommandNodeFactory.java new file mode 100644 index 00000000..8c9072a3 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/JsonElementMethodCommandNodeFactory.java @@ -0,0 +1,52 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.object; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.annotation.NonNullInput; +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.object.JsonElementCommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class JsonElementMethodCommandNodeFactory extends MethodCommandNodeFactory { + + private final @NotNull Gson gson; + + public JsonElementMethodCommandNodeFactory(@NotNull Gson gson) { + this.gson = gson; + } + + @Override + public @NotNull JsonElementCommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + return new JsonElementCommandNode( + name, + root == null ? List.of() : List.of(root), + parameter.isAnnotationPresent(NonNullInput.class) ? AllowedInputs.anyNotNull() : AllowedInputs.anyNullable(), + null, + null, + null, + gson + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {JsonElement.class}; + } + + @Override + public boolean isStrict() { + return false; + } + + public @NotNull Gson getGson() { + return gson; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/UUIDMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/UUIDMethodCommandNodeFactory.java new file mode 100644 index 00000000..cb3817c4 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/object/UUIDMethodCommandNodeFactory.java @@ -0,0 +1,38 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.object; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.annotation.NonNullInput; +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.input.object.AllowedUUIDInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.object.UUIDCommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.UUID; + +public class UUIDMethodCommandNodeFactory extends MethodCommandNodeFactory { + + @Override + public @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + if (!isSupported(parameter.getType())) + throw new IllegalArgumentException("Unsupported type. We only accept UUIDs: " + parameter); + + return new UUIDCommandNode( + name, + root == null ? List.of() : List.of(root), + parameter.isAnnotationPresent(NonNullInput.class) ? AllowedUUIDInputs.anyNotNull() : AllowedUUIDInputs.anyNullable(), + null, + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {UUID.class}; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/BooleanMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/BooleanMethodCommandNodeFactory.java new file mode 100644 index 00000000..9cbe2856 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/BooleanMethodCommandNodeFactory.java @@ -0,0 +1,37 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.primitive; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.input.primitive.AllowedBooleanInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.BooleanCommandNode; +import com.wizardlybump17.wlib.command.suggestion.primitive.BooleanSuggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class BooleanMethodCommandNodeFactory extends PrimitiveMethodCommandNodeFactory { + + @Override + public @NotNull BooleanCommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + Class type = parameter.getType(); + if (isSupported(type)) + throw new IllegalArgumentException("Unsupported type. We only accept booleans: " + type); + + return new BooleanCommandNode( + name, + root == null ? List.of() : List.of(root), + AllowedBooleanInputs.anyNotNull(), + BooleanSuggester.any(), + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {boolean.class, Boolean.class}; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/CharacterMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/CharacterMethodCommandNodeFactory.java new file mode 100644 index 00000000..cd662956 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/CharacterMethodCommandNodeFactory.java @@ -0,0 +1,37 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.primitive; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.input.primitive.AllowedCharacterInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.AbstractPrimitiveCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.CharacterCommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class CharacterMethodCommandNodeFactory extends PrimitiveMethodCommandNodeFactory { + + @Override + public @NotNull AbstractPrimitiveCommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + Class type = parameter.getType(); + if (!isSupported(type)) + throw new IllegalArgumentException("Unsupported type. We only accept chars: " + type); + + return new CharacterCommandNode( + name, + root == null ? List.of() : List.of(root), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {char.class, Character.class}; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/NumberMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/NumberMethodCommandNodeFactory.java new file mode 100644 index 00000000..2210ae5d --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/NumberMethodCommandNodeFactory.java @@ -0,0 +1,100 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.primitive; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.input.primitive.number.*; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.*; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class NumberMethodCommandNodeFactory extends PrimitiveMethodCommandNodeFactory { + + @Override + public @NotNull NumberCommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + Class type = parameter.getType(); + if (!isSupported(type)) + throw new IllegalArgumentException("Unsupported type. We accept only primitive numbers: " + type); + + List> children = root == null ? List.of() : List.of(root); + + if (type == byte.class || type == Byte.class) { + return new ByteCommandNode( + name, + children, + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ); + } + if (type == short.class || type == Short.class) { + return new ShortCommandNode( + name, + children, + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ); + } + if (type == int.class || type == Integer.class) { + return new IntegerCommandNode( + name, + children, + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ); + } + if (type == long.class || type == Long.class) { + return new LongCommandNode( + name, + children, + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ); + } + if (type == float.class || type == Float.class) { + return new FloatCommandNode( + name, + children, + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ); + } + if (type == double.class || type == Double.class) { + return new DoubleCommandNode( + name, + children, + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ); + } + + throw new IllegalArgumentException("Unsupported type. We accept only primitive numbers: " + type); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] { + byte.class, Byte.class, + short.class, Short.class, + int.class, Integer.class, + long.class, Long.class, + float.class, Float.class, + double.class, Double.class + }; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/PrimitiveMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/PrimitiveMethodCommandNodeFactory.java new file mode 100644 index 00000000..1ded737b --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/primitive/PrimitiveMethodCommandNodeFactory.java @@ -0,0 +1,16 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.primitive; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +public abstract class PrimitiveMethodCommandNodeFactory extends MethodCommandNodeFactory { + + @Override + public abstract @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/string/StringMethodCommandNodeFactory.java b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/string/StringMethodCommandNodeFactory.java new file mode 100644 index 00000000..eb29fc96 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/string/StringMethodCommandNodeFactory.java @@ -0,0 +1,39 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory.string; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.annotation.NonNullInput; +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.string.StringCommandNode; +import com.wizardlybump17.wlib.command.suggestion.string.StringSuggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class StringMethodCommandNodeFactory extends MethodCommandNodeFactory { + + @Override + public @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + Class type = parameter.getType(); + if (!isSupported(type)) + throw new IllegalArgumentException("Unsupported type. We accept only Strings: " + type); + + return new StringCommandNode( + name, + root == null ? List.of() : List.of(root), + parameter.isAnnotationPresent(NonNullInput.class) ? AllowedStringInputs.anyNotNull() : AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {String.class}; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/Command.java b/commands/src/main/java/com/wizardlybump17/wlib/command/holder/Command.java deleted file mode 100644 index e766809d..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/Command.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.wizardlybump17.wlib.command.holder; - -import com.wizardlybump17.wlib.command.CommandManager; - -public interface Command { - - void setExecutor(CommandExecutor executor); - - CommandExecutor getExecutor(); - - /** - *

Used by the {@link com.wizardlybump17.wlib.command.CommandManager} when creating a new command.

- *

This is used to set the default command executor.

- * @param manager the command manager - * @param name the command name - * @return the default command executor - */ - CommandExecutor getDefaultExecutor(CommandManager manager, String name); -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandExecutor.java b/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandExecutor.java deleted file mode 100644 index 05321945..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandExecutor.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.wizardlybump17.wlib.command.holder; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReaderException; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.sender.CommandSender; - -/** - * Interface for intercepting commands when they are called - */ -public interface CommandExecutor { - - void execute(CommandSender sender, String commandName, String[] args) throws ArgsReaderException, CommandException; -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandHolder.java b/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandHolder.java deleted file mode 100644 index c5c8e0ef..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/holder/CommandHolder.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.wizardlybump17.wlib.command.holder; - -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -import java.util.logging.Logger; - -/** - * Represents something that can hold commands - * @param The holder type - */ -public interface CommandHolder { - - @Nullable Command getCommand(String name); - - @NonNull H getHandle(); - - @NonNull - Logger getLogger(); -} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedInputs.java new file mode 100644 index 00000000..afa40388 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedInputs.java @@ -0,0 +1,67 @@ +package com.wizardlybump17.wlib.command.input; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedInputs { + + boolean isAllowed(@Nullable T input); + + @SuppressWarnings("unchecked") + static @NotNull Any anyNullable() { + return (Any) Any.NULLABLE; + } + + @SuppressWarnings("unchecked") + static @NotNull Any anyNotNull() { + return (Any) Any.NOT_NULL; + } + + static AllowedListInputs.@NotNull Values values(@NotNull List values) { + return new AllowedListInputs.Values<>(values); + } + + final class Any implements AllowedInputs { + + private static final @NotNull Any NULLABLE = new Any<>(true); + private static final @NotNull Any NOT_NULL = new Any<>(false); + + private final boolean nullable; + + Any(boolean nullable) { + this.nullable = nullable; + } + + @Override + public boolean isAllowed(@Nullable T input) { + return nullable || input != null; + } + + public boolean nullable() { + return nullable; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Any any = (Any) o; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedListInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedListInputs.java new file mode 100644 index 00000000..5a6a4fbb --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/AllowedListInputs.java @@ -0,0 +1,53 @@ +package com.wizardlybump17.wlib.command.input; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedListInputs extends AllowedInputs { + + @NotNull List allowedValues(); + + @Override + default boolean isAllowed(@Nullable T input) { + if (input == null) + return false; + return allowedValues().contains(input); + } + + final class Values implements AllowedListInputs { + + private final @NotNull List values; + + Values(@NotNull List values) { + this.values = List.copyOf(values); + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Values values1 = (Values) o; + return Objects.equals(values, values1.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + @Override + public String toString() { + return "AllowedListInputs$Values{" + + "values=" + values + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/RangedAllowedInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/RangedAllowedInputs.java new file mode 100644 index 00000000..2cf43936 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/RangedAllowedInputs.java @@ -0,0 +1,12 @@ +package com.wizardlybump17.wlib.command.input; + +import org.jetbrains.annotations.NotNull; + +public interface RangedAllowedInputs extends AllowedInputs { + + @NotNull T from(); + + @NotNull T to(); + + boolean isInRange(@NotNull T input); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/SingleValueInput.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/SingleValueInput.java new file mode 100644 index 00000000..a78c8a29 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/SingleValueInput.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.input; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface SingleValueInput extends AllowedInputs { + + @NotNull T value(); + + @Override + default boolean isAllowed(@Nullable T input) { + return value().equals(input); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/object/AllowedUUIDInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/object/AllowedUUIDInputs.java new file mode 100644 index 00000000..3dc4e768 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/object/AllowedUUIDInputs.java @@ -0,0 +1,139 @@ +package com.wizardlybump17.wlib.command.input.object; + +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.input.AllowedListInputs; +import com.wizardlybump17.wlib.command.input.SingleValueInput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public interface AllowedUUIDInputs extends AllowedInputs { + + static @NotNull Value value(@NotNull UUID value) { + return new Value(value); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(List.copyOf(values)); + } + + static @NotNull Values values(@NotNull UUID @NotNull ... values) { + return new Values(List.of(values)); + } + + static @NotNull Any anyNullable() { + return Any.NULLABLE; + } + + static @NotNull Any anyNotNull() { + return Any.NOT_NULL; + } + + final class Value implements AllowedUUIDInputs, SingleValueInput { + + private final @NotNull UUID value; + + Value(@NotNull UUID value) { + this.value = value; + } + + @Override + public @NotNull UUID value() { + return value; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return Objects.equals(value, value1.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public String toString() { + return "AllowedUUIDInputs$Value{" + + "value=" + value + + '}'; + } + } + + final class Values implements AllowedUUIDInputs, AllowedListInputs { + + private final @NotNull List values; + + private Values(@NotNull List values) { + this.values = values; + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedUUIDInputs.Values values1 = (AllowedUUIDInputs.Values) object; + return Objects.equals(values, values1.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + @Override + public String toString() { + return "AllowedUUIDInputs$Values{" + + "values=" + values + + '}'; + } + } + + final class Any implements AllowedUUIDInputs { + + private static final @NotNull AllowedUUIDInputs.Any NULLABLE = new AllowedUUIDInputs.Any(true); + private static final @NotNull AllowedUUIDInputs.Any NOT_NULL = new AllowedUUIDInputs.Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + @Override + public boolean isAllowed(@Nullable UUID input) { + return nullable || input != null; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedUUIDInputs.Any any = (AllowedUUIDInputs.Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedUUIDInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedBooleanInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedBooleanInputs.java new file mode 100644 index 00000000..154211a6 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedBooleanInputs.java @@ -0,0 +1,103 @@ +package com.wizardlybump17.wlib.command.input.primitive; + +import com.wizardlybump17.wlib.command.input.SingleValueInput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public interface AllowedBooleanInputs extends PrimitiveAllowedInputs { + + static @NotNull True trueValue() { + return True.INSTANCE; + } + + static @NotNull False falseValue() { + return False.INSTANCE; + } + + static @NotNull Any anyNullable() { + return Any.NULLABLE; + } + + static @NotNull Any anyNotNull() { + return Any.NOT_NULL; + } + + final class True implements AllowedBooleanInputs, SingleValueInput { + + private static final @NotNull True INSTANCE = new True(); + + private True() { + } + + @Override + public @NotNull Boolean value() { + return true; + } + + @Override + public String toString() { + return "AllowedBooleanInputs$True{}"; + } + } + + final class False implements AllowedBooleanInputs, SingleValueInput { + + private static final @NotNull False INSTANCE = new False(); + + private False() { + } + + @Override + public @NotNull Boolean value() { + return false; + } + + @Override + public String toString() { + return "AllowedBooleanInputs$False{}"; + } + } + + final class Any implements AllowedBooleanInputs { + + private static final @NotNull Any NULLABLE = new Any(true); + private static final @NotNull Any NOT_NULL = new Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + public boolean nullable() { + return nullable; + } + + @Override + public boolean isAllowed(@Nullable Boolean input) { + return nullable || input != null; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Any any = (Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedBooleanInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedCharacterInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedCharacterInputs.java new file mode 100644 index 00000000..79046455 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/AllowedCharacterInputs.java @@ -0,0 +1,190 @@ +package com.wizardlybump17.wlib.command.input.primitive; + +import com.wizardlybump17.wlib.command.input.AllowedListInputs; +import com.wizardlybump17.wlib.command.input.SingleValueInput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public interface AllowedCharacterInputs extends PrimitiveAllowedInputs { + + static @NotNull Value value(char value) { + return new Value(value, false); + } + + static @NotNull Value valueIgnoreCase(char value) { + return new Value(value, true); + } + + static @NotNull Values values(@NotNull List values) { + values = List.copyOf(values); + return new Values(values, values, false); + } + + static @NotNull Values values(@NotNull Character @NotNull ... values) { + List valuesList = List.of(values); + return new Values(valuesList, valuesList, false); + } + + static @NotNull Values valuesIgnoreCase(@NotNull List values) { + return new Values( + List.copyOf(values), + values.stream() + .map(Character::toLowerCase) + .collect(Collectors.toList()), + true + ); + } + + static @NotNull Any anyNullable() { + return Any.NULLABLE; + } + + static @NotNull Any anyNotNull() { + return Any.NOT_NULL; + } + + final class Value implements AllowedCharacterInputs, SingleValueInput { + + private final char value; + private final boolean ignoreCase; + + private Value(char value, boolean ignoreCase) { + this.value = value; + this.ignoreCase = ignoreCase; + } + + @Override + public boolean isAllowed(@Nullable Character input) { + if (input == null) + return false; + return ignoreCase ? Character.toLowerCase(input) == Character.toLowerCase(value) : input == value; + } + + @Override + public @NotNull Character value() { + return value; + } + + public boolean ignoreCase() { + return ignoreCase; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return value == value1.value && ignoreCase == value1.ignoreCase; + } + + @Override + public int hashCode() { + return Objects.hash(value, ignoreCase); + } + + @Override + public String toString() { + return "AllowedCharacterInputs$Value{" + + "value=" + value + + ", ignoreCase=" + ignoreCase + + '}'; + } + } + + final class Values implements AllowedCharacterInputs, AllowedListInputs { + + private final @NotNull List values; + private final @NotNull List toCheck; + private final boolean ignoreCase; + + private Values(@NotNull List values, @NotNull List toCheck, boolean ignoreCase) { + this.values = values; + this.toCheck = toCheck; + this.ignoreCase = ignoreCase; + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + public boolean isIgnoreCase() { + return ignoreCase; + } + + @Override + public boolean isAllowed(@Nullable Character input) { + if (input == null) + return false; + if (ignoreCase) + return toCheck.contains(Character.toLowerCase(input)); + return toCheck.contains(input); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedCharacterInputs.Values values1 = (AllowedCharacterInputs.Values) object; + return ignoreCase == values1.ignoreCase && Objects.equals(values, values1.values) && Objects.equals(toCheck, values1.toCheck); + } + + @Override + public int hashCode() { + return Objects.hash(values, toCheck, ignoreCase); + } + + @Override + public String toString() { + return "AllowedCharacterInputs$Values{" + + "values=" + values + + ", ignoreCase=" + ignoreCase + + '}'; + } + } + + final class Any implements AllowedCharacterInputs { + + private static final @NotNull Any NULLABLE = new Any(true); + private static final @NotNull Any NOT_NULL = new Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + public boolean nullable() { + return nullable; + } + + @Override + public boolean isAllowed(@Nullable Character input) { + return nullable || input != null; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Any any = (Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedCharacterInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/PrimitiveAllowedInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/PrimitiveAllowedInputs.java new file mode 100644 index 00000000..4c815ba3 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/PrimitiveAllowedInputs.java @@ -0,0 +1,6 @@ +package com.wizardlybump17.wlib.command.input.primitive; + +import com.wizardlybump17.wlib.command.input.AllowedInputs; + +public interface PrimitiveAllowedInputs

extends AllowedInputs

{ +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedByteInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedByteInputs.java new file mode 100644 index 00000000..93d349d9 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedByteInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedByteInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(byte from, byte to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(byte value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedByteInputs { + + Range(byte from, byte to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedByteInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedByteInputs { + + static final @NotNull AllowedByteInputs.Unlimited INSTANCE = new AllowedByteInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedByteInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedByteInputs { + + static final @NotNull AllowedByteInputs.Positive INSTANCE = new AllowedByteInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Byte from() { + return 0; + } + + @Override + public @NotNull Byte to() { + return Byte.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Byte input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedByteInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedByteInputs { + + static final @NotNull AllowedByteInputs.Negative INSTANCE = new AllowedByteInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Byte from() { + return -1; + } + + @Override + public @NotNull Byte to() { + return Byte.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Byte input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedByteInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedByteInputs { + + Value(byte value) { + super(value); + } + + @Override + public String toString() { + return "AllowedByteInputs$Value{" + + "value=" + value() + + '}'; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedByteInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedByteInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedDoubleInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedDoubleInputs.java new file mode 100644 index 00000000..e4124abc --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedDoubleInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedDoubleInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(double from, double to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(double value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedDoubleInputs { + + Range(double from, double to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedByteInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedDoubleInputs { + + static final @NotNull AllowedDoubleInputs.Unlimited INSTANCE = new AllowedDoubleInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedDoubleInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedDoubleInputs { + + static final @NotNull AllowedDoubleInputs.Positive INSTANCE = new AllowedDoubleInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Double from() { + return 0.0; + } + + @Override + public @NotNull Double to() { + return Double.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Double input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedDoubleInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedDoubleInputs { + + static final @NotNull AllowedDoubleInputs.Negative INSTANCE = new AllowedDoubleInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Double from() { + return -1.0; + } + + @Override + public @NotNull Double to() { + return Double.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Double input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedDoubleInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedDoubleInputs { + + Value(double value) { + super(value); + } + + @Override + public String toString() { + return "AllowedDoubleInputs$Value{" + + "value=" + value() + + "}"; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedDoubleInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedDoubleInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedFloatInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedFloatInputs.java new file mode 100644 index 00000000..f1c606a3 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedFloatInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedFloatInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(float from, float to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(float value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedFloatInputs { + + Range(float from, float to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedFloatInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedFloatInputs { + + static final @NotNull AllowedFloatInputs.Unlimited INSTANCE = new AllowedFloatInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedFloatInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedFloatInputs { + + static final @NotNull AllowedFloatInputs.Positive INSTANCE = new AllowedFloatInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Float from() { + return 0.0F; + } + + @Override + public @NotNull Float to() { + return Float.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Float input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedFloatInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedFloatInputs { + + static final @NotNull AllowedFloatInputs.Negative INSTANCE = new AllowedFloatInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Float from() { + return -1.0F; + } + + @Override + public @NotNull Float to() { + return Float.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Float input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedFloatInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedFloatInputs { + + Value(float value) { + super(value); + } + + @Override + public String toString() { + return "AllowedFloatInputs$Value{" + + "value=" + value() + + "}"; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedFloatInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedFloatInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedIntegerInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedIntegerInputs.java new file mode 100644 index 00000000..87a2648b --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedIntegerInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedIntegerInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(int from, int to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(int value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedIntegerInputs { + + Range(int from, int to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedIntegerInputs{ + + static final @NotNull AllowedIntegerInputs.Unlimited INSTANCE = new AllowedIntegerInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedIntegerInputs { + + static final @NotNull AllowedIntegerInputs.Positive INSTANCE = new AllowedIntegerInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Integer from() { + return 0; + } + + @Override + public @NotNull Integer to() { + return Integer.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Integer input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedIntegerInputs { + + static final @NotNull AllowedIntegerInputs.Negative INSTANCE = new AllowedIntegerInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Integer from() { + return -1; + } + + @Override + public @NotNull Integer to() { + return Integer.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Integer input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedIntegerInputs { + + Value(@NotNull Integer value) { + super(value); + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Value{" + + "value=" + value() + + "}"; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedIntegerInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedIntegerInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedLongInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedLongInputs.java new file mode 100644 index 00000000..080ecfc4 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedLongInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedLongInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(long from, long to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(long value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedLongInputs { + + Range(long from, long to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedLongInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedLongInputs { + + static final @NotNull AllowedLongInputs.Unlimited INSTANCE = new AllowedLongInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedLongInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedLongInputs { + + static final @NotNull AllowedLongInputs.Positive INSTANCE = new AllowedLongInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Long from() { + return 0L; + } + + @Override + public @NotNull Long to() { + return Long.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Long input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedLongInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedLongInputs { + + static final @NotNull AllowedLongInputs.Negative INSTANCE = new AllowedLongInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Long from() { + return -1L; + } + + @Override + public @NotNull Long to() { + return Long.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Long input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedLongInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedLongInputs { + + Value(long value) { + super(value); + } + + @Override + public String toString() { + return "AllowedLongInputs$Value{" + + "value=" + value() + + "}"; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedLongInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedLongInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedNumberInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedNumberInputs.java new file mode 100644 index 00000000..916bdfc8 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedNumberInputs.java @@ -0,0 +1,182 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.input.AllowedListInputs; +import com.wizardlybump17.wlib.command.input.RangedAllowedInputs; +import com.wizardlybump17.wlib.command.input.SingleValueInput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedNumberInputs extends AllowedInputs { + + static @NotNull Value value(@NotNull N value) { + return new Value<>(value); + } + + static @NotNull Values values(@NotNull List values) { + return new Values<>(values); + } + + abstract class Ranged> implements AllowedNumberInputs, RangedAllowedInputs { + + private final @NotNull N from; + private final @NotNull N to; + + public Ranged(@NotNull N from, @NotNull N to) { + this.from = from; + this.to = to; + } + + @Override + public @NotNull N from() { + return from; + } + + @Override + public @NotNull N to() { + return to; + } + + @Override + public boolean isInRange(@NotNull N number) { + return number.compareTo(from) >= 0 && number.compareTo(to) <= 0; + } + + @Override + public boolean isAllowed(@Nullable N input) { + if (input == null) + return false; + return isInRange(input); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Ranged ranged = (Ranged) object; + return Objects.equals(from, ranged.from) && Objects.equals(to, ranged.to); + } + + @Override + public int hashCode() { + return Objects.hash(from, to); + } + + @Override + public String toString() { + return "Ranged{" + + "from=" + from + + ", to=" + to + + '}'; + } + } + + abstract class Unlimited implements AllowedNumberInputs { + + @Override + public boolean isAllowed(@Nullable N input) { + return true; + } + } + + class Value implements AllowedNumberInputs, SingleValueInput { + + private final @NotNull N value; + + Value(@NotNull N value) { + this.value = value; + } + + @Override + public @NotNull N value() { + return value; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return Objects.equals(value, value1.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public String toString() { + return "Value{" + + "value=" + value + + '}'; + } + } + + class Values implements AllowedNumberInputs, AllowedListInputs { + + private final @NotNull List values; + + Values(@NotNull List values) { + this.values = List.copyOf(values); + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedNumberInputs.Values values1 = (AllowedNumberInputs.Values) object; + return Objects.equals(values, values1.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + @Override + public String toString() { + return "Values{" + + "values=" + values + + '}'; + } + } + + abstract class Positive implements AllowedNumberInputs, RangedAllowedInputs { + + @Override + public boolean isAllowed(@Nullable N input) { + if (input == null) + return false; + return isInRange(input); + } + + @Override + public String toString() { + return "Positive{}"; + } + } + + abstract class Negative implements AllowedNumberInputs, RangedAllowedInputs { + + @Override + public boolean isAllowed(@Nullable N input) { + if (input == null) + return false; + return isInRange(input); + } + + @Override + public String toString() { + return "Negative{}"; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedShortInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedShortInputs.java new file mode 100644 index 00000000..45ca67e6 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/primitive/number/AllowedShortInputs.java @@ -0,0 +1,147 @@ +package com.wizardlybump17.wlib.command.input.primitive.number; + +import com.wizardlybump17.wlib.command.input.primitive.PrimitiveAllowedInputs; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface AllowedShortInputs extends PrimitiveAllowedInputs { + + static @NotNull Range range(short from, short to) { + return new Range(from, to); + } + + static @NotNull Unlimited unlimited() { + return Unlimited.INSTANCE; + } + + static @NotNull Positive positive() { + return Positive.INSTANCE; + } + + static @NotNull Negative negative() { + return Negative.INSTANCE; + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Value value(short value) { + return new Value(value); + } + + final class Range extends AllowedNumberInputs.Ranged implements AllowedShortInputs { + + Range(short from, short to) { + super(from, to); + if (from >= to) + throw new IllegalArgumentException("from must be less than to"); + } + + @Override + public String toString() { + return "AllowedShortInputs$Range{" + + "from=" + from() + + ", to=" + to() + + "}"; + } + } + + final class Unlimited extends AllowedNumberInputs.Unlimited implements AllowedShortInputs { + + static final @NotNull AllowedShortInputs.Unlimited INSTANCE = new AllowedShortInputs.Unlimited(); + + private Unlimited() { + } + + @Override + public String toString() { + return "AllowedShortInputs$Unlimited{}"; + } + } + + final class Positive extends AllowedNumberInputs.Positive implements AllowedShortInputs { + + static final @NotNull AllowedShortInputs.Positive INSTANCE = new AllowedShortInputs.Positive(); + + private Positive() { + } + + @Override + public @NotNull Short from() { + return 0; + } + + @Override + public @NotNull Short to() { + return Short.MAX_VALUE; + } + + @Override + public boolean isInRange(@NotNull Short input) { + return input > -1; + } + + @Override + public String toString() { + return "AllowedShortInputs$Positive{}"; + } + } + + final class Negative extends AllowedNumberInputs.Negative implements AllowedShortInputs { + + static final @NotNull AllowedShortInputs.Negative INSTANCE = new AllowedShortInputs.Negative(); + + private Negative() { + } + + @Override + public @NotNull Short from() { + return -1; + } + + @Override + public @NotNull Short to() { + return Short.MIN_VALUE; + } + + @Override + public boolean isInRange(@NotNull Short input) { + return input < 0; + } + + @Override + public String toString() { + return "AllowedShortInputs$Negative{}"; + } + } + + final class Value extends AllowedNumberInputs.Value implements AllowedShortInputs { + + Value(short value) { + super(value); + } + + @Override + public String toString() { + return "AllowedShortInputs$Value{" + + "value=" + value() + + "}"; + } + } + + final class Values extends AllowedNumberInputs.Values implements AllowedShortInputs { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "AllowedShortInputs$Values{" + + "values=" + allowedValues() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/input/string/AllowedStringInputs.java b/commands/src/main/java/com/wizardlybump17/wlib/command/input/string/AllowedStringInputs.java new file mode 100644 index 00000000..1e5cf8d1 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/input/string/AllowedStringInputs.java @@ -0,0 +1,178 @@ +package com.wizardlybump17.wlib.command.input.string; + +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.input.AllowedListInputs; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedStringInputs extends AllowedInputs { + + static @NotNull Values valuesIgnoreCase(@NotNull List values) { + return new Values(values, true); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values, false); + } + + static @NotNull Value valueIgnoreCase(@NotNull String value) { + return new Value(value, true); + } + + static @NotNull Value value(@NotNull String value) { + return new Value(value, false); + } + + static @NotNull Any anyNullable() { + return Any.NULLABLE; + } + + static @NotNull Any anyNotNull() { + return Any.NOT_NULL; + } + + final class Values implements AllowedStringInputs, AllowedListInputs { + + private final @NotNull List values; + private final @NotNull List toCheck; + private final boolean ignoreCase; + + private Values(@NotNull List values, boolean ignoreCase) { + this.values = List.copyOf(values); + this.ignoreCase = ignoreCase; + if (ignoreCase) + toCheck = values.stream().map(String::toLowerCase).toList(); + else + toCheck = this.values; + } + + @Override + public @NotNull @Unmodifiable List allowedValues() { + return values; + } + + @Override + public boolean isAllowed(@Nullable String input) { + if (input == null) + return false; + if (ignoreCase) + return toCheck.contains(input.toLowerCase()); + return toCheck.contains(input); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedStringInputs.Values values1 = (AllowedStringInputs.Values) object; + return ignoreCase == values1.ignoreCase && Objects.equals(values, values1.values) && Objects.equals(toCheck, values1.toCheck); + } + + @Override + public int hashCode() { + return Objects.hash(values, toCheck, ignoreCase); + } + + @Override + public String toString() { + return "AllowedStringInputs$Values{" + + "values=" + values + + ", ignoreCase=" + ignoreCase + + '}'; + } + } + + final class Value implements AllowedStringInputs { + + private final @NotNull String value; + private final boolean ignoreCase; + + public Value(@NotNull String value, boolean ignoreCase) { + this.value = value; + this.ignoreCase = ignoreCase; + } + + public @NotNull String value() { + return value; + } + + public boolean ignoreCase() { + return ignoreCase; + } + + @Override + public boolean isAllowed(@Nullable String input) { + if (input == null) + return false; + if (ignoreCase) + return value.equalsIgnoreCase(input); + return value.equals(input); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return ignoreCase == value1.ignoreCase && Objects.equals(value, value1.value); + } + + @Override + public int hashCode() { + return Objects.hash(value, ignoreCase); + } + + @Override + public String toString() { + return "AllowedStringInputs$Value{" + + "value='" + value + '\'' + + ", ignoreCase=" + ignoreCase + + '}'; + } + } + + final class Any implements AllowedStringInputs { + + private static final @NotNull AllowedStringInputs.Any NULLABLE = new AllowedStringInputs.Any(true); + private static final @NotNull AllowedStringInputs.Any NOT_NULL = new AllowedStringInputs.Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + @Override + public boolean isAllowed(@Nullable String input) { + return nullable || input != null; + } + + public boolean nullable() { + return nullable; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedStringInputs.Any any = (AllowedStringInputs.Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedStringInput$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/manager/CommandManager.java b/commands/src/main/java/com/wizardlybump17/wlib/command/manager/CommandManager.java new file mode 100644 index 00000000..990c6fd7 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/manager/CommandManager.java @@ -0,0 +1,242 @@ +package com.wizardlybump17.wlib.command.manager; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.manager.listener.CommandManagerListener; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.util.StringUtil; +import com.wizardlybump17.wlib.util.exception.QuotedStringException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class CommandManager { + + public static final char SEPARATOR = ':'; + + private final @NotNull Map commandsByFullName = new ConcurrentHashMap<>(); + private final @NotNull Map commandsByName = new ConcurrentHashMap<>(); + private final @NotNull Set listeners = ConcurrentHashMap.newKeySet(); + private final @NotNull Map> commandsByHolder = new ConcurrentHashMap<>(); + private final @NotNull Map holdersByFullName = new ConcurrentHashMap<>(); + + protected void addCommand(@NotNull String identifier, @NotNull String name, @NotNull Command command, @Nullable Object holder) { + String fullName = identifier + SEPARATOR + name; + + commandsByFullName.put(fullName, command); + commandsByName.put(name, command); + + if (holder != null) { + commandsByHolder.computeIfAbsent(holder, $ -> ConcurrentHashMap.newKeySet()).add(command); + holdersByFullName.put(fullName, holder); + } + + for (CommandManagerListener listener : listeners) + listener.onRegister(identifier, command, holder, this); + } + + public @NotNull Command registerCommand(@NotNull String identifier, @NotNull Command command, @Nullable Object holder) { + String commandName = command.getRoot().getName().toLowerCase(); + String fullCommandName = identifier + SEPARATOR + commandName; + + Command existingCommand = commandsByFullName.get(fullCommandName); + + if (existingCommand != null) { + Command newCommand = mergeCommand(existingCommand, command); + addCommand(identifier, commandName, newCommand, holder); + return newCommand; + } + + addCommand(identifier, commandName, command, holder); + return command; + } + + public @NotNull Command registerCommand(@NotNull String identifier, @NotNull Command command) { + return registerCommand(identifier, command, null); + } + + public @NotNull List registerCommands(@NotNull String identifier, @NotNull List commands, @Nullable Object holder) { + List newCommands = new ArrayList<>(commands.size()); + for (Command command : commands) + newCommands.add(registerCommand(identifier, command, holder)); + return newCommands; + } + + public @NotNull List registerCommands(@NotNull String identifier, @NotNull List commands) { + return registerCommands(identifier, commands, null); + } + + protected @NotNull Command mergeCommand(@NotNull Command left, @NotNull Command right) { + return left.merge(right); + } + + public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull List input) { + if (input.isEmpty()) + return CommandResult.commandNotFound(""); + + String commandName = input.getFirst(); + + Command command = commandsByFullName.get(commandName); + + if (command == null) + command = commandsByName.get(commandName); + if (command == null) + return CommandResult.commandNotFound(commandName); + + return command.execute(sender, input); + } + + public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String input) { + return execute(sender, StringUtil.parseQuotedStrings(input)); + } + + public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String @NotNull [] input) { + return execute(sender, String.join(" ", input)); + } + + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input) throws SuggesterException { + if (input.isEmpty() || input.size() == 1) { + return commandsByName.values().stream() + .map(Command::getRoot) + .filter(node -> node.canExecute(sender)) + .map(CommandNode::getName) + .toList(); + } + + String commandName = input.getFirst(); + + Command command = commandsByFullName.get(commandName); + + if (command == null) + command = commandsByName.get(commandName); + if (command == null) + return List.of(); + + return command.getSuggestions(sender, input); + } + + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull String input) throws SuggesterException { + try { + if (!input.isEmpty() && !StringUtil.isProperlyQuoted(input)) + input = input + "\""; + return getSuggestions(sender, StringUtil.parseQuotedStrings(input)); + } catch (QuotedStringException e) { + return List.of(); + } + } + + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull String @NotNull [] input) throws SuggesterException { + if (input.length == 0) + return getSuggestions(sender, List.of()); + + String inputString = String.join(" ", input); + try { + if (!inputString.isEmpty() && !StringUtil.isProperlyQuoted(inputString)) + inputString = inputString + "\""; + + List inputList = StringUtil.parseQuotedStrings(inputString); + if (input[input.length - 1].isEmpty()) + inputList.add(""); + + return getSuggestions(sender, inputList); + } catch (QuotedStringException e) { + return List.of(); + } + } + + public @NotNull Optional getCommand(@NotNull String name) { + return Optional + .ofNullable(commandsByFullName.get(name)) + .or(() -> Optional.ofNullable(commandsByName.get(name))); + } + + public void clear() { + for (CommandManagerListener listener : listeners) + listener.onPreClear(this); + + commandsByName.clear(); + commandsByFullName.clear(); + + commandsByHolder.forEach((holder, commands) -> commands.clear()); + commandsByHolder.clear(); + + holdersByFullName.clear(); + + for (CommandManagerListener listener : listeners) + listener.onPostClear(this); + } + + public @NotNull @UnmodifiableView Map getCommandsByFullName() { + return Collections.unmodifiableMap(commandsByFullName); + } + + public @NotNull @UnmodifiableView Map getCommandsByName() { + return Collections.unmodifiableMap(commandsByName); + } + + public @NotNull @UnmodifiableView Map> getCommandsByHolder() { + return Collections.unmodifiableMap(commandsByHolder); + } + + public @NotNull @UnmodifiableView Set getCommandsByHolder(@NotNull Object holder) { + return Collections.unmodifiableSet(commandsByHolder.getOrDefault(holder, Set.of())); + } + + public @NotNull @UnmodifiableView Map getHoldersByFullName() { + return Collections.unmodifiableMap(holdersByFullName); + } + + public void addListener(@NotNull CommandManagerListener listener) { + listeners.add(listener); + } + + public void clearListeners() { + listeners.clear(); + } + + public @NotNull @UnmodifiableView Set getListeners() { + return Collections.unmodifiableSet(listeners); + } + + public boolean isEmpty() { + return commandsByFullName.isEmpty(); + } + + public void unregister(@NotNull String identifier, @NotNull Command command) { + unregister(identifier, command.getName()); + } + + public void unregister(@NotNull String identifier, @NotNull String name) { + String fullName = identifier + SEPARATOR + name; + + Command command = commandsByFullName.remove(fullName); + if (command == null) + return; + + commandsByName.remove(name); + + Object holder = holdersByFullName.remove(fullName); + if (holder != null) { + Set commands = commandsByHolder.get(holder); + if (commands != null) + commands.remove(command); + } + + for (CommandManagerListener listener : listeners) + listener.onUnregister(identifier, command, holder, this); + } + + public void unregisterByHolder(@NotNull String identifier, @NotNull Object holder) { + Set commands = commandsByHolder.get(holder); + if (commands == null) + return; + + for (Command command : commands) + unregister(identifier, command); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/manager/listener/CommandManagerListener.java b/commands/src/main/java/com/wizardlybump17/wlib/command/manager/listener/CommandManagerListener.java new file mode 100644 index 00000000..a0d3974e --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/manager/listener/CommandManagerListener.java @@ -0,0 +1,17 @@ +package com.wizardlybump17.wlib.command.manager.listener; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface CommandManagerListener { + + void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager); + + void onPreClear(@NotNull CommandManager manager); + + void onPostClear(@NotNull CommandManager manager); + + void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/CommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/CommandNode.java new file mode 100644 index 00000000..8cf9cf3d --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/CommandNode.java @@ -0,0 +1,255 @@ +package com.wizardlybump17.wlib.command.node; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.exception.InvalidInputException; +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import com.wizardlybump17.wlib.util.CollectionUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.*; + +public abstract class CommandNode { + + private final @NotNull String name; + private final @NotNull @Unmodifiable List> children; + private final @NotNull AllowedInputs allowedInputs; + private final @Nullable Suggester suggester; + private final @Nullable CommandNodeExecutor executor; + private final @Nullable String permission; + + public CommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + this.name = name; + this.children = List.copyOf(children); + this.allowedInputs = allowedInputs; + this.suggester = suggester; + this.executor = executor; + this.permission = permission; + } + + public @NotNull String getName() { + return name; + } + + public @NotNull @Unmodifiable List> getChildren() { + return children; + } + + public @NotNull AllowedInputs getAllowedInputs() { + return allowedInputs; + } + + public @Nullable Suggester getSuggester() { + return suggester; + } + + public abstract @Nullable T parse(@NotNull String input) throws InputParsingException; + + public final boolean isValidInput(@Nullable T input) { + return allowedInputs.isAllowed(input); + } + + public final @Nullable T parseOrInvalid(@NotNull String input) throws InputParsingException, InvalidInputException { + T parse = parse(input); + if (!isValidInput(parse)) + throw new InvalidInputException("Invalid input " + input); + return parse; + } + + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List args, @NotNull String currentInput) throws SuggesterException { + return suggester == null ? List.of() : suggester.getSuggestions(sender, args, currentInput, this); + } + + public @Nullable CommandNodeExecutor getExecutor() { + return executor; + } + + public @Nullable String getPermission() { + return permission; + } + + @Override + public String toString() { + return "CommandNode{" + + "name='" + name + '\'' + + ", children=" + children + + ", allowedInputs=" + allowedInputs + + ", suggester=" + suggester + + ", executor=" + executor + + ", permission='" + permission + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + CommandNode that = (CommandNode) o; + return Objects.equals(name, that.name) + && CollectionUtil.contentEquals(children, that.children) + && Objects.equals(allowedInputs, that.allowedInputs) + && Objects.equals(executor, that.executor) + && Objects.equals(permission, that.permission); + } + + public boolean equalsIgnoreExecutor(@Nullable Object other) { + if (other == null || getClass() != other.getClass()) + return false; + CommandNode that = (CommandNode) other; + return Objects.equals(name, that.name) + && equalsIgnoreExecutor(children, that.children) + && Objects.equals(allowedInputs, that.allowedInputs) + && Objects.equals(permission, that.permission); + } + + public static boolean equalsIgnoreExecutor(@NotNull Collection> a, @NotNull Collection> b) { + if (a.size() != b.size()) + return false; + + Iterator> aIterator = a.iterator(); + Iterator> bIterator = b.iterator(); + + while (aIterator.hasNext() && bIterator.hasNext()) { + CommandNode aNode = aIterator.next(); + CommandNode bNode = bIterator.next(); + if (!aNode.equalsIgnoreExecutor(bNode)) + return false; + } + + return true; + } + + @Override + public int hashCode() { + return Objects.hash(name, children, allowedInputs, executor, permission); + } + + public @NotNull String getFullCommand() { + StringBuilder builder = new StringBuilder(); + if (this instanceof LiteralCommandNode) + builder.append(name); + else + builder.append('<').append(name).append('>'); + + for (CommandNode child : children) { + builder.append(' '); + builder.append(child.getFullCommand()); + } + + return builder.toString(); + } + + public abstract @NotNull CommandNode withChildren(@NotNull List> children); + + public abstract @NotNull CommandNode withExecutor(@Nullable CommandNodeExecutor executor); + + public abstract @NotNull CommandNode withPermission(@Nullable String permission); + + public @NotNull Optional> getChild(@NotNull String name) { + for (CommandNode child : children) + if (child.getName().equals(name)) + return Optional.of(child); + return Optional.empty(); + } + + public @NotNull CommandNode merge(@NotNull CommandNode right) { + List> newChildrenLeft = getNewChildren(right, this); + List> newChildrenRight = getNewChildren(this, right); + + newChildrenLeft.removeIf(newChildLeft -> { + for (CommandNode newChildRight : newChildrenRight) + if (newChildRight.getName().equals(newChildLeft.getName())) + return true; + return false; + }); + newChildrenRight.removeIf(newChildRight -> { + for (CommandNode newChildLeft : newChildrenLeft) + if (newChildLeft.getName().equals(newChildRight.getName())) + return true; + return false; + }); + + newChildrenLeft.addAll(newChildrenRight); + + CommandNode newNode = withChildren(newChildrenLeft); + + if (executor == null && right.getExecutor() != null) + newNode = newNode.withExecutor(right.getExecutor()); + if (permission == null && right.getPermission() != null) + newNode = newNode.withPermission(right.getPermission()); + + return newNode; + } + + private static @Nullable CommandNodeExecutor getNewExecutor(@NotNull CommandNode left, @NotNull CommandNode right) { + CommandNodeExecutor leftExecutor = left.getExecutor(); + CommandNodeExecutor rightExecutor = right.getExecutor(); + CommandNodeExecutor newLeftExecutor; + + if (leftExecutor == null && rightExecutor == null) { + newLeftExecutor = null; + } else if (leftExecutor == null && rightExecutor != null) { + newLeftExecutor = rightExecutor; + } else if (leftExecutor != null && rightExecutor == null) { + newLeftExecutor = leftExecutor; + } else if (Objects.equals(leftExecutor, rightExecutor)) { + newLeftExecutor = leftExecutor; + } else { + throw new IllegalStateException("Could not resolve an executor for the merged node."); + } + + return newLeftExecutor; + } + + private static @NotNull List> getNewChildren(@NotNull CommandNode left, @NotNull CommandNode right) { + List> newChildren = new ArrayList<>(); + + for (CommandNode rightChild : right.children) { + Optional> leftChildOptional = left.getChild(rightChild.name); + + if (leftChildOptional.isEmpty()) { + newChildren.add(rightChild); + continue; + } + + CommandNode leftChild = leftChildOptional.get(); + + CommandNodeExecutor newLeftExecutor = getNewExecutor(leftChild, rightChild); + leftChild = leftChild.withExecutor(newLeftExecutor); + + newChildren.add(leftChild.merge(rightChild)); + } + + return newChildren; + } + + public boolean canExecute(@NotNull CommandSender sender) { + return permission == null || sender.hasPermission(permission); + } + + public @Nullable CommandNode findChild(@NotNull String name) { + for (CommandNode child : children) + if (child.getName().equals(name)) + return child; + + for (CommandNode child : children) { + CommandNode found = child.findChild(name); + if (found != null) + return found; + } + + return null; + } + + public int getTotalNodes() { + int total = 1; + for (CommandNode child : children) + total += child.getTotalNodes(); + return total; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/LiteralCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/LiteralCommandNode.java new file mode 100644 index 00000000..666ede29 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/LiteralCommandNode.java @@ -0,0 +1,57 @@ +package com.wizardlybump17.wlib.command.node; + +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.LiteralSuggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class LiteralCommandNode extends CommandNode { + + public LiteralCommandNode(@NotNull String name, @NotNull List> children, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, AllowedStringInputs.valueIgnoreCase(name), LiteralSuggester.of(name), executor, permission); + } + + @Override + public @NotNull AllowedStringInputs.Value getAllowedInputs() { + return (AllowedStringInputs.Value) super.getAllowedInputs(); + } + + @Override + public @NotNull LiteralSuggester getSuggester() { + return (LiteralSuggester) super.getSuggester(); + } + + @Override + public @NotNull String parse(@NotNull String input) { + return input; + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List args, @NotNull String currentInput) { + return List.of(getName()); + } + + @Override + public @NotNull LiteralCommandNode withChildren(@NotNull List> children) { + return new LiteralCommandNode(getName(), children, getExecutor(), getPermission()); + } + + @Override + public @NotNull CommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new LiteralCommandNode(getName(), getChildren(), executor, getPermission()); + } + + @Override + public @NotNull CommandNode withPermission(@Nullable String permission) { + return new LiteralCommandNode(getName(), getChildren(), getExecutor(), permission); + } + + @Override + public @NotNull LiteralCommandNode merge(@NotNull CommandNode right) { + return (LiteralCommandNode) super.merge(right); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/EnumCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/EnumCommandNode.java new file mode 100644 index 00000000..b9cdf5f0 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/EnumCommandNode.java @@ -0,0 +1,49 @@ +package com.wizardlybump17.wlib.command.node.object; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class EnumCommandNode> extends CommandNode { + + private final @NotNull Class enumType; + + public EnumCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission, @NotNull Class enumType) { + super(name, children, allowedInputs, suggester, executor, permission); + this.enumType = enumType; + } + + @Override + public @Nullable E parse(@NotNull String input) throws InputParsingException { + try { + return Enum.valueOf(enumType, input.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new InputParsingException("Could not parse as " + enumType.getSimpleName() + ": " + input, e); + } + } + + @Override + public @NotNull CommandNode withChildren(@NotNull List> children) { + return new EnumCommandNode<>(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission(), enumType); + } + + @Override + public @NotNull CommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new EnumCommandNode<>(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission(), enumType); + } + + @Override + public @NotNull CommandNode withPermission(@Nullable String permission) { + return new EnumCommandNode<>(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission, enumType); + } + + public @NotNull Class getEnumType() { + return enumType; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/JsonElementCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/JsonElementCommandNode.java new file mode 100644 index 00000000..4667f8af --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/JsonElementCommandNode.java @@ -0,0 +1,49 @@ +package com.wizardlybump17.wlib.command.node.object; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +//TODO: test +public class JsonElementCommandNode extends CommandNode { + + private final @NotNull Gson gson; + + public JsonElementCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission, @NotNull Gson gson) { + super(name, children, allowedInputs, suggester, executor, permission); + this.gson = gson; + } + + @Override + public @Nullable JsonElement parse(@NotNull String input) throws InputParsingException { + try { + return gson.fromJson(input, JsonElement.class); + } catch (JsonSyntaxException e) { + throw new InputParsingException("Could not parse as JsonElement: " + input, e); + } + } + + @Override + public @NotNull CommandNode withChildren(@NotNull List> children) { + return new JsonElementCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission(), gson); + } + + @Override + public @NotNull CommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new JsonElementCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission(), gson); + } + + @Override + public @NotNull CommandNode withPermission(@Nullable String permission) { + return new JsonElementCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission, gson); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/UUIDCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/UUIDCommandNode.java new file mode 100644 index 00000000..97040cbb --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/object/UUIDCommandNode.java @@ -0,0 +1,44 @@ +package com.wizardlybump17.wlib.command.node.object; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.UUID; + +public class UUIDCommandNode extends CommandNode { + + public UUIDCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable UUID parse(@NotNull String input) throws InputParsingException { + try { + return UUID.fromString(input); + } catch (IllegalArgumentException e) { + throw new InputParsingException("Could not parse as UUID: " + input, e); + } + } + + @Override + public @NotNull UUIDCommandNode withChildren(@NotNull List> children) { + return new UUIDCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull UUIDCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new UUIDCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull UUIDCommandNode withPermission(@Nullable String permission) { + return new UUIDCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/AbstractPrimitiveCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/AbstractPrimitiveCommandNode.java new file mode 100644 index 00000000..8578fe08 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/AbstractPrimitiveCommandNode.java @@ -0,0 +1,18 @@ +package com.wizardlybump17.wlib.command.node.primitive; + +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +public abstract class AbstractPrimitiveCommandNode

extends CommandNode

implements PrimitiveCommandNode { + + public AbstractPrimitiveCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs

allowedInputs, @Nullable Suggester

suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/BooleanCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/BooleanCommandNode.java new file mode 100644 index 00000000..17a84308 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/BooleanCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +public class BooleanCommandNode extends AbstractPrimitiveCommandNode { + + public BooleanCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable Boolean parse(@NotNull String input) throws InputParsingException { + if (input.equalsIgnoreCase("true")) + return true; + if (input.equalsIgnoreCase("false")) + return false; + throw new InputParsingException("Could not parse as boolean: " + input); + } + + @Override + public @NotNull BooleanCommandNode withChildren(@NotNull List> children) { + return new BooleanCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull BooleanCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new BooleanCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull BooleanCommandNode withPermission(@Nullable String permission) { + return new BooleanCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/CharacterCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/CharacterCommandNode.java new file mode 100644 index 00000000..e3f356fd --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/CharacterCommandNode.java @@ -0,0 +1,41 @@ +package com.wizardlybump17.wlib.command.node.primitive; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +public class CharacterCommandNode extends AbstractPrimitiveCommandNode { + + public CharacterCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable Character parse(@NotNull String input) throws InputParsingException { + if (input.length() != 1) + throw new InputParsingException("Invalid input length. Expected exactly one char: " + input); + return input.charAt(0); + } + + @Override + public @NotNull CharacterCommandNode withChildren(@NotNull List> children) { + return new CharacterCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull CharacterCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new CharacterCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull CharacterCommandNode withPermission(@Nullable String permission) { + return new CharacterCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/PrimitiveCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/PrimitiveCommandNode.java new file mode 100644 index 00000000..b0564f5c --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/PrimitiveCommandNode.java @@ -0,0 +1,4 @@ +package com.wizardlybump17.wlib.command.node.primitive; + +public interface PrimitiveCommandNode { +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ByteCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ByteCommandNode.java new file mode 100644 index 00000000..a8072033 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ByteCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ByteCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public ByteCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Byte parse(@NotNull String input) throws InputParsingException { + try { + return Byte.parseByte(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as byte: " + input, e); + } + } + + @Override + public @NotNull ByteCommandNode withChildren(@NotNull List> children) { + return new ByteCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull ByteCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new ByteCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull ByteCommandNode withPermission(@Nullable String permission) { + return new ByteCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/DoubleCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/DoubleCommandNode.java new file mode 100644 index 00000000..d27e8c53 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/DoubleCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class DoubleCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public DoubleCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Double parse(@NotNull String input) throws InputParsingException { + try { + return Double.parseDouble(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as double: " + input, e); + } + } + + @Override + public @NotNull DoubleCommandNode withChildren(@NotNull List> children) { + return new DoubleCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull DoubleCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new DoubleCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull DoubleCommandNode withPermission(@Nullable String permission) { + return new DoubleCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/FloatCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/FloatCommandNode.java new file mode 100644 index 00000000..e0f3baa0 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/FloatCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class FloatCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public FloatCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Float parse(@NotNull String input) throws InputParsingException { + try { + return Float.parseFloat(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as float: " + input, e); + } + } + + @Override + public @NotNull FloatCommandNode withChildren(@NotNull List> children) { + return new FloatCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull FloatCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new FloatCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull FloatCommandNode withPermission(@Nullable String permission) { + return new FloatCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/IntegerCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/IntegerCommandNode.java new file mode 100644 index 00000000..89c26e53 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/IntegerCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class IntegerCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public IntegerCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Integer parse(@NotNull String input) throws InputParsingException { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as int: " + input, e); + } + } + + @Override + public @NotNull IntegerCommandNode withChildren(@NotNull List> children) { + return new IntegerCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull IntegerCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new IntegerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull IntegerCommandNode withPermission(@Nullable String permission) { + return new IntegerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/LongCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/LongCommandNode.java new file mode 100644 index 00000000..8bad6eda --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/LongCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class LongCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public LongCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Long parse(@NotNull String input) throws InputParsingException { + try { + return Long.parseLong(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as short: " + input, e); + } + } + + @Override + public @NotNull LongCommandNode withChildren(@NotNull List> children) { + return new LongCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull LongCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new LongCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull LongCommandNode withPermission(@Nullable String permission) { + return new LongCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/NumberCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/NumberCommandNode.java new file mode 100644 index 00000000..4b807681 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/NumberCommandNode.java @@ -0,0 +1,26 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class NumberCommandNode extends CommandNode { + + public NumberCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public abstract @NotNull NumberCommandNode withChildren(@NotNull List> children); + + @Override + public abstract @NotNull NumberCommandNode withExecutor(@Nullable CommandNodeExecutor executor); + + @Override + public abstract @NotNull NumberCommandNode withPermission(@Nullable String permission); +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ShortCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ShortCommandNode.java new file mode 100644 index 00000000..c45cdd7d --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/primitive/number/ShortCommandNode.java @@ -0,0 +1,43 @@ +package com.wizardlybump17.wlib.command.node.primitive.number; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.primitive.PrimitiveCommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class ShortCommandNode extends NumberCommandNode implements PrimitiveCommandNode { + + public ShortCommandNode(@NotNull String name, @NotNull List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @NotNull Short parse(@NotNull String input) throws InputParsingException { + try { + return Short.parseShort(input); + } catch (NumberFormatException e) { + throw new InputParsingException("Could not parse as short: " + input, e); + } + } + + @Override + public @NotNull ShortCommandNode withChildren(@NotNull List> children) { + return new ShortCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull ShortCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new ShortCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull ShortCommandNode withPermission(@Nullable String permission) { + return new ShortCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/node/string/StringCommandNode.java b/commands/src/main/java/com/wizardlybump17/wlib/command/node/string/StringCommandNode.java new file mode 100644 index 00000000..eedeb82a --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/node/string/StringCommandNode.java @@ -0,0 +1,39 @@ +package com.wizardlybump17.wlib.command.node.string; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +public class StringCommandNode extends CommandNode { + + public StringCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable String parse(@NotNull String input) throws InputParsingException { + return input; + } + + @Override + public @NotNull StringCommandNode withChildren(@NotNull List> children) { + return new StringCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull StringCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new StringCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull StringCommandNode withPermission(@Nullable String permission) { + return new StringCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredCommand.java b/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredCommand.java deleted file mode 100644 index aa31aea1..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredCommand.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.wizardlybump17.wlib.command.registered; - -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.CommandResult; -import com.wizardlybump17.wlib.command.args.ArgsNode; -import com.wizardlybump17.wlib.command.args.reader.ArgsReaderException; -import com.wizardlybump17.wlib.command.data.CommandData; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.executor.CommandExecutor; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import com.wizardlybump17.wlib.util.StringUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Optional; - -public class RegisteredCommand implements Comparable { - - private final @NotNull CommandData command; - private final @NotNull List nodes; - private final @NotNull CommandExecutor executor; - - public RegisteredCommand(@NotNull CommandData command, @NotNull List nodes, @NotNull CommandExecutor executor) { - this.command = command; - this.nodes = nodes; - this.executor = executor; - } - - protected RegisteredCommand(@NotNull CommandData command, @NotNull List nodes) { - this.command = command; - this.nodes = nodes; - executor = createExecutor(); - } - - protected CommandExecutor createExecutor() { - throw new UnsupportedOperationException(); - } - - public String getName() { - return command.getExecution().split(" ")[0]; - } - - @Override - public int compareTo(@NotNull RegisteredCommand o) { - return Integer.compare(o.command.getPriority(), command.getPriority()); - } - - public Optional> parse(String input) throws ArgsReaderException { - List toParse = StringUtil.parseQuotedStrings(input); - - LinkedHashMap result = new LinkedHashMap<>(); - - if (checkNodes(result, toParse)) - return Optional.of(result); - - return Optional.empty(); - } - - protected boolean checkNodes(LinkedHashMap target, List strings) throws ArgsReaderException { - if (nodes.size() > strings.size()) - return false; - - for (int i = 0; i < nodes.size() && i < strings.size(); i++) { - ArgsNode node = nodes.get(i); - if (!node.isUserInput()) { - if (node.getName().equalsIgnoreCase(strings.get(i))) - continue; - return false; - } - - Object parse = node.parse(strings.get(i)); - if (parse != ArgsNode.EMPTY) - target.put(node.getName(), parse); - } - - return true; - } - - public @NotNull CommandResult execute(@NotNull CommandSender sender, @NotNull String string) throws CommandException { - if (!canExecute(sender)) - return CommandResult.INVALID_SENDER; - - String permission = command.getPermission(); - if (permission != null && !sender.hasPermission(permission)) - return CommandResult.PERMISSION_FAIL; - - try { - Optional> parse = parse(string); - if (parse.isEmpty()) - return CommandResult.ARGS_FAIL; - - LinkedHashMap args = parse.get(); - if (!isValidArgs(args)) - return CommandResult.ARGS_FAIL; - - executor.execute(sender, args); - return CommandResult.SUCCESS; - } catch (ArgsReaderException e) { - return CommandResult.EXCEPTION; - } - } - - protected boolean isValidArgs(@NotNull LinkedHashMap args) { - return true; - } - - public boolean canExecute(@NotNull CommandSender sender) { - return command.getSenderType().isInstance(sender.getHandle()); - } - - @Override - public String toString() { - return "RegisteredCommand{" + command.getExecution() + "}"; - } - - public @NotNull CommandData getCommand() { - return command; - } - - public @NotNull CommandExecutor getExecutor() { - return executor; - } - - public @NotNull List getNodes() { - return nodes; - } - - public boolean isOwnedBy(@NotNull Object object) { - return false; - } - - public void onRegister(@NotNull CommandManager manager) { - } - - public void onUnregister(@NotNull CommandManager manager) { - } -} \ No newline at end of file diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredMethodCommand.java b/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredMethodCommand.java deleted file mode 100644 index 721860f0..00000000 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredMethodCommand.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.wizardlybump17.wlib.command.registered; - -import com.wizardlybump17.wlib.command.Argument; -import com.wizardlybump17.wlib.command.Description; -import com.wizardlybump17.wlib.command.annotation.Command; -import com.wizardlybump17.wlib.command.args.ArgsNode; -import com.wizardlybump17.wlib.command.args.ArgsReaderRegistry; -import com.wizardlybump17.wlib.command.args.ArgsReaderType; -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.data.AnnotationCommandData; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.executor.CommandExecutor; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; - -@Getter -public class RegisteredMethodCommand extends RegisteredCommand { - - private final @NotNull Command annotation; - private final @NotNull Object object; - private final @NotNull Method method; - private final @NotNull MethodHandle methodHandle; - - public RegisteredMethodCommand(@NotNull Command annotation, @NotNull Object object, @NotNull Method method) throws NoSuchMethodException, IllegalAccessException { - super( - new AnnotationCommandData( - annotation, - object - ), - new ArrayList<>() - ); - this.annotation = annotation; - this.object = object; - this.method = method; - methodHandle = MethodHandles.publicLookup().findVirtual(object.getClass(), method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())); - prepareNodes(); - } - - @Override - protected CommandExecutor createExecutor() { - return (sender, args) -> { - List objects = new ArrayList<>(args.values()); - objects.add(0, object); - objects.add(1, sender); - try { - methodHandle.invokeWithArguments(objects); - } catch (Throwable e) { - throw new CommandException("Error while executing the command " + getCommand().getExecution() + " with the arguments " + args, e); - } - }; - } - - protected void prepareNodes() { - String[] commandArgs = getCommand().getExecution().split(" "); - - Class[] types = method.getParameterTypes(); - Parameter[] parameters = method.getParameters(); - int index = 1; //skipping the first type because of the CommandSender - for (String commandArg : commandArgs) { - if (!isRequiredArgs(commandArg)) { - getNodes().add(new ArgsNode( - commandArg, - false, - null, - null, - false - )); - continue; - } - - Description description = parameters[index].getAnnotation(Description.class); - - ArgsReaderType argsReaderType = parameters[index].getAnnotation(ArgsReaderType.class); - if (argsReaderType == null && Argument.class.isAssignableFrom(types[index])) - throw new IllegalArgumentException("the \"" + commandArg + "\" argument requires the " + ArgsReaderType.class.getName() + " annotation"); - - ArgsReader reader; - if (argsReaderType == null) - reader = ArgsReaderRegistry.INSTANCE.getReader(types[index]); - else - reader = ArgsReaderRegistry.INSTANCE.get(argsReaderType.value()); - if (reader == null) - throw new IllegalArgumentException("no reader found for " + types[index].getName()); - - getNodes().add(new ArgsNode( - trim(commandArg), - true, - reader, - description == null ? null : description.value(), - Argument.class.isAssignableFrom(types[index]) - )); - - index++; - } - } - - @Override - protected boolean isValidArgs(@NotNull LinkedHashMap args) { - return args.size() + 1 == method.getParameterCount(); - } - - @Override - public boolean isOwnedBy(@NotNull Object object) { - return this.object == object; - } - - private static String trim(String string) { - return string.substring(1, string.length() - 1); - } - - private static boolean isRequiredArgs(String string) { - return string.startsWith("<") && string.endsWith(">"); - } -} \ No newline at end of file diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/registry/MethodCommandNodeFactoryRegistry.java b/commands/src/main/java/com/wizardlybump17/wlib/command/registry/MethodCommandNodeFactoryRegistry.java new file mode 100644 index 00000000..f8df2b2e --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/registry/MethodCommandNodeFactoryRegistry.java @@ -0,0 +1,97 @@ +package com.wizardlybump17.wlib.command.registry; + +import com.wizardlybump17.wlib.command.extractor.method.factory.MethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.object.UUIDMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.primitive.BooleanMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.primitive.CharacterMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.primitive.NumberMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.string.StringMethodCommandNodeFactory; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class MethodCommandNodeFactoryRegistry { + + private final @NotNull Map, MethodCommandNodeFactory> factories = new HashMap<>(); + + public void addFactory(@NotNull Class clazz, @NotNull MethodCommandNodeFactory factory) { + factories.put(clazz, factory); + } + + public void addFactory(@NotNull MethodCommandNodeFactory factory, @NotNull Class ... classes) { + if (classes.length < 1) + throw new IllegalArgumentException("The classes array must contain at least one element"); + for (Class clazz : classes) + addFactory(clazz, factory); + } + + public void addFactory(@NotNull MethodCommandNodeFactory factory) { + addFactory(factory, factory.getSupportedTypes()); + } + + public @Nullable MethodCommandNodeFactory getFactory(@NotNull Class clazz) { + return factories.get(clazz); + } + + public void removeFactory(@NotNull Class clazz) { + factories.remove(clazz); + } + + public void removeFactory(@NotNull Class ... classes) { + if (classes.length < 1) + throw new IllegalArgumentException("The classes array must contain at least one element"); + for (Class clazz : classes) + removeFactory(clazz); + } + + public boolean hasFactory(@NotNull Class clazz) { + return factories.containsKey(clazz); + } + + public @NotNull @Unmodifiable Map, MethodCommandNodeFactory> getFactories() { + return Collections.unmodifiableMap(factories); + } + + public void clear() { + factories.clear(); + } + + @ApiStatus.Internal + public void registerDefaults() { + addFactory(new BooleanMethodCommandNodeFactory()); + addFactory(new CharacterMethodCommandNodeFactory()); + addFactory(new NumberMethodCommandNodeFactory()); + addFactory(new StringMethodCommandNodeFactory()); + addFactory(new UUIDMethodCommandNodeFactory()); + } + + @ApiStatus.Internal + public void unregisterDefaults() { + removeFactory( + boolean.class, + Boolean.class, + char.class, + Character.class, + byte.class, + Byte.class, + short.class, + Short.class, + int.class, + Integer.class, + long.class, + Long.class, + float.class, + Float.class, + double.class, + Double.class, + String.class, + UUID.class + ); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/CommandResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/CommandResult.java new file mode 100644 index 00000000..56377cba --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/CommandResult.java @@ -0,0 +1,142 @@ +package com.wizardlybump17.wlib.command.result; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.result.error.*; +import com.wizardlybump17.wlib.command.result.success.NoContentResult; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +public interface CommandResult { + + boolean success(); + + @Nullable T data(); + + @Range(from = 0, to = Integer.MAX_VALUE) + int lastInputIndex(); + + @NotNull CommandNode lastNode(); + + @NotNull String id(); + + //without context + + static @NotNull SuccessResult successful(int lastInputIndex, @NotNull CommandNode lastNode, @Nullable T data) { + return new SuccessResult<>(lastInputIndex, lastNode, data); + } + + static @NotNull ExceptionResult exceptionally( int lastInputIndex, @NotNull CommandNode lastNode, @NotNull Throwable throwable) { + return new ExceptionResult<>(lastInputIndex, lastNode, throwable); + } + + static @NotNull OutOfRangeInputResult outOfRangeInput(int lastInputIndex, @NotNull CommandNode lastNode) { + return new OutOfRangeInputResult<>(lastInputIndex, lastNode); + } + + static @NotNull ExtraArgumentsResult extraArguments(int lastInputIndex, @NotNull CommandNode lastNode) { + return new ExtraArgumentsResult<>(lastInputIndex, lastNode); + } + + static @NotNull InsufficientArgumentsResult insufficientArguments(@NotNull Command command) { + return new InsufficientArgumentsResult<>(command); + } + + static @NotNull ParseInputExceptionResult parseInputException(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull InputParsingException exception) { + return new ParseInputExceptionResult<>(lastInputIndex, lastNode, exception); + } + + static @NotNull CommandNodeExecutorNotFoundResult noCommandNodeExecutor(int lastInputIndex, @NotNull CommandNode lastNode) { + return new CommandNodeExecutorNotFoundResult<>(lastInputIndex, lastNode); + } + + static @NotNull GenericErrorResult genericError(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull String message) { + return new GenericErrorResult<>(lastInputIndex, lastNode, message); + } + + static @NotNull GenericErrorResult genericError(int lastInputIndex, @NotNull CommandNode lastNode) { + return new GenericErrorResult<>(lastInputIndex, lastNode); + } + + static @NotNull NoPermissionResult noPermission(int lastInputIndex, @NotNull CommandNode lastNode) { + return new NoPermissionResult<>(lastInputIndex, lastNode); + } + + static @NotNull CommandNotFoundResult commandNotFound(@NotNull String input) { + return new CommandNotFoundResult<>(input); + } + + static @NotNull InvalidSenderResult invalidSender(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull CommandSender sender, @NotNull Class> expectedSender) { + return new InvalidSenderResult<>(lastInputIndex, lastNode, sender, expectedSender); + } + + static @NotNull NoContentResult noContent(int lastInputIndex, @NotNull CommandNode lastNode) { + return new NoContentResult<>(lastInputIndex, lastNode); + } + + static @NotNull NotFoundResult notFound(int lastInputIndex, @NotNull CommandNode lastNode) { + return new NotFoundResult<>(lastInputIndex, lastNode); + } + + static @NotNull ConflictResult conflict(int lastInputIndex, @NotNull CommandNode lastNode) { + return new ConflictResult<>(lastInputIndex, lastNode); + } + + static @NotNull BadRequestResult badRequest(int lastInputIndex, @NotNull CommandNode lastNode) { + return new BadRequestResult<>(lastInputIndex, lastNode); + } + + static @NotNull UnprocessableContentResult unprocessableContent(int lastInputIndex, @NotNull CommandNode lastNode) { + return new UnprocessableContentResult<>(lastInputIndex, lastNode); + } + + //with context + + static @NotNull SuccessResult successful(@NotNull CommandContext context, @Nullable T data) { + return successful(context.lastInputIndex(), context.lastNode(), data); + } + + static @NotNull ExceptionResult exceptionally(@NotNull CommandContext context, @NotNull Throwable exception) { + return exceptionally(context.lastInputIndex(), context.lastNode(), exception); + } + + static @NotNull GenericErrorResult genericError(@NotNull CommandContext context, @NotNull String message) { + return genericError(context.lastInputIndex(), context.lastNode(), message); + } + + static @NotNull GenericErrorResult genericError(@NotNull CommandContext context) { + return genericError(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull NoPermissionResult noPermission(@NotNull CommandContext context) { + return noPermission(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull InvalidSenderResult invalidSender(@NotNull CommandContext context, @NotNull Class> expectedSender) { + return invalidSender(context.lastInputIndex(), context.lastNode(), context.sender(), expectedSender); + } + + static @NotNull NoContentResult noContent(@NotNull CommandContext context) { + return noContent(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull NotFoundResult notFound(@NotNull CommandContext context) { + return notFound(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull ConflictResult conflict(@NotNull CommandContext context) { + return conflict(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull BadRequestResult badRequest(@NotNull CommandContext context) { + return badRequest(context.lastInputIndex(), context.lastNode()); + } + + static @NotNull UnprocessableContentResult unprocessableContent(@NotNull CommandContext context) { + return unprocessableContent(context.lastInputIndex(), context.lastNode()); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/SuccessResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/SuccessResult.java new file mode 100644 index 00000000..eed1e9a7 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/SuccessResult.java @@ -0,0 +1,20 @@ +package com.wizardlybump17.wlib.command.result; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record SuccessResult(int lastInputIndex, @NotNull CommandNode lastNode, @Nullable T data) implements CommandResult { + + public static final @NotNull String ID = "WLib:Success"; + + @Override + public boolean success() { + return true; + } + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/BadRequestResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/BadRequestResult.java new file mode 100644 index 00000000..fcdd6d69 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/BadRequestResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record BadRequestResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Error/BadRequest"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNodeExecutorNotFoundResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNodeExecutorNotFoundResult.java new file mode 100644 index 00000000..0d37d793 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNodeExecutorNotFoundResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record CommandNodeExecutorNotFoundResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/NoCommandExecutor"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNotFoundResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNotFoundResult.java new file mode 100644 index 00000000..8d48b768 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/CommandNotFoundResult.java @@ -0,0 +1,57 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public record CommandNotFoundResult(@NotNull String input) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/CommandNotFound"; + + @Override + public int lastInputIndex() { + return 0; + } + + @Override + public @NotNull CommandNode lastNode() { + return DummyNode.INSTANCE; + } + + @Override + public @NotNull String id() { + return ID; + } + + private static final class DummyNode extends CommandNode { + + public static final @NotNull DummyNode INSTANCE = new DummyNode(); + + private DummyNode() { + super("DummyNode", List.of(), $ -> true, null, null, null); + } + + @Override + public @Nullable Object parse(@NotNull String input) { + return null; + } + + @Override + public @NotNull DummyNode withChildren(@NotNull List> children) { + return this; + } + + @Override + public @NotNull DummyNode withExecutor(@Nullable CommandNodeExecutor executor) { + return this; + } + + @Override + public @NotNull CommandNode withPermission(@Nullable String permission) { + return this; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ConflictResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ConflictResult.java new file mode 100644 index 00000000..c97aa295 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ConflictResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record ConflictResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Error/Conflict"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExceptionResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExceptionResult.java new file mode 100644 index 00000000..40de6e8c --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExceptionResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record ExceptionResult(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull Throwable exception) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/Exception"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExtraArgumentsResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExtraArgumentsResult.java new file mode 100644 index 00000000..bb1ed267 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ExtraArgumentsResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record ExtraArgumentsResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/ExtraArguments"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/GenericErrorResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/GenericErrorResult.java new file mode 100644 index 00000000..8bc97952 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/GenericErrorResult.java @@ -0,0 +1,19 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record GenericErrorResult(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull String message) implements UnsuccessResult { + + public static final @NotNull String DEFAULT_MESSAGE = "An error occurred while executing the command."; + public static final @NotNull String ID = "WLib:Unsuccess/Generic"; + + public GenericErrorResult(int lastInputIndex, @NotNull CommandNode lastNode) { + this(lastInputIndex, lastNode, DEFAULT_MESSAGE); + } + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InsufficientArgumentsResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InsufficientArgumentsResult.java new file mode 100644 index 00000000..168c3e28 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InsufficientArgumentsResult.java @@ -0,0 +1,25 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record InsufficientArgumentsResult(@NotNull Command command) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/InsufficientArguments"; + + @Override + public int lastInputIndex() { + return -1; + } + + @Override + public @NotNull CommandNode lastNode() { + return command.getRoot(); + } + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InvalidSenderResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InvalidSenderResult.java new file mode 100644 index 00000000..9e95f4ac --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/InvalidSenderResult.java @@ -0,0 +1,15 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; + +public record InvalidSenderResult(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull CommandSender sender, @NotNull Class> expectedSender) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/InvalidSender"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NoPermissionResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NoPermissionResult.java new file mode 100644 index 00000000..c9b4cf76 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NoPermissionResult.java @@ -0,0 +1,18 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record NoPermissionResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/NoPermission"; + + public @NotNull String permission() { + return lastNode.getPermission(); + } + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NotFoundResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NotFoundResult.java new file mode 100644 index 00000000..97f600e4 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/NotFoundResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record NotFoundResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Error/NotFound"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/OutOfRangeInputResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/OutOfRangeInputResult.java new file mode 100644 index 00000000..91c15c24 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/OutOfRangeInputResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record OutOfRangeInputResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/OutOfRange"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ParseInputExceptionResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ParseInputExceptionResult.java new file mode 100644 index 00000000..0feca4da --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/ParseInputExceptionResult.java @@ -0,0 +1,15 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record ParseInputExceptionResult(int lastInputIndex, @NotNull CommandNode lastNode, @NotNull InputParsingException exception) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Unsuccess/ParseError"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnprocessableContentResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnprocessableContentResult.java new file mode 100644 index 00000000..5a1e8a62 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnprocessableContentResult.java @@ -0,0 +1,14 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import org.jetbrains.annotations.NotNull; + +public record UnprocessableContentResult(int lastInputIndex, @NotNull CommandNode lastNode) implements UnsuccessResult { + + public static final @NotNull String ID = "WLib:Error/UnprocessableContent"; + + @Override + public @NotNull String id() { + return ID; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnsuccessResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnsuccessResult.java new file mode 100644 index 00000000..dd95036b --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/error/UnsuccessResult.java @@ -0,0 +1,19 @@ +package com.wizardlybump17.wlib.command.result.error; + +import com.wizardlybump17.wlib.command.result.CommandResult; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +public interface UnsuccessResult extends CommandResult { + + @Override + default boolean success() { + return false; + } + + @Contract("-> null") + @Override + default @Nullable T data() { + return null; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/result/success/NoContentResult.java b/commands/src/main/java/com/wizardlybump17/wlib/command/result/success/NoContentResult.java new file mode 100644 index 00000000..69a530e3 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/result/success/NoContentResult.java @@ -0,0 +1,26 @@ +package com.wizardlybump17.wlib.command.result.success; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public record NoContentResult(int lastInputIndex, @NotNull CommandNode lastNode) implements CommandResult { + + private static final @NotNull String ID = "WLib:Success/NoContent"; + + @Override + public @NotNull String id() { + return ID; + } + + @Override + public boolean success() { + return true; + } + + @Override + public @Nullable T data() { + return null; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/sender/BasicCommandSender.java b/commands/src/main/java/com/wizardlybump17/wlib/command/sender/BasicCommandSender.java index 8f2d8f86..11becfc8 100644 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/sender/BasicCommandSender.java +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/sender/BasicCommandSender.java @@ -3,10 +3,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; public class BasicCommandSender implements CommandSender { @@ -39,6 +41,19 @@ public void sendMessage(String... messages) { sendMessage(String.join("\n", messages)); } + @Override + public void sendMessage(@Nullable Object message) { + sendMessage(String.valueOf(message)); + } + + @Override + public void sendMessage(@Nullable Object @Nullable ... messages) { + if (messages == null) + sendMessage((Object) null); + else + sendMessage(Arrays.stream(messages).map(String::valueOf).collect(Collectors.joining("\n"))); + } + @Override public @NotNull String getName() { return name; diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/sender/CommandSender.java b/commands/src/main/java/com/wizardlybump17/wlib/command/sender/CommandSender.java index 0786ac74..80941403 100644 --- a/commands/src/main/java/com/wizardlybump17/wlib/command/sender/CommandSender.java +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/sender/CommandSender.java @@ -1,6 +1,7 @@ package com.wizardlybump17.wlib.command.sender; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.UUID; @@ -20,6 +21,10 @@ public interface CommandSender { void sendMessage(String... messages); + void sendMessage(@Nullable Object message); + + void sendMessage(@Nullable Object @Nullable ... messages); + String getName(); boolean hasPermission(String permission); diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/AbstractValuesSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/AbstractValuesSuggester.java new file mode 100644 index 00000000..a1c895a6 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/AbstractValuesSuggester.java @@ -0,0 +1,47 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; + +public abstract class AbstractValuesSuggester implements ValuesSuggester { + + private final @NotNull List suggestions; + + public AbstractValuesSuggester(@NotNull List suggestions) { + this.suggestions = List.copyOf(suggestions); + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return suggestions; + } + + @Override + public @NotNull List values() { + return suggestions; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AbstractValuesSuggester that = (AbstractValuesSuggester) object; + return Objects.equals(suggestions, that.suggestions); + } + + @Override + public int hashCode() { + return Objects.hashCode(suggestions); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "suggestions=" + suggestions + + '}'; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/CachingSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/CachingSuggester.java new file mode 100644 index 00000000..a64681ea --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/CachingSuggester.java @@ -0,0 +1,62 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.Collections; +import java.util.List; + +public class CachingSuggester implements Suggester { + + public static final long DEFAULT_EXPIRATION_MILLIS = 30 * 1000; + + private final @NotNull Suggester delegate; + private final long expirationMillis; + private @NotNull @UnmodifiableView List cache = List.of(); + private long lastUpdateMillis; + + private CachingSuggester(@NotNull Suggester delegate, long expirationMillis) { + this.delegate = delegate; + this.expirationMillis = expirationMillis; + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) throws SuggesterException { + if (lastUpdateMillis + expirationMillis <= System.currentTimeMillis()) { + List suggestions = delegate.getSuggestions(sender, input, current, currentNode); + cache = Collections.unmodifiableList(suggestions); + lastUpdateMillis = System.currentTimeMillis(); + return suggestions; + } + + return cache; + } + + @Override + public @NotNull String getStringRepresentation(@NotNull T value) { + return delegate.getStringRepresentation(value); + } + + public long expirationMillis() { + return expirationMillis; + } + + public @NotNull @UnmodifiableView List cache() { + return cache; + } + + private long lastUpdateMillis() { + return lastUpdateMillis; + } + + public static @NotNull CachingSuggester of(@NotNull Suggester suggester, long expirationMillis) { + return new CachingSuggester<>(suggester, expirationMillis); + } + + public static @NotNull CachingSuggester of(@NotNull Suggester suggester) { + return new CachingSuggester<>(suggester, DEFAULT_EXPIRATION_MILLIS); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/LiteralSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/LiteralSuggester.java new file mode 100644 index 00000000..6df01e63 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/LiteralSuggester.java @@ -0,0 +1,47 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; + +public final class LiteralSuggester implements Suggester { + + private final @NotNull List suggestions; + + private LiteralSuggester(@NotNull String value) { + this.suggestions = List.of(value); + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) throws SuggesterException { + return suggestions; + } + + public static @NotNull LiteralSuggester of(@NotNull String value) { + return new LiteralSuggester(value); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + LiteralSuggester that = (LiteralSuggester) o; + return Objects.equals(suggestions, that.suggestions); + } + + @Override + public int hashCode() { + return Objects.hashCode(suggestions); + } + + @Override + public String toString() { + return "LiteralSuggester{" + + "value=" + suggestions.getFirst() + + '}'; + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/Suggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/Suggester.java new file mode 100644 index 00000000..f7ca1ed3 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/Suggester.java @@ -0,0 +1,39 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Function; + +public interface Suggester { + + @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) throws SuggesterException; + + default @NotNull String getStringRepresentation(@NotNull T value) { + return value.toString(); + } + + default boolean needsEscape() { + return false; + } + + static ValuesSuggester.@NotNull Values values(@NotNull List values, @Nullable Function stringRepresentation, boolean needsEscape) { + return new ValuesSuggester.Values<>(values, stringRepresentation, needsEscape); + } + + static ValuesSuggester.@NotNull Values values(@NotNull List values, @Nullable Function stringRepresentation) { + return Suggester.values(values, stringRepresentation, false); + } + + static ValuesSuggester.@NotNull Values values(@NotNull List values, boolean needsEscape) { + return Suggester.values(values, null, needsEscape); + } + + static ValuesSuggester.@NotNull Values values(@NotNull List values) { + return Suggester.values(values, null, false); + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/ValuesSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/ValuesSuggester.java new file mode 100644 index 00000000..5ac20908 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/ValuesSuggester.java @@ -0,0 +1,78 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +public interface ValuesSuggester extends Suggester { + + @NotNull List values(); + + final class Values implements ValuesSuggester { + + private final @NotNull List values; + private final @Nullable Function stringRepresentationFunction; + private final boolean needsEscape; + + Values(@NotNull List values, @Nullable Function stringRepresentationFunction, boolean needsEscape) { + this.values = List.copyOf(values); + this.stringRepresentationFunction = stringRepresentationFunction; + this.needsEscape = needsEscape; + } + + @Override + public @NotNull @Unmodifiable List values() { + return values; + } + + public @Nullable Function stringRepresentation() { + return stringRepresentationFunction; + } + + @Override + public boolean needsEscape() { + return needsEscape; + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) throws SuggesterException { + return values; + } + + @Override + public @NotNull String getStringRepresentation(@NotNull T value) { + return stringRepresentationFunction == null ? ValuesSuggester.super.getStringRepresentation(value) : stringRepresentationFunction.apply(value); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Values values1 = (Values) o; + return Objects.equals(values, values1.values) + && Objects.equals(stringRepresentationFunction, values1.stringRepresentationFunction) + && needsEscape == values1.needsEscape; + } + + @Override + public int hashCode() { + return Objects.hash(values, stringRepresentationFunction, needsEscape); + } + + @Override + public String toString() { + return "ValuesSuggester$Values{" + + "values=" + values + + ", stringRepresentationFunction=" + stringRepresentationFunction + + ", needsEscape=" + needsEscape + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/EnumSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/EnumSuggester.java new file mode 100644 index 00000000..66cd5e27 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/EnumSuggester.java @@ -0,0 +1,66 @@ +package com.wizardlybump17.wlib.command.suggestion.object; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; + +public interface EnumSuggester> extends Suggester { + + @Override + default @NotNull String getStringRepresentation(@NotNull E value) { + return value.name(); + } + + @NotNull Class enumType(); + + static > @NotNull Any any(@NotNull Class enumType) { + return new Any<>(enumType); + } + + final class Any> implements EnumSuggester { + + private final @NotNull List cache; + private final @NotNull Class enumType; + + private Any(@NotNull Class enumType) { + this.enumType = enumType; + this.cache = List.of(enumType.getEnumConstants()); + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) throws SuggesterException { + return cache; + } + + @Override + public @NotNull Class enumType() { + return enumType; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Any any = (Any) o; + return Objects.equals(cache, any.cache) && Objects.equals(enumType, any.enumType); + } + + @Override + public int hashCode() { + return Objects.hash(cache, enumType); + } + + @Override + public String toString() { + return "EnumSuggester$Any{" + + "cache=" + cache + + ", enumType=" + enumType + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/JsonElementSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/JsonElementSuggester.java new file mode 100644 index 00000000..6c6d514a --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/JsonElementSuggester.java @@ -0,0 +1,123 @@ +package com.wizardlybump17.wlib.command.suggestion.object; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.Objects; + +public interface JsonElementSuggester extends Suggester { + + @NotNull Gson gson(); + + @Override + default boolean needsEscape() { + return true; + } + + static @NotNull Values values(@NotNull Gson gson, @NotNull List elements) { + return new Values(gson, elements); + } + + static @NotNull Values values(@NotNull List elements) { + return new Values(Values.GSON, elements); + } + + static @NotNull Value value(@NotNull Gson gson, @NotNull JsonElement value) { + return new Value(gson, value); + } + + static @NotNull Value value(@NotNull JsonElement value) { + return new Value(Values.GSON, value); + } + + final class Values extends AbstractValuesSuggester implements JsonElementSuggester { + + private static final @NotNull Gson GSON = new Gson(); + + private final @NotNull Gson gson; + + private Values(@NotNull Gson gson, @NotNull List suggestions) { + super(suggestions); + this.gson = gson; + } + + @Override + public @NotNull Gson gson() { + return gson; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) return false; + JsonElementSuggester.Values values = (JsonElementSuggester.Values) o; + return Objects.equals(gson, values.gson); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), gson); + } + + @Override + public String toString() { + return "JsonElementSuggester$Values{" + + "gson=" + gson + + "} " + super.toString(); + } + } + + final class Value implements JsonElementSuggester { + + private final @NotNull Gson gson; + private final @NotNull @Unmodifiable List list; + + private Value(@NotNull Gson gson, @NotNull JsonElement value) { + this.gson = gson; + this.list = List.of(value); + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return list; + } + + @Override + public @NotNull Gson gson() { + return gson; + } + + public @NotNull JsonElement value() { + return list.getFirst(); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + Value value = (Value) o; + return Objects.equals(gson, value.gson) && Objects.equals(list, value.list); + } + + @Override + public int hashCode() { + return Objects.hash(gson, list); + } + + @Override + public String toString() { + return "Value{" + + "gson=" + gson + + ", value=" + value() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/UUIDSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/UUIDSuggester.java new file mode 100644 index 00000000..062a1c24 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/object/UUIDSuggester.java @@ -0,0 +1,37 @@ +package com.wizardlybump17.wlib.command.suggestion.object; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; + +public interface UUIDSuggester extends Suggester { + + static @NotNull Values value(@NotNull UUID value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(UUID... values) { + return new Values(List.of(values)); + } + + final class Values extends AbstractValuesSuggester implements UUIDSuggester { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "UUIDSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/BooleanSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/BooleanSuggester.java new file mode 100644 index 00000000..e79cd8d6 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/BooleanSuggester.java @@ -0,0 +1,79 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface BooleanSuggester extends PrimitiveSuggester { + + static @NotNull True trueValue() { + return True.INSTANCE; + } + + static @NotNull False falseValue() { + return False.INSTANCE; + } + + static @NotNull Any any() { + return Any.INSTANCE; + } + + final class True implements BooleanSuggester { + + private static final @NotNull List LIST = List.of(true); + private static final @NotNull True INSTANCE = new True(); + + private True() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return LIST; + } + + @Override + public String toString() { + return "BooleanSuggester$True{}"; + } + } + + final class False implements BooleanSuggester { + + private static final @NotNull List LIST = List.of(false); + private static final @NotNull False INSTANCE = new False(); + + private False() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return LIST; + } + + @Override + public String toString() { + return "BooleanSuggester$False{}"; + } + } + + final class Any implements BooleanSuggester { + + private static final @NotNull List LIST = List.of(true, false); + private static final @NotNull Any INSTANCE = new Any(); + + private Any() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return LIST; + } + + @Override + public String toString() { + return "BooleanSuggester$Any{}"; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/CharacterSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/CharacterSuggester.java new file mode 100644 index 00000000..a4b10a12 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/CharacterSuggester.java @@ -0,0 +1,58 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface CharacterSuggester extends PrimitiveSuggester { + + static @NotNull Values value(char value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(char @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (char value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(char from, char to, int amount) { + if (to - from < amount) { + Character[] values = new Character[(int) (to - from)]; + for (int i = 0; i < values.length; i++) + values[i] = (char) (to - from - i); + return new Values(List.of(values)); + } + + char part = (char) ((to - from) / amount); + List values = new ArrayList<>(amount); + for (char i = 0; i < amount; i++) + values.add((char) (from + part * amount)); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(char from, char to) { + return range(from, to, 4); + } + + final class Values extends AbstractValuesSuggester implements CharacterSuggester { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "CharacterSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/PrimitiveSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/PrimitiveSuggester.java new file mode 100644 index 00000000..568178db --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/PrimitiveSuggester.java @@ -0,0 +1,6 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive; + +import com.wizardlybump17.wlib.command.suggestion.Suggester; + +public interface PrimitiveSuggester extends Suggester { +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ByteSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ByteSuggester.java new file mode 100644 index 00000000..c2f2ccde --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ByteSuggester.java @@ -0,0 +1,93 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface ByteSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(byte value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(byte @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (byte value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(byte from, byte to, int amount) { + if (to - from < amount) { + Byte[] values = new Byte[to - from]; + for (int i = 0; i < values.length; i++) + values[i] = (byte) (to - from - i); + return new Values(List.of(values)); + } + + byte part = (byte) ((to - from) / amount); + List values = new ArrayList<>(amount); + for (int i = 0; i < amount; i++) + values.add((byte) (from + part * amount)); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(byte from, byte to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return Values.POSITIVE; + } + + static @NotNull Values negative() { + return Values.NEGATIVE; + } + + static @NotNull Values unlimited() { + return Values.UNLIMITED; + } + + final class Values extends AbstractValuesSuggester implements ByteSuggester { + + private static final @NotNull ByteSuggester.Values POSITIVE = new ByteSuggester.Values(List.of( + (byte) 0, + (byte) 50, + (byte) 100, + (byte) 127 + )); + private static final @NotNull ByteSuggester.Values NEGATIVE = new ByteSuggester.Values(List.of( + (byte) -128, + (byte) -100, + (byte) -50, + (byte) -1 + )); + private static final @NotNull ByteSuggester.Values UNLIMITED = new ByteSuggester.Values(List.of( + (byte) -128, + (byte) -100, + (byte) -50, + (byte) 0, + (byte) 50, + (byte) 100, + (byte) 127 + )); + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "ByteSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/DoubleSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/DoubleSuggester.java new file mode 100644 index 00000000..58e45291 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/DoubleSuggester.java @@ -0,0 +1,90 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface DoubleSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(double value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(double @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (double value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(double from, double to, int amount) { + double part = (to - from) / amount; + List values = new ArrayList<>(amount); + for (double i = 0; i < amount; i++) + values.add(from + part * amount); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(double from, double to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return new Values(List.of( + 0.0, + 50.5, + 200.5, + 3000.5, + 50000.5, + 100000.5 + )); + } + + static @NotNull Values negative() { + return new Values(List.of( + -100000.5, + -50000.5, + -3000.5, + -200.5, + -50.5, + -1.0 + )); + } + + static @NotNull Values unlimited() { + return new Values(List.of( + -100000.5, + -50000.5, + -3000.5, + -200.5, + -50.5, + 0.0, + 50.5, + 200.5, + 3000.5, + 50000.5, + 100000.5 + )); + } + + final class Values extends AbstractValuesSuggester implements DoubleSuggester { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "DoubleSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/FloatSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/FloatSuggester.java new file mode 100644 index 00000000..d72d740d --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/FloatSuggester.java @@ -0,0 +1,90 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface FloatSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(float value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(float @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (float value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(float from, float to, int amount) { + float part = (to - from) / amount; + List values = new ArrayList<>(amount); + for (float i = 0; i < amount; i++) + values.add(from + part * amount); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(float from, float to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return new Values(List.of( + 0.0F, + 50.5F, + 200.5F, + 3000.5F, + 50000.5F, + 100000.5F + )); + } + + static @NotNull Values negative() { + return new Values(List.of( + -100000.5F, + -50000.5F, + -3000.5F, + -200.5F, + -50.5F, + -1.0F + )); + } + + static @NotNull Values unlimited() { + return new Values(List.of( + -100000.5F, + -50000.5F, + -3000.5F, + -200.5F, + -50.5F, + 0.0F, + 50.5F, + 200.5F, + 3000.5F, + 50000.5F, + 100000.5F + )); + } + + final class Values extends AbstractValuesSuggester implements FloatSuggester { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "FloatSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/IntegerSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/IntegerSuggester.java new file mode 100644 index 00000000..64beda11 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/IntegerSuggester.java @@ -0,0 +1,99 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +public interface IntegerSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(int value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(int @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (int value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(int from, int to, int amount) { + if (to - from < amount) + return new Values(IntStream.rangeClosed(from, to).boxed().toList()); + + int part = (to - from) / amount; + List values = new ArrayList<>(amount); + for (int i = 0; i < amount; i++) + values.add(from + part * i); + values.add(to); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(int from, int to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return Values.POSITIVE; + } + + static @NotNull Values negative() { + return Values.NEGATIVE; + } + + static @NotNull Values unlimited() { + return Values.UNLIMITED; + } + + final class Values extends AbstractValuesSuggester implements IntegerSuggester { + + private static final @NotNull IntegerSuggester.Values POSITIVE = new IntegerSuggester.Values(List.of( + 0, + 50, + 200, + 3000, + 50000, + 100000 + )); + private static final @NotNull IntegerSuggester.Values NEGATIVE = new IntegerSuggester.Values(List.of( + -100000, + -50000, + -3000, + -200, + -50, + -1 + )); + private static final @NotNull IntegerSuggester.Values UNLIMITED = new IntegerSuggester.Values(List.of( + -100000, + -50000, + -3000, + -200, + -50, + 0, + 50, + 200, + 3000, + 50000, + 100000 + )); + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "IntegerSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/LongSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/LongSuggester.java new file mode 100644 index 00000000..5913f0f7 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/LongSuggester.java @@ -0,0 +1,101 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface LongSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(long value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(long @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (long value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(long from, long to, int amount) { + if (to - from < amount) { + Long[] longs = new Long[(int) (to - from)]; + for (int i = 0; i < longs.length; i++) + longs[i] = to - from - i; + return new Values(List.of(longs)); + } + + long part = (to - from) / amount; + List values = new ArrayList<>(amount); + for (long i = 0; i < amount; i++) + values.add(from + part * amount); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(long from, long to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return Values.POSITIVE; + } + + static @NotNull Values negative() { + return Values.NEGATIVE; + } + + static @NotNull Values unlimited() { + return Values.UNLIMITED; + } + + final class Values extends AbstractValuesSuggester implements LongSuggester { + + private static final @NotNull LongSuggester.Values POSITIVE = new LongSuggester.Values(List.of( + 0L, + 50L, + 200L, + 3000L, + 50000L, + 100000L + )); + private static final @NotNull LongSuggester.Values NEGATIVE = new LongSuggester.Values(List.of( + -100000L, + -50000L, + -3000L, + -200L, + -50L, + -1L + )); + private static final @NotNull LongSuggester.Values UNLIMITED = new LongSuggester.Values(List.of( + -100000L, + -50000L, + -3000L, + -200L, + -50L, + 0L, + 50L, + 200L, + 3000L, + 50000L, + 100000L + )); + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "LongSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/NumberSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/NumberSuggester.java new file mode 100644 index 00000000..de51aecb --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/NumberSuggester.java @@ -0,0 +1,6 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.Suggester; + +public interface NumberSuggester extends Suggester { +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ShortSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ShortSuggester.java new file mode 100644 index 00000000..0f6fd57a --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/primitive/number/ShortSuggester.java @@ -0,0 +1,97 @@ +package com.wizardlybump17.wlib.command.suggestion.primitive.number; + +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface ShortSuggester extends PrimitiveSuggester, NumberSuggester { + + static @NotNull Values value(short value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(short @NotNull ... values) { + List list = new ArrayList<>(values.length); + for (short value : values) + list.add(value); + return new Values(List.copyOf(list)); + } + + static @NotNull Values range(short from, short to, int amount) { + if (to - from < amount) { + Short[] values = new Short[to - from]; + for (int i = 0; i < values.length; i++) + values[i] = (short) (to - from - i); + return new Values(List.of(values)); + } + + short part = (short) ((to - from) / amount); + List values = new ArrayList<>(amount); + for (short i = 0; i < amount; i++) + values.add((short) (from + part * amount)); + return new Values(List.copyOf(values)); + } + + static @NotNull Values range(short from, short to) { + return range(from, to, 4); + } + + static @NotNull Values positive() { + return Values.POSITIVE; + } + + static @NotNull Values negative() { + return Values.NEGATIVE; + } + + static @NotNull Values unlimited() { + return Values.UNLIMITED; + } + + final class Values extends AbstractValuesSuggester implements ShortSuggester { + + private static final @NotNull ShortSuggester.Values POSITIVE = new ShortSuggester.Values(List.of( + (short) 0, + (short) 50, + (short) 100, + (short) 3000, + (short) 32767 + )); + private static final @NotNull ShortSuggester.Values NEGATIVE = new ShortSuggester.Values(List.of( + (short) -32768, + (short) -3000, + (short) -100, + (short) -50, + (short) -1 + )); + private static final @NotNull ShortSuggester.Values UNLIMITED = new ShortSuggester.Values(List.of( + (short) -32768, + (short) -3000, + (short) -100, + (short) -50, + (short) 0, + (short) 50, + (short) 100, + (short) 3000, + (short) 32767 + )); + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "ShortSuggester$Values{" + + "values=" + values() + + '}'; + } + } +} diff --git a/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/string/StringSuggester.java b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/string/StringSuggester.java new file mode 100644 index 00000000..7f004cf5 --- /dev/null +++ b/commands/src/main/java/com/wizardlybump17/wlib/command/suggestion/string/StringSuggester.java @@ -0,0 +1,57 @@ +package com.wizardlybump17.wlib.command.suggestion.string; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.AbstractValuesSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.PrimitiveSuggester; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface StringSuggester extends PrimitiveSuggester { + + static @NotNull Values value(String value) { + return new Values(List.of(value)); + } + + static @NotNull Values values(@NotNull List values) { + return new Values(values); + } + + static @NotNull Values values(String @NotNull ... values) { + return new Values(List.of(values)); + } + + static @NotNull Any any() { + return Any.INSTANCE; + } + + @Override + default boolean needsEscape() { + return true; + } + + final class Values extends AbstractValuesSuggester implements StringSuggester { + + Values(@NotNull List values) { + super(values); + } + + @Override + public String toString() { + return "StringSuggester$Values{" + + "values=" + values() + + '}'; + } + } + + final class Any implements StringSuggester { + + private static final @NotNull Any INSTANCE = new Any(); + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return List.of(current); + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/CommandTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/CommandTests.java new file mode 100644 index 00000000..c25744d5 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/CommandTests.java @@ -0,0 +1,697 @@ +package com.wizardlybump17.wlib.test.command; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.primitive.number.AllowedIntegerInputs; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.IntegerCommandNode; +import com.wizardlybump17.wlib.command.node.string.StringCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.result.error.*; +import com.wizardlybump17.wlib.command.sender.BasicCommandSender; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.util.CollectionUtil; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class CommandTests { + + static final @NotNull Consumer SENDER_MESSAGE_CONSUMER = System.out::println; + static final @NotNull CommandSender CHAD_SENDER = new BasicCommandSender<>(new Object(), "Chad", UUID.nameUUIDFromBytes("Chad".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> true); + static final @NotNull CommandSender BETA_SENDER = new BasicCommandSender<>(new Object(), "Beta", UUID.nameUUIDFromBytes("Beta".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> false); + + @Test + void testSuccessHello() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + null + ); + Command command = new Command(helloNode); + + CommandResult expected = CommandResult.successful(0, helloNode, "hello"); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuccessHelloWorld() { + LiteralCommandNode worldNode = new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ); + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(worldNode), + null, + null + ) + ); + + CommandResult expected = CommandResult.successful(1, worldNode, "hello world"); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "world")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuccessHelloWorldHi() { + LiteralCommandNode hiNode = new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hello world hi"), + null + ); + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(hiNode), + null, + null + ) + ), + null, + null + ) + ); + + CommandResult expected = CommandResult.successful(2, hiNode, "hello world hi"); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "world", "hi")); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Multiple children 0 (hello, hi): success") + void test0() { + LiteralCommandNode hiNode = new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hello hi"), + null + ); + Command command = new Command(new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + hiNode + ), + null, + null + )); + + CommandResult expected = CommandResult.successful(1, hiNode, "hello hi"); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "hi")); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Multiple children 1 (hello, hi, world): success") + void test1() { + LiteralCommandNode hiWorldNode = new LiteralCommandNode( + "world0", + List.of(), + context -> CommandResult.successful(context, "hello hi world"), + null + ); + Command command = new Command(new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + new LiteralCommandNode( + "hi", + List.of(hiWorldNode), + null, + null + ) + ), + null, + null + )); + + CommandResult expected = CommandResult.successful(2, hiWorldNode, "hello hi world"); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "hi", "world0")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testExtraArguments0() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + null + ); + Command command = new Command(helloNode); + + ExtraArgumentsResult expected = CommandResult.extraArguments(1, helloNode); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "world")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testExtraArguments1() { + LiteralCommandNode worldNode = new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ); + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(worldNode), + null, + null + ) + ); + + ExtraArgumentsResult expected = CommandResult.extraArguments(2, worldNode); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "world", "hi")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testExtraArguments2() { + LiteralCommandNode hiWorldNode = new LiteralCommandNode( + "world0", + List.of(), + context -> CommandResult.successful(context, "hello hi world"), + null + ); + Command command = new Command(new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + new LiteralCommandNode( + "hi", + List.of(hiWorldNode), + null, + null + ) + ), + null, + null + )); + + ExtraArgumentsResult expected = CommandResult.extraArguments(3, hiWorldNode); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "hi", "world0", "extra")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testException() { + RuntimeException helloException = new RuntimeException("hello"); + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> { + throw helloException; + }, + null + ); + Command command = new Command(helloNode); + + ExceptionResult expected = CommandResult.exceptionally(0, helloNode, helloException); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testInsufficientArguments() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + null + ) + ); + + InsufficientArgumentsResult expected = CommandResult.insufficientArguments(command); + CommandResult actual = command.execute(CHAD_SENDER, List.of()); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testNoPermission() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ); + Command command = new Command(helloNode); + + NoPermissionResult expected = CommandResult.noPermission(0, helloNode); + CommandResult actual = command.execute(BETA_SENDER, List.of("hello")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testOutOfRange() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + null + ); + Command command = new Command(helloNode); + + OutOfRangeInputResult expected = CommandResult.outOfRangeInput(0, helloNode); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello0")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testParseInputException() { + IntegerCommandNode worldNode = new IntegerCommandNode( + "world", + List.of(), + AllowedIntegerInputs.value(10), + null, + context -> CommandResult.successful(context, 10), + null + ); + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(worldNode), + null, + null + ) + ); + + ParseInputExceptionResult expected = CommandResult.parseInputException(1, worldNode, new InputParsingException("Could not parse as int: world", new NumberFormatException("For input string: \"world\""))); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello", "world")); + + Assertions.assertInstanceOf(ParseInputExceptionResult.class, actual); + + ParseInputExceptionResult actualException = (ParseInputExceptionResult) actual; + Assertions.assertEquals(expected.lastInputIndex(), actual.lastInputIndex()); + Assertions.assertEquals(expected.lastNode(), actual.lastNode()); + Assertions.assertEquals(expected.exception().getMessage(), actualException.exception().getMessage()); + Assertions.assertEquals(expected.exception().getCause().getMessage(), actualException.exception().getCause().getMessage()); + } + + @Test + void testCommandNodeExecutorNotFound() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + null, + null + ); + Command command = new Command(helloNode); + + CommandNodeExecutorNotFoundResult expected = CommandResult.noCommandNodeExecutor(0, helloNode); + CommandResult actual = command.execute(CHAD_SENDER, List.of("hello")); + + Assertions.assertEquals(expected, actual); + } + + /** + *

+ * The CommandNode#merge(CommandNode) method wasnt merging the executor and permission. + * This test will ensure that we have the correct executor and permission after merging. + *

+ */ + @Test + void testMerge() { + CommandNodeExecutor test0Executor = context -> CommandResult.successful(context, "It works 0!"); + CommandNodeExecutor test1Executor = context -> CommandResult.successful(context, "It works 1!"); + + CommandNode expected = new LiteralCommandNode( + "test0", + List.of( + new LiteralCommandNode( + "test1", + List.of(), + test1Executor, + "permission1" + ) + ), + test0Executor, + "permission0" + ); + CommandNode actual = new LiteralCommandNode( + "test0", + List.of(), + null, + null + ).merge(new LiteralCommandNode( + "test0", + List.of( + new LiteralCommandNode( + "test1", + List.of(), + test1Executor, + "permission1" + ) + ), + test0Executor, + "permission0" + )); + + Assertions.assertEquals(expected, actual); + + Assertions.assertEquals( + new TestMerge0( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of(), + null, + null + ) + ).merge( + new TestMerge0( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + null, + null + ) + ), + null, + null + ) + ) + ) + ); + + Assertions.assertEquals( + new TestMerge1( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hi", + List.of(), + null, + null + ) + ).merge( + new TestMerge1( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + null, + null + ) + ), + null, + null + ) + ) + ) + ); + + Assertions.assertEquals( + new TestMerge0( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + new TestMerge0( + new LiteralCommandNode( + "test", + List.of(), + null, + null + ) + ).merge( + new TestMerge1( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + null, + null + ) + ) + ) + ); + } + + static class TestMerge0 extends Command { + + public TestMerge0(@NotNull LiteralCommandNode root) { + super(root); + } + + @Override + public @NotNull Command merge(@NotNull Command other) { + return new TestMerge0(getRoot().merge(other.getRoot())); + } + } + + static class TestMerge1 extends Command { + + public TestMerge1(@NotNull LiteralCommandNode root) { + super(root); + } + + @Override + public @NotNull Command merge(@NotNull Command other) { + return new TestMerge1(getRoot().merge(other.getRoot())); + } + } + + @Test + void testCommandArguments() { + { + LiteralCommandNode world = new LiteralCommandNode( + "world", + List.of(), + context -> { + Assertions.assertEquals(Map.of(), context.arguments().getArguments()); + return CommandResult.successful(context, "Hello World"); + }, + null + ); + CommandResult result = new Command( + new LiteralCommandNode( + "hello", + List.of(world), + null, + null + ) + ).execute(CHAD_SENDER, List.of("hello", "world")); + + Assertions.assertEquals(CommandResult.successful(1, world, "Hello World"), result); + } + + { + AtomicReference worldReference = new AtomicReference<>(); + IntegerCommandNode world = new IntegerCommandNode( + "world", + List.of(), + AllowedIntegerInputs.unlimited(), + null, + context -> { + Assertions.assertEquals(Map.of("world", new CommandContext.CommandNodeArgument<>(worldReference.get(), "10", 10)), context.arguments().getArguments()); + return CommandResult.successful(context, context.arguments().getArgumentData("world").orElseThrow()); + }, + null + ); + worldReference.set(world); + + CommandResult result = new Command( + new LiteralCommandNode( + "hello", + List.of(world), + null, + null + ) + ).execute(CHAD_SENDER, List.of("hello", "10")); + + Assertions.assertEquals(CommandResult.successful(1, world, 10), result); + } + + { + AtomicReference worldReference = new AtomicReference<>(); + LiteralCommandNode test = new LiteralCommandNode( + "test", + List.of(), + context -> { + Assertions.assertEquals(Map.of("world", new CommandContext.CommandNodeArgument<>(worldReference.get(), "10", 10)), context.arguments().getArguments()); + return CommandResult.successful(context, "Hello " + context.arguments().getArgumentData("world").orElseThrow() + " world"); + }, + null + ); + IntegerCommandNode world = new IntegerCommandNode( + "world", + List.of(test), + AllowedIntegerInputs.unlimited(), + null, + null, + null + ); + worldReference.set(world); + + CommandResult result = new Command( + new LiteralCommandNode( + "hello", + List.of(world), + null, + null + ) + ).execute(CHAD_SENDER, List.of("hello", "10", "test")); + + Assertions.assertEquals(CommandResult.successful(2, test, "Hello 10 world"), result); + } + } + + @Test + void testNullArgumentsSuccess() { + Command command = new Command( + new LiteralCommandNode( + "test", + List.of( + new StringCommandNode( + "nullable0", + List.of(), + AllowedStringInputs.anyNullable(), + null, + context -> CommandResult.successful(context, context.arguments().getArgumentData("nullable0").orElse(null)), + null + ) + ), + null, + null + ) + ); + + Assertions.assertEquals( + CommandResult.successful(1, command.findNode("nullable0"), "Hello World"), + command.execute(CHAD_SENDER, List.of("test", "Hello World")) + ); + Assertions.assertEquals( + CommandResult.successful(1, command.findNode("nullable0"), null), + command.execute(CHAD_SENDER, CollectionUtil.listOf("test", null)) + ); + } + + @Test + void testNullArgumentsError() { + Command command = new Command( + new LiteralCommandNode( + "test", + List.of( + new StringCommandNode( + "nullable0", + List.of(), + AllowedStringInputs.anyNotNull(), + null, + context -> CommandResult.successful(context, context.arguments().getArgumentData("nullable0").orElse(null)), + null + ) + ), + null, + null + ) + ); + + Assertions.assertEquals( + CommandResult.successful(1, command.findNode("nullable0"), "Hello World"), + command.execute(CHAD_SENDER, List.of("test", "Hello World")) + ); + Assertions.assertEquals( + CommandResult.outOfRangeInput(1, command.findNode("nullable0")), + command.execute(CHAD_SENDER, CollectionUtil.listOf("test", null)) + ); + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/SuggestionTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/SuggestionTests.java new file mode 100644 index 00000000..216b076a --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/SuggestionTests.java @@ -0,0 +1,797 @@ +package com.wizardlybump17.wlib.test.command; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.input.object.AllowedUUIDInputs; +import com.wizardlybump17.wlib.command.input.primitive.number.*; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.object.UUIDCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.*; +import com.wizardlybump17.wlib.command.node.string.StringCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.BasicCommandSender; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.object.UUIDSuggester; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.*; +import com.wizardlybump17.wlib.command.suggestion.string.StringSuggester; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +class SuggestionTests { + + static final @NotNull Consumer SENDER_MESSAGE_CONSUMER = System.out::println; + static final @NotNull CommandSender CHAD_SENDER = new BasicCommandSender<>(new Object(), "Chad", UUID.nameUUIDFromBytes("Chad".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> true); + static final @NotNull CommandSender BETA_SENDER = new BasicCommandSender<>(new Object(), "Beta", UUID.nameUUIDFromBytes("Beta".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> false); + + @Test + @DisplayName("Test Success: root 0") + void testSuccess0() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ) + ), + null, + null + ) + ); + + List expected = List.of("hello"); + List actual = Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of())); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Test Success: root 1") + void testSuccess1() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ) + ), + null, + null + ) + ); + + List expected = List.of("hello"); + List actual = Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("he"))); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Test Success: children 0") + void testSuccess2() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hello hi"), + null + ), + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hello there"), + null + ) + ), + null, + null + ) + ); + + List expected = List.of("world", "hi", "there"); + List actual = Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", ""))); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Test Success: children 1") + void testSuccess3() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hello hi"), + null + ), + new IntegerCommandNode( + "there", + List.of(), + AllowedIntegerInputs.range(1, 10), + IntegerSuggester.range(1, 10), + context -> CommandResult.successful(context, "hello there"), + null + ) + ), + null, + null + ) + ); + + List expected = List.of("world", "hi", "1", "3", "5", "7", "10"); + List actual = Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", ""))); + + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Test Success: children 2") + void testSuccess4() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + context -> CommandResult.successful(context, "hello world"), + null + ), + new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hello hi"), + null + ), + new IntegerCommandNode( + "there", + List.of(), + AllowedIntegerInputs.range(1, 10), + IntegerSuggester.range(1, 10), + context -> CommandResult.successful(context, "hello there"), + null + ), + new LiteralCommandNode( + "happy", + List.of(), + context -> CommandResult.successful(context, "hello happy"), + null + ) + ), + null, + null + ) + ); + + List expected = List.of("world", "hi", "1", "3", "5", "7", "10", "happy"); + List actual = Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "h"))); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testDefaultSuggestions() { + //literal + Assertions.assertEquals( + List.of("hello"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of(), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of())) + ); + Assertions.assertEquals( + List.of("hello"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of(), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of(""))) + ); + Assertions.assertEquals( + List.of("hello"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of(), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hel"))) + ); + + //byte + Assertions.assertEquals( + List.of("-128", "-100", "-50", "0", "50", "100", "127"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ByteCommandNode( + "world", + List.of(), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-128", "-100", "-50", "0", "50", "100", "127"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ByteCommandNode( + "world", + List.of(), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-128", "-100", "-50", "0", "50", "100", "127"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ByteCommandNode( + "world", + List.of(), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //short + Assertions.assertEquals( + List.of("-32768", "-3000", "-100", "-50", "0", "50", "100", "3000", "32767"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ShortCommandNode( + "world", + List.of(), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-32768", "-3000", "-100", "-50", "0", "50", "100", "3000", "32767"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ShortCommandNode( + "world", + List.of(), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-32768", "-3000", "-100", "-50", "0", "50", "100", "3000", "32767"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new ShortCommandNode( + "world", + List.of(), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //int + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "world", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "world", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "world", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //long + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new LongCommandNode( + "world", + List.of(), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new LongCommandNode( + "world", + List.of(), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-100000", "-50000", "-3000", "-200", "-50", "0", "50", "200", "3000", "50000", "100000"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new LongCommandNode( + "world", + List.of(), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //float + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new FloatCommandNode( + "world", + List.of(), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new FloatCommandNode( + "world", + List.of(), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new FloatCommandNode( + "world", + List.of(), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //double + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new DoubleCommandNode( + "world", + List.of(), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new DoubleCommandNode( + "world", + List.of(), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "0"))) + ); + Assertions.assertEquals( + List.of("-100000.5", "-50000.5", "-3000.5", "-200.5", "-50.5", "0.0", "50.5", "200.5", "3000.5", "50000.5", "100000.5"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new DoubleCommandNode( + "world", + List.of(), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "10"))) + ); + + //String + Assertions.assertEquals( + List.of("\"\""), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new StringCommandNode( + "world", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("\"wo\""), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new StringCommandNode( + "world", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "wo"))) + ); + Assertions.assertEquals( + List.of("\"spaced \""), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new StringCommandNode( + "world", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "spaced "))) + ); + Assertions.assertEquals( + List.of("\"spaced string\""), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new StringCommandNode( + "world", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "spaced string"))) + ); + + //UUID + Assertions.assertEquals( + List.of("9b07bd8a-a4c0-3681-997f-6b6df78c0abe", "2931e955-084c-3d9e-aea4-8e5c2c1089c1", "2931e955-084c-3d9e-aea4-8e5c2c1089c1"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new UUIDCommandNode( + "world", + List.of(), + AllowedUUIDInputs.anyNullable(), + UUIDSuggester.values( + UUID.nameUUIDFromBytes("WizardlyBump17".getBytes()), + UUID.nameUUIDFromBytes("WizardlyBump18".getBytes()), + UUID.nameUUIDFromBytes("WizardlyBump18".getBytes()) + ), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", ""))) + ); + Assertions.assertEquals( + List.of("9b07bd8a-a4c0-3681-997f-6b6df78c0abe", "2931e955-084c-3d9e-aea4-8e5c2c1089c1", "2931e955-084c-3d9e-aea4-8e5c2c1089c1"), + Assertions.assertDoesNotThrow(() -> new Command( + new LiteralCommandNode( + "hello", + List.of( + new UUIDCommandNode( + "world", + List.of(), + AllowedUUIDInputs.anyNullable(), + UUIDSuggester.values( + UUID.nameUUIDFromBytes("WizardlyBump17".getBytes()), + UUID.nameUUIDFromBytes("WizardlyBump18".getBytes()), + UUID.nameUUIDFromBytes("WizardlyBump18".getBytes()) + ), + null, + null + ) + ), + null, + null + ) + ).getSuggestions(CHAD_SENDER, List.of("hello", "9b07bd8a-a4c0"))) + ); + } + + @Test + void testEmptyStrings0() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + null, + null + ) + ), + null, + null + ) + ); + + Assertions.assertEquals(List.of("world"), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "")))); + Assertions.assertEquals(List.of(), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "", "")))); + } + + @Test + void testEmptyStrings1() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ); + + Assertions.assertEquals(List.of("world"), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "")))); + Assertions.assertEquals(List.of(), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "", "")))); + Assertions.assertEquals(List.of("test0"), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "world", "")))); + Assertions.assertEquals(List.of(), Assertions.assertDoesNotThrow(() -> command.getSuggestions(CHAD_SENDER, List.of("hello", "world", "test0", "")))); + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorRightExecutorTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorRightExecutorTests.java new file mode 100644 index 00000000..16b380f1 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorRightExecutorTests.java @@ -0,0 +1,266 @@ +package com.wizardlybump17.wlib.test.command.extractor.method; + +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.extractor.method.executor.AbstractMethodCommandNodeExecutor; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +class MethodCommandExtractorRightExecutorTests { + + /* + No parameters + */ + + @Test + void testNoArgumentsCommandResult() { + TestNoArgumentsCommandResultExecutor object = new TestNoArgumentsCommandResultExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello")); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.NoArgumentsCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello"); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestNoArgumentsCommandResultExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult hello() { + return null; + } + } + + @Test + void testNoArguments() { + TestNoArgumentsExecutor object = new TestNoArgumentsExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello")); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.NoArgumentsExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello"); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestNoArgumentsExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void hello() { + } + } + + /* + CommandSender/CommandContext only + */ + + @Test + void testCommandSenderCommandResult() { + TestCommandSenderCommandResultExecutor object = new TestCommandSenderCommandResultExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandSender.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandSenderCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandSenderCommandResultExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult hello(@NotNull CommandSender sender) { + return null; + } + } + + @Test + void testCommandSender() { + TestCommandSenderExecutor object = new TestCommandSenderExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandSender.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandSenderExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandSenderExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void hello(@NotNull CommandSender sender) { + } + } + + @Test + void testCommandContextCommandResult() { + TestCommandContextCommandResultExecutor object = new TestCommandContextCommandResultExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandContext.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandContextCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandContextCommandResultExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult hello(@NotNull CommandContext context) { + return null; + } + } + + @Test + void testCommandContext() { + TestCommandContextExecutor object = new TestCommandContextExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandContext.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandContextExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandContextExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void hello(@NotNull CommandContext context) { + } + } + + /* + CommandSender/CommandContext and parameters + */ + + @Test + void testCommandSenderAndArgumentsCommandResult() { + TestCommandSenderAndArgumentsCommandResultExecutor object = new TestCommandSenderAndArgumentsCommandResultExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandSender.class, int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandSenderAndArgumentsCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class, int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandSenderAndArgumentsCommandResultExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult hello(@NotNull CommandSender sender, int arg0) { + return null; + } + } + + @Test + void testCommandSenderAndArguments() { + TestCommandSenderAndArgumentsExecutor object = new TestCommandSenderAndArgumentsExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandSender.class, int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandSenderAndArgumentsExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class, int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandSenderAndArgumentsExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void hello(@NotNull CommandSender sender, int arg0) { + } + } + + @Test + void testCommandContextAndArgumentsCommandResult() { + TestCommandContextAndArgumentsCommandResultExecutor object = new TestCommandContextAndArgumentsCommandResultExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandContext.class, int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandContextAndArgumentsCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class, int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandContextAndArgumentsCommandResultExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult hello(@NotNull CommandContext context, int arg0) { + return null; + } + } + + @Test + void testCommandContextAndArguments() { + TestCommandContextAndArgumentsExecutor object = new TestCommandContextAndArgumentsExecutor(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", CommandContext.class, int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.CommandContextAndArgumentsExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class, int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestCommandContextAndArgumentsExecutor { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void hello(@NotNull CommandContext context, int arg0) { + } + } + + /* + Parameters only + */ + + @Test + void testArgumentsCommandResult() { + TestArgumentsCommandResult object = new TestArgumentsCommandResult(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.ArgumentsCommandResultExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestArgumentsCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult hello(int arg0) { + return null; + } + } + + @Test + void testArguments() { + TestArguments object = new TestArguments(); + + Method method = Assertions.assertDoesNotThrow(() -> object.getClass().getMethod("hello", int.class)); + + CommandNodeExecutor expected = new AbstractMethodCommandNodeExecutor.ArgumentsExecutor<>(object, method); + CommandNodeExecutor actual = MethodCommandExtractor.createExecutor(object, "hello", int.class); + + Assertions.assertEquals(expected, actual); + } + + public static final class TestArguments { + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void hello(int arg0) { + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorStructureTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorStructureTests.java new file mode 100644 index 00000000..66381a97 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorStructureTests.java @@ -0,0 +1,191 @@ +package com.wizardlybump17.wlib.test.command.extractor.method; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.extractor.CommandExtractor; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.input.primitive.number.AllowedIntegerInputs; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.IntegerCommandNode; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.IntegerSuggester; +import com.wizardlybump17.wlib.test.util.AssertionUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +class MethodCommandExtractorStructureTests { + + static MethodCommandExtractor methodCommandExtractor; + + @BeforeAll + static void setup() { + MethodCommandNodeFactoryRegistry registry = new MethodCommandNodeFactoryRegistry(); + registry.registerDefaults(); + methodCommandExtractor = CommandExtractor.method(registry); + } + + @AfterAll + static void clear() { + if (methodCommandExtractor == null) + return; + + MethodCommandNodeFactoryRegistry registry = methodCommandExtractor.getFactoryRegistry(); + registry.clear(); + methodCommandExtractor = null; + } + + @Test + void test0() { + Command test0 = new Command( + new LiteralCommandNode( + "test0", + List.of( + new LiteralCommandNode( + "123", + List.of(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test1", + List.of( + new LiteralCommandNode( + "123", + List.of( + new LiteralCommandNode( + "456", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ); + + Test0 object = new Test0(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static class Test0 { + + @com.wizardlybump17.wlib.command.annotation.Command("test0 123") + public void test0() { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test1 123 456") + public void test1() { + } + } + + @Test + void test1() { + Command test0 = new Command( + new LiteralCommandNode( + "test0", + List.of( + new IntegerCommandNode( + "int", + List.of( + new LiteralCommandNode( + "123", + List.of(), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test1", + List.of( + new IntegerCommandNode( + "int0", + List.of( + new IntegerCommandNode( + "int1", + List.of( + new LiteralCommandNode( + "123", + List.of( + new LiteralCommandNode( + "abc", + List.of(), + null, + null + ) + ), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + Test1 object = new Test1(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static class Test1 { + + @com.wizardlybump17.wlib.command.annotation.Command("test0 123") + public void test0(int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test1 123 abc") + public void test1(int arg0, int arg1) { + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorTests.java new file mode 100644 index 00000000..6fdf91fc --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandExtractorTests.java @@ -0,0 +1,1446 @@ +package com.wizardlybump17.wlib.test.command.extractor.method; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.context.CommandContext; +import com.wizardlybump17.wlib.command.extractor.CommandExtractor; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.input.primitive.number.AllowedIntegerInputs; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.IntegerCommandNode; +import com.wizardlybump17.wlib.command.node.string.StringCommandNode; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.BasicCommandSender; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.IntegerSuggester; +import com.wizardlybump17.wlib.command.suggestion.string.StringSuggester; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +class MethodCommandExtractorTests { + + static MethodCommandExtractor methodCommandExtractor; + + @BeforeAll + static void setup() { + MethodCommandNodeFactoryRegistry registry = new MethodCommandNodeFactoryRegistry(); + registry.registerDefaults(); + methodCommandExtractor = CommandExtractor.method(registry); + } + + @AfterAll + static void clear() { + if (methodCommandExtractor == null) + return; + + MethodCommandNodeFactoryRegistry registry = methodCommandExtractor.getFactoryRegistry(); + registry.clear(); + methodCommandExtractor = null; + } + + /* + No parameters + */ + @Test + void testNoParameters() { + TestNoParameters object = new TestNoParameters(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello")), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld")), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi")), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestNoParameters { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public void hello() { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void helloWorld() { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public void helloThereHi() { + } + } + + /* + No parameters returning CommandResult + */ + @Test + void testNoParametersCommandResult() { + TestNoParametersCommandResult object = new TestNoParametersCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello")), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld")), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi")), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestNoParametersCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public CommandResult hello() { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult helloWorld() { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public CommandResult helloThereHi() { + return null; + } + } + + /* + CommandSender/CommandContext only + */ + + @Test + void testCommandSender() { + TestCommandSender object = new TestCommandSender(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class)), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandSender.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandSender.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandSender { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public void hello(@NotNull CommandSender sender) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void helloWorld(@NotNull CommandSender sender) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public void helloThereHi(@NotNull CommandSender sender) { + } + } + + @Test + void testCommandContext() { + TestCommandContext object = new TestCommandContext(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class)), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandContext.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandContext.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandContext { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public void hello(@NotNull CommandContext context) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public void helloWorld(@NotNull CommandContext context) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public void helloThereHi(@NotNull CommandContext context) { + } + } + + /* + CommandSender/CommandSender returning CommandResult + */ + + @Test + void testCommandContextCommandResult() { + TestCommandContextCommandResult object = new TestCommandContextCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class)), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandContext.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandContext.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandContextCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public @NotNull CommandResult hello(@NotNull CommandContext context) { + return CommandResult.successful(context, null); + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public @NotNull CommandResult helloWorld(@NotNull CommandContext context) { + return CommandResult.successful(context, null); + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public @NotNull CommandResult helloThereHi(@NotNull CommandContext context) { + return CommandResult.successful(context, null); + } + } + + @Test + void testCommandSenderCommandResult() { + TestCommandSenderCommandResult object = new TestCommandSenderCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class)), + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandSender.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandSender.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandSenderCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public CommandResult hello(@NotNull CommandSender sender) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult helloWorld(@NotNull CommandSender sender) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi") + public CommandResult helloThereHi(@NotNull CommandSender sender) { + return null; + } + } + + /* + CommandSender/CommandContext + parameters + */ + + @Test + void testCommandSenderParameters() { + TestCommandSenderParameters object = new TestCommandSenderParameters(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandSenderParameters { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public void hello(@NotNull CommandSender sender, int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void helloWorld(@NotNull CommandSender sender, int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public void helloThereHi(@NotNull CommandSender sender, int arg0) { + } + } + + @Test + void testCommandContextParameters() { + TestCommandContextParameters object = new TestCommandContextParameters(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandContextParameters { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public void hello(@NotNull CommandContext context, int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void helloWorld(@NotNull CommandContext context, int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public void helloThereHi(@NotNull CommandContext context, int arg0) { + } + } + + /* + CommandSender/CommandContext + parameters returning CommandResult + */ + + @Test + void testCommandSenderParametersCommandResult() { + TestCommandSenderParametersCommandResult object = new TestCommandSenderParametersCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandSender.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandSenderParametersCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public CommandResult hello(@NotNull CommandSender sender, int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult helloWorld(@NotNull CommandSender sender, int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public CommandResult helloThereHi(@NotNull CommandSender sender, int arg0) { + return null; + } + } + + @Test + void testCommandContextParametersCommandResult() { + TestCommandContextParametersCommandResult object = new TestCommandContextParametersCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", CommandContext.class, int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestCommandContextParametersCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public CommandResult hello(@NotNull CommandContext context, int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult helloWorld(@NotNull CommandContext context, int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public CommandResult helloThereHi(@NotNull CommandContext context, int arg0) { + return null; + } + } + + /* + Parameters not starting with CommandSender/CommandContext + */ + + @Test + void testParametersPure() { + TestParametersPure object = new TestParametersPure(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestParametersPure { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public void hello(int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public void helloWorld(int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public void helloThereHi(int arg0) { + } + } + + /* + Parameters not starting with CommandSender/CommandContext returning CommandResult + */ + + @Test + void testParametersPureCommandResult() { + TestParametersPureCommandResult object = new TestParametersPureCommandResult(); + + List expected = new ArrayList<>(List.of( + new Command( + new LiteralCommandNode( + "hello", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello", int.class)), + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld", int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "there", + List.of( + new LiteralCommandNode( + "hi", + List.of( + new IntegerCommandNode( + "int", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloThereHi", int.class)), + null + ) + ), + null, + null + ) + ), + null, + null + ) + ), + null, + null + ) + ) + )); + List actual = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + Assertions.assertEquals(expected, actual); + } + + public static class TestParametersPureCommandResult { + + @com.wizardlybump17.wlib.command.annotation.Command("hello ") + public CommandResult hello(int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world ") + public CommandResult helloWorld(int arg0) { + return null; + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello there hi ") + public CommandResult helloThereHi(int arg0) { + return null; + } + } + + /* + Lets execute some commands + */ + + static final @NotNull Consumer SENDER_MESSAGE_CONSUMER = System.out::println; + static final @NotNull CommandSender CHAD_SENDER = new BasicCommandSender<>(new Object(), "Chad", UUID.nameUUIDFromBytes("Chad".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> true); + static final @NotNull CommandSender BETA_SENDER = new BasicCommandSender<>(new Object(), "Beta", UUID.nameUUIDFromBytes("Beta".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> false); + + @Test + void testExecute() { + TestExecute object = new TestExecute(); + + Command hello = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "helloWorld")), + null + ) + ), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hello")), + null + ) + ); + Command hi = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hiWorld", CommandSender.class)), + null + ) + ), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "hi", CommandSender.class)), + null + ) + ); + Command greetings = new Command( + new LiteralCommandNode( + "greetings", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "greetingsWorld", CommandContext.class)), + null + ) + ), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "greetings", CommandContext.class)), + null + ) + ); + Command welcome = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new StringCommandNode( + "name", + List.of( + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "welcomeWorld", CommandSender.class, String.class)), + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "welcome", CommandSender.class, String.class)), + null + ) + ), + null, + null + ) + ); + Command wassup = new Command( + new LiteralCommandNode( + "wassup", + List.of( + new StringCommandNode( + "name", + List.of( + new LiteralCommandNode( + "nice", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "wassupNice", CommandContext.class, String.class)), + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "wassup", CommandContext.class, String.class)), + null + ) + ), + null, + null + ) + ); + Command aye = new Command( + new LiteralCommandNode( + "aye", + List.of( + new StringCommandNode( + "name", + List.of( + new LiteralCommandNode( + "nice", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "ayeNice", String.class)), + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(object, "aye", String.class)), + null + ) + ), + null, + null + ) + ); + + List extractedCommands = Assertions.assertDoesNotThrow(() -> methodCommandExtractor.extract(object)); + + CommandManager manager = new CommandManager(); + + List expectedCommands = new ArrayList<>(List.of(hello, hi, greetings, welcome, wassup, aye)); + List actualCommands = manager.registerCommands("test", extractedCommands); + + expectedCommands.sort(null); + actualCommands.sort(null); + + //We already know that the register method works + actualCommands.remove(0); + actualCommands.remove(1); + actualCommands.remove(2); + actualCommands.remove(3); + actualCommands.remove(4); + actualCommands.remove(5); + + Assertions.assertEquals(expectedCommands, actualCommands); + + Assertions.assertEquals( + CommandResult.successful(0, hello.getRoot(), null), + manager.execute(CHAD_SENDER, "hello") + ); + Assertions.assertEquals( + CommandResult.successful(1, hello.findNode("world"), "Hello World"), + manager.execute(CHAD_SENDER, "hello world") + ); + + Assertions.assertEquals( + CommandResult.successful(0, hi.getRoot(), null), + manager.execute(CHAD_SENDER, "hi") + ); + Assertions.assertEquals( + CommandResult.successful(1, hi.findNode("world"), "Hi World"), + manager.execute(CHAD_SENDER, "hi world") + ); + + Assertions.assertEquals( + CommandResult.successful(0, greetings.getRoot(), null), + manager.execute(CHAD_SENDER, "greetings") + ); + Assertions.assertEquals( + CommandResult.successful(1, greetings.findNode("world"), "Hello, world!"), + manager.execute(CHAD_SENDER, "greetings world") + ); + + Assertions.assertEquals( + CommandResult.successful(1, welcome.findNode("name"), null), + manager.execute(CHAD_SENDER, "welcome test") + ); + Assertions.assertEquals( + CommandResult.successful(2, welcome.findNode("world"), "Welcome test"), + manager.execute(CHAD_SENDER, "welcome test world") + ); + + Assertions.assertEquals( + CommandResult.successful(1, wassup.findNode("name"), null), + manager.execute(CHAD_SENDER, "wassup test") + ); + Assertions.assertEquals( + CommandResult.successful(2, wassup.findNode("nice"), "Nice to meet you, test!"), + manager.execute(CHAD_SENDER, "wassup test nice") + ); + + Assertions.assertEquals( + CommandResult.successful(1, aye.findNode("name"), null), + manager.execute(CHAD_SENDER, "aye test") + ); + Assertions.assertEquals( + CommandResult.successful(2, aye.findNode("nice"), "Aye test"), + manager.execute(CHAD_SENDER, "aye test nice") + ); + } + + public static class TestExecute { + + @com.wizardlybump17.wlib.command.annotation.Command("hello") + public void hello() { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hello world") + public CommandResult helloWorld() { + return CommandResult.successful( + 1, + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(this, "helloWorld")), + null + ), + "Hello World" + ); + } + + @com.wizardlybump17.wlib.command.annotation.Command("hi") + public void hi(@NotNull CommandSender sender) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("hi world") + public CommandResult hiWorld(@NotNull CommandSender sender) { + return CommandResult.successful( + 1, + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(this, "hiWorld", CommandSender.class)), + null + ), + "Hi World" + ); + } + + @com.wizardlybump17.wlib.command.annotation.Command("greetings") + public void greetings(@NotNull CommandContext context) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("greetings world") + public @NotNull CommandResult greetingsWorld(@NotNull CommandContext context) { + return CommandResult.successful(context, "Hello, world!"); + } + + @com.wizardlybump17.wlib.command.annotation.Command("welcome ") + public void welcome(@NotNull CommandSender sender, @NotNull String name) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("welcome world") + public CommandResult welcomeWorld(@NotNull CommandSender sender, @NotNull String name) { + return CommandResult.successful( + 2, + new LiteralCommandNode( + "world", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(this, "welcomeWorld", CommandSender.class, String.class)), + null + ), + "Welcome " + name + ); + } + + @com.wizardlybump17.wlib.command.annotation.Command("wassup ") + public void wassup(@NotNull CommandContext context, @NotNull String name) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("wassup nice") + public @NotNull CommandResult wassupNice(@NotNull CommandContext context, @NotNull String name) { + return CommandResult.successful(context, "Nice to meet you, " + name + "!"); + } + + @com.wizardlybump17.wlib.command.annotation.Command("aye ") + public void aye(@NotNull String name) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("aye nice") + public CommandResult ayeNice(@NotNull String name) { + return CommandResult.successful( + 2, + new LiteralCommandNode( + "nice", + List.of(), + Assertions.assertDoesNotThrow(() -> MethodCommandExtractor.createExecutor(this, "ayeNice", String.class)), + null + ), + "Aye " + name + ); + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandNodeFactoryTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandNodeFactoryTests.java new file mode 100644 index 00000000..8fc74708 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/extractor/method/MethodCommandNodeFactoryTests.java @@ -0,0 +1,974 @@ +package com.wizardlybump17.wlib.test.command.extractor.method; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.input.object.AllowedUUIDInputs; +import com.wizardlybump17.wlib.command.input.primitive.AllowedCharacterInputs; +import com.wizardlybump17.wlib.command.input.primitive.number.*; +import com.wizardlybump17.wlib.command.input.string.AllowedStringInputs; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.object.UUIDCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.CharacterCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.*; +import com.wizardlybump17.wlib.command.node.string.StringCommandNode; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.*; +import com.wizardlybump17.wlib.command.suggestion.string.StringSuggester; +import com.wizardlybump17.wlib.test.util.AssertionUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +class MethodCommandNodeFactoryTests { + + static MethodCommandExtractor commandExtractor; + + @BeforeAll + static void setup() { + MethodCommandNodeFactoryRegistry registry = new MethodCommandNodeFactoryRegistry(); + registry.registerDefaults(); + commandExtractor = new MethodCommandExtractor(registry); + } + + @AfterAll + static void shutdown() { + if (commandExtractor == null) + return; + + MethodCommandNodeFactoryRegistry registry = commandExtractor.getFactoryRegistry(); + registry.clear(); + commandExtractor = null; + } + + @Test + void testByte() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new ByteCommandNode( + "arg0", + List.of(), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new ByteCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new ByteCommandNode( + "arg1", + List.of(), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestByte object = new TestByte(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestByte { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(byte arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(byte arg0, byte arg1) { + } + } + + @Test + void testShort() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new ShortCommandNode( + "arg0", + List.of(), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new ShortCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new ShortCommandNode( + "arg1", + List.of(), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestShort object = new TestShort(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestShort { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(short arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(short arg0, short arg1) { + } + } + + @Test + void testInt() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new IntegerCommandNode( + "arg0", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new IntegerCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new IntegerCommandNode( + "arg1", + List.of(), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestInt object = new TestInt(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestInt { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(int arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(int arg0, int arg1) { + } + } + + @Test + void testLong() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LongCommandNode( + "arg0", + List.of(), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LongCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new LongCommandNode( + "arg1", + List.of(), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestLong object = new TestLong(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestLong { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(long arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(long arg0, long arg1) { + } + } + + @Test + void testFloat() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new FloatCommandNode( + "arg0", + List.of(), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new FloatCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new FloatCommandNode( + "arg1", + List.of(), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestFloat object = new TestFloat(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestFloat { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(float arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(float arg0, float arg1) { + } + } + + @Test + void testDouble() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new DoubleCommandNode( + "arg0", + List.of(), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new DoubleCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new DoubleCommandNode( + "arg1", + List.of(), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ); + + TestDouble object = new TestDouble(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestDouble { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(double arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(double arg0, double arg1) { + } + } + + @Test + void testChar() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new CharacterCommandNode( + "arg0", + List.of(), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new CharacterCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new CharacterCommandNode( + "arg1", + List.of(), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ) + ), + null, + null + ) + ), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ) + ), + null, + null + ) + ); + + TestChar object = new TestChar(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestChar { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(char arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(char arg0, char arg1) { + } + } + + @Test + void testString() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new StringCommandNode( + "arg0", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new StringCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new StringCommandNode( + "arg1", + List.of(), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ); + + TestString object = new TestString(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestString { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(String arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(String arg0, String arg1) { + } + } + + @Test + void testUUID() { + Command test0 = new Command( + new LiteralCommandNode( + "test", + List.of( + new UUIDCommandNode( + "arg0", + List.of(), + AllowedUUIDInputs.anyNullable(), + null, + null, + null + ) + ), + null, + null + ) + ); + Command test1 = new Command( + new LiteralCommandNode( + "test", + List.of( + new UUIDCommandNode( + "arg0", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new UUIDCommandNode( + "arg1", + List.of(), + AllowedUUIDInputs.anyNullable(), + null, + null, + null + ) + ), + null, + null + ) + ), + AllowedUUIDInputs.anyNullable(), + null, + null, + null + ) + ), + null, + null + ) + ); + + TestUUID object = new TestUUID(); + + List expected = new ArrayList<>(List.of(test0, test1)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestUUID { + + @com.wizardlybump17.wlib.command.annotation.Command("test ") + public void test(UUID arg0) { + } + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 ") + public void test(UUID arg0, UUID arg1) { + } + } + + @Test + void testMix() { + Command test0 = new Command(new LiteralCommandNode( + "test", + List.of( + new IntegerCommandNode( + "int0", + List.of( + new IntegerCommandNode( + "int1", + List.of( + new LiteralCommandNode( + "test1", + List.of( + new ByteCommandNode( + "byte0", + List.of( + new ByteCommandNode( + "byte1", + List.of( + new LiteralCommandNode( + "test2", + List.of( + new FloatCommandNode( + "float0", + List.of( + new LiteralCommandNode( + "test3", + List.of( + new FloatCommandNode( + "float1", + List.of( + new LongCommandNode( + "long0", + List.of( + new LongCommandNode( + "long1", + List.of( + new LiteralCommandNode( + "test4", + List.of( + new ShortCommandNode( + "short0", + List.of( + new ShortCommandNode( + "short1", + List.of( + new LiteralCommandNode( + "test5", + List.of( + new DoubleCommandNode( + "double0", + List.of( + new DoubleCommandNode( + "double1", + List.of( + new LiteralCommandNode( + "test6", + List.of( + new CharacterCommandNode( + "char0", + List.of( + new CharacterCommandNode( + "char1", + List.of( + new LiteralCommandNode( + "test7", + List.of( + new StringCommandNode( + "string0", + List.of( + new LiteralCommandNode( + "test8", + List.of( + new StringCommandNode( + "string1", + List.of( + new UUIDCommandNode( + "uuid0", + List.of( + new LiteralCommandNode( + "test9", + List.of( + new UUIDCommandNode( + "uuid1", + List.of(), + AllowedUUIDInputs.anyNullable(), + null, + null, + null + ) + ), + null, + null + ) + ), + AllowedUUIDInputs.anyNullable(), + null, + null, + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ), + AllowedStringInputs.anyNullable(), + StringSuggester.any(), + null, + null + ) + ), + null, + null + ) + ), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ) + ), + AllowedCharacterInputs.anyNotNull(), + null, + null, + null + ) + ), + null, + null + ) + ), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + AllowedDoubleInputs.unlimited(), + DoubleSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + AllowedShortInputs.unlimited(), + ShortSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + AllowedLongInputs.unlimited(), + LongSuggester.unlimited(), + null, + null + ) + ), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedFloatInputs.unlimited(), + FloatSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + AllowedByteInputs.unlimited(), + ByteSuggester.unlimited(), + null, + null + ) + ), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + AllowedIntegerInputs.unlimited(), + IntegerSuggester.unlimited(), + null, + null + ) + ), + null, + null + )); + + TestMix object = new TestMix(); + + List expected = new ArrayList<>(List.of(test0)); + List actual = Assertions.assertDoesNotThrow(() -> commandExtractor.extract(object)); + + expected.sort(null); + actual.sort(null); + + AssertionUtil.assertCommandsEqualsIgnoreExecutor(expected, actual); + } + + public static final class TestMix { + + @com.wizardlybump17.wlib.command.annotation.Command("test test1 test2 test3 test4 test5 test6 test7 test8 test9 ") + public void test(int int0, int int1, byte byte0, byte byte1, float float0, float float1, long long0, long log1, short short0, short short1, double double0, double double1, char char0, char char1, String string0, String string1, UUID uuid0, UUID uuid1) { + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerSuggestionTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerSuggestionTests.java new file mode 100644 index 00000000..81963a7e --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerSuggestionTests.java @@ -0,0 +1,1013 @@ +package com.wizardlybump17.wlib.test.command.manager; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.input.primitive.number.AllowedIntegerInputs; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.node.primitive.number.IntegerCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.sender.BasicCommandSender; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import com.wizardlybump17.wlib.command.suggestion.primitive.number.IntegerSuggester; +import com.wizardlybump17.wlib.util.CollectionUtil; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +class CommandManagerSuggestionTests { + + static final @NotNull Consumer SENDER_MESSAGE_CONSUMER = System.out::println; + static final @NotNull CommandSender CHAD_SENDER = new BasicCommandSender<>(new Object(), "Chad", UUID.nameUUIDFromBytes("Chad".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> true); + static final @NotNull CommandSender BETA_SENDER = new BasicCommandSender<>(new Object(), "Beta", UUID.nameUUIDFromBytes("Beta".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> false); + + @Test + void testSuggestionsSuccessListChad0() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("hello", "hi", "welcome"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of())); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsSuccessListChad1() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("hello", "hi", "welcome"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of("he"))); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsSuccessListChad2() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("there"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of("hi", ""))); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsSuccessListChad3() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("there"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of("hi", "ther"))); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsSuccessListChad4() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("0", "50", "200", "3000", "50000", "100000"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of("welcome", ""))); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessListChad6() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("0", "50", "200", "3000", "50000", "100000"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, List.of("welcome", "10"))); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsFailListBeta0() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of(); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(BETA_SENDER, List.of())); + + Assertions.assertTrue(CollectionUtil.contentEquals(expected, actual)); + } + + @Test + void testSuggestionsSuccessStringChad0() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("hi", "hello", "welcome"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, "")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringChad1() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("there"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, "hi t")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringChad2() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + List expected = List.of("0", "50", "200", "3000", "50000", "100000"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, "welcome 1")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringChad3() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("spaced string"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, "test \"spaced ")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad0() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("hi", "test", "hello", "welcome"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{})); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad1() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("hi", "test", "hello", "welcome"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{"he"})); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad2() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("0", "50", "200", "3000", "50000", "100000"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{"welcome", ""})); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad3() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("spaced string"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{"test", ""})); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad4() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("spaced string"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{"test", "spaced"})); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuggestionsSuccessStringArrayChad5() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of( + new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of( + new IntegerCommandNode( + "repeat", + List.of(), + AllowedIntegerInputs.positive(), + IntegerSuggester.positive(), + context -> CommandResult.successful(context, "welcome".repeat(context.arguments().getArgument("repeat").orElseThrow().data())), + "permission" + ) + ), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + Command command3 = new Command( + new LiteralCommandNode( + "test", + List.of( + new LiteralCommandNode( + "spaced string", + List.of(), + context -> CommandResult.successful(context, "spaced string"), + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + manager.registerCommand("test", command3); + + List expected = List.of("spaced string"); + List actual = Assertions.assertDoesNotThrow(() -> manager.getSuggestions(CHAD_SENDER, new String[]{"test", "\"space"})); + + Assertions.assertEquals(expected, actual); + } + + //TODO: add more tests +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerTests.java new file mode 100644 index 00000000..c3734118 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/CommandManagerTests.java @@ -0,0 +1,580 @@ +package com.wizardlybump17.wlib.test.command.manager; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.result.SuccessResult; +import com.wizardlybump17.wlib.command.result.error.CommandNotFoundResult; +import com.wizardlybump17.wlib.command.sender.BasicCommandSender; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +class CommandManagerTests { + + static final @NotNull Consumer SENDER_MESSAGE_CONSUMER = System.out::println; + static final @NotNull CommandSender CHAD_SENDER = new BasicCommandSender<>(new Object(), "Chad", UUID.nameUUIDFromBytes("Chad".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> true); + static final @NotNull CommandSender BETA_SENDER = new BasicCommandSender<>(new Object(), "Beta", UUID.nameUUIDFromBytes("Beta".getBytes()), SENDER_MESSAGE_CONSUMER, $ -> false); + + @Test + void testRegisterDifferent() { + Command command0 = new Command( + new LiteralCommandNode( + "hello0", + List.of(), + context -> CommandResult.successful(context, "hello0"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hello1", + List.of(), + context -> CommandResult.successful(context, "hello1"), + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "hello2", + List.of(), + context -> CommandResult.successful(context, "hello2"), + "permission") + ); + Command command3 = new Command( + new LiteralCommandNode( + "hello3", + List.of(), + context -> CommandResult.successful(context, "hello3"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + + Command registeredCommand0 = manager.registerCommand("test", command0); + Command registeredCommand1 = manager.registerCommand("test", command1); + Command registeredCommand2 = manager.registerCommand("test", command2); + Command registeredCommand3 = manager.registerCommand("test", command3); + + Assertions.assertEquals(command0, registeredCommand0); + Assertions.assertEquals(command1, registeredCommand1); + Assertions.assertEquals(command2, registeredCommand2); + Assertions.assertEquals(command3, registeredCommand3); + } + + @Test + void testRegisterMerging0() { + CommandNodeExecutor helloExecutor = context -> CommandResult.successful(context, "hello"); + CommandNodeExecutor helloWorldExecutor = context -> CommandResult.successful(context, "hello world"); + CommandNodeExecutor helloWorldHiExecutor = context -> CommandResult.successful(context, "hello world hi"); + + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + null, + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + + Command registeredCommand0 = manager.registerCommand("test", command0); + Command registeredCommand1 = manager.registerCommand("test", command1); + Command registeredCommand2 = manager.registerCommand("test", command2); + + Command expectedCommand0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command expectedCommand1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + Command expectedCommand2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + helloWorldExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + + Assertions.assertEquals(expectedCommand0, registeredCommand0); + Assertions.assertEquals(expectedCommand1, registeredCommand1); + Assertions.assertEquals(expectedCommand2, registeredCommand2); + } + + @Test + void testRegisterMerging1() { + CommandNodeExecutor helloExecutor = context -> CommandResult.successful(context, "hello"); + CommandNodeExecutor helloWorldExecutor = context -> CommandResult.successful(context, "hello world"); + CommandNodeExecutor helloWorldHiExecutor = context -> CommandResult.successful(context, "hello world hi"); + CommandNodeExecutor helloThereExecutor = context -> CommandResult.successful(context, "hello there"); + + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ), + new LiteralCommandNode( + "there", + List.of(), + helloThereExecutor, + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + null, + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + + Command registeredCommand0 = manager.registerCommand("test", command0); + Command registeredCommand1 = manager.registerCommand("test", command1); + Command registeredCommand2 = manager.registerCommand("test", command2); + + Command expectedCommand0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command expectedCommand1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ), + new LiteralCommandNode( + "there", + List.of(), + helloThereExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + Command expectedCommand2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + helloWorldExecutor, + "permission" + ), + new LiteralCommandNode( + "there", + List.of(), + helloThereExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + + Assertions.assertEquals(expectedCommand0, registeredCommand0); + Assertions.assertEquals(expectedCommand1, registeredCommand1); + Assertions.assertEquals(expectedCommand2, registeredCommand2); + } + + @Test + void testCommandNotFound0() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command); + + CommandNotFoundResult expected = CommandResult.commandNotFound("hello0"); + CommandResult actual = manager.execute(CHAD_SENDER, List.of("hello0")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testCommandNotFound1() { + Command command = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command); + + CommandNotFoundResult expected = CommandResult.commandNotFound(""); + CommandResult actual = manager.execute(CHAD_SENDER, List.of()); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuccess0() { + LiteralCommandNode helloNode = new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ); + Command command0 = new Command(helloNode); + + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of(), + context -> CommandResult.successful(context, "hi"), + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + SuccessResult expected = CommandResult.successful(0, helloNode, "hello"); + CommandResult actual = manager.execute(CHAD_SENDER, List.of("hello")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testSuccess1() { + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + context -> CommandResult.successful(context, "hello"), + "permission" + ) + ); + + LiteralCommandNode thereNode = new LiteralCommandNode( + "there", + List.of(), + context -> CommandResult.successful(context, "hi there"), + "permission" + ); + Command command1 = new Command( + new LiteralCommandNode( + "hi", + List.of(thereNode), + null, + "permission" + ) + ); + + Command command2 = new Command( + new LiteralCommandNode( + "welcome", + List.of(), + context -> CommandResult.successful(context, "welcome"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + manager.registerCommand("test", command0); + manager.registerCommand("test", command1); + manager.registerCommand("test", command2); + + SuccessResult expected = CommandResult.successful(1, thereNode, "hi there"); + CommandResult actual = manager.execute(CHAD_SENDER, List.of("hi", "there")); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testRegisterMultipleDifferent0() { + Command command0 = new Command( + new LiteralCommandNode( + "hello0", + List.of(), + context -> CommandResult.successful(context, "hello0"), + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hello1", + List.of(), + context -> CommandResult.successful(context, "hello1"), + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "hello2", + List.of(), + context -> CommandResult.successful(context, "hello2"), + "permission") + ); + Command command3 = new Command( + new LiteralCommandNode( + "hello3", + List.of(), + context -> CommandResult.successful(context, "hello3"), + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + + List expected = List.of(command0, command1, command2, command3); + List actual = manager.registerCommands("test", List.of(command0, command1, command2, command3)); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testRegisterMultipleMerging0() { + CommandNodeExecutor helloExecutor = context -> CommandResult.successful(context, "hello"); + CommandNodeExecutor helloWorldExecutor = context -> CommandResult.successful(context, "hello world"); + CommandNodeExecutor helloWorldHiExecutor = context -> CommandResult.successful(context, "hello world hi"); + + Command command0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command command1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ) + ), + null, + "permission" + ) + ); + Command command2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + null, + "permission" + ) + ), + null, + "permission" + ) + ); + + CommandManager manager = new CommandManager(); + + Command expectedCommand0 = new Command( + new LiteralCommandNode( + "hello", + List.of(), + helloExecutor, + "permission" + ) + ); + Command expectedCommand1 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of(), + helloWorldExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + Command expectedCommand2 = new Command( + new LiteralCommandNode( + "hello", + List.of( + new LiteralCommandNode( + "world", + List.of( + new LiteralCommandNode( + "hi", + List.of(), + helloWorldHiExecutor, + "permission" + ) + ), + helloWorldExecutor, + "permission" + ) + ), + helloExecutor, + "permission" + ) + ); + + List expected = List.of(expectedCommand0, expectedCommand1, expectedCommand2); + List actual = manager.registerCommands("test", List.of(command0, command1, command2)); + Assertions.assertEquals(expected, actual); + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/listener/CommandManagerListenerTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/listener/CommandManagerListenerTests.java new file mode 100644 index 00000000..e04c7055 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/manager/listener/CommandManagerListenerTests.java @@ -0,0 +1,613 @@ +package com.wizardlybump17.wlib.test.command.manager.listener; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.manager.listener.CommandManagerListener; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.test.util.AssertionUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +class CommandManagerListenerTests { + + @Test + void testBeingCalledRegister() { + AtomicInteger counter = new AtomicInteger(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + counter.incrementAndGet(); + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test", + List.of(), + null, + null + ) + ) + ); + + Assertions.assertEquals(3, counter.get()); + } + + @Test + void testSeeingRightCommands() { + List seenCommands = new ArrayList<>(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + seenCommands.add(identifier + CommandManager.SEPARATOR + command.getRoot().getName()); + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + ) + ); + + Assertions.assertEquals( + List.of( + "test:test0", + "test:test1", + "test:test2" + ), + seenCommands + ); + } + + @Test + void testBeingCalledClear() { + AtomicBoolean called = new AtomicBoolean(); + + CommandManager manager = new CommandManager(); + + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + Assertions.assertTrue(manager.isEmpty()); + called.set(true); + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + ) + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + ) + ); + + manager.clear(); + + Assertions.assertTrue(called.get()); + } + + @Test + void testBeingCalledUnregisterByIdentifierAndName() { + List seenCommands = new ArrayList<>(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + seenCommands.add(identifier + CommandManager.SEPARATOR + command.getName()); + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + )); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + )); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + )); + + manager.unregister("test", "test0"); + manager.unregister("test", "test1"); + manager.unregister("test", "test2"); + + Assertions.assertEquals( + List.of( + "test:test0", + "test:test1", + "test:test2" + ), + seenCommands + ); + } + + @Test + void testBeingCalledUnregisterByIdentifierAndHolder0() { + List seenCommands = new ArrayList<>(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + seenCommands.add(identifier + CommandManager.SEPARATOR + command.getName()); + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test10", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test11", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test12", + List.of(), + null, + null + ) + ), + "test1" + ); + + manager.unregisterByHolder("test", "test"); + + AssertionUtil.assertContentEquals( + List.of( + "test:test0", + "test:test1", + "test:test2" + ), + seenCommands + ); + } + + @Test + void testBeingCalledUnregisterByIdentifierAndHolder1() { + List seenCommands = new ArrayList<>(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + seenCommands.add(identifier + CommandManager.SEPARATOR + command.getName()); + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test10", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test11", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test12", + List.of(), + null, + null + ) + ), + "test1" + ); + + manager.unregisterByHolder("test1", "test1"); + + AssertionUtil.assertContentEquals( + List.of( + "test1:test10", + "test1:test11", + "test1:test12" + ), + seenCommands + ); + } + + @Test + void testBeingCalledUnregisterByIdentifierAndHolder2() { + List seenCommands = new ArrayList<>(); + + CommandManager manager = new CommandManager(); + manager.addListener(new CommandManagerListener() { + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + seenCommands.add(identifier + CommandManager.SEPARATOR + command.getName()); + } + }); + + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test", + new Command( + new LiteralCommandNode( + "test2", + List.of(), + null, + null + ) + ), + "test" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test10", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test11", + List.of(), + null, + null + ) + ), + "test1" + ); + manager.registerCommand( + "test1", + new Command( + new LiteralCommandNode( + "test12", + List.of(), + null, + null + ) + ), + "test1" + ); + + manager.unregisterByHolder("test", "test"); + manager.unregisterByHolder("test1", "test1"); + + AssertionUtil.assertContentEquals( + List.of( + "test:test0", + "test:test1", + "test:test2", + "test1:test10", + "test1:test11", + "test1:test12" + ), + seenCommands + ); + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/command/test/sender/BasicCommandSenderTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/command/sender/BasicCommandSenderTests.java similarity index 98% rename from commands/src/test/java/com/wizardlybump17/wlib/command/test/sender/BasicCommandSenderTests.java rename to commands/src/test/java/com/wizardlybump17/wlib/test/command/sender/BasicCommandSenderTests.java index 6c66a360..c19eef75 100644 --- a/commands/src/test/java/com/wizardlybump17/wlib/command/test/sender/BasicCommandSenderTests.java +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/command/sender/BasicCommandSenderTests.java @@ -1,4 +1,4 @@ -package com.wizardlybump17.wlib.command.test.sender; +package com.wizardlybump17.wlib.test.command.sender; import com.wizardlybump17.wlib.command.sender.BasicCommandSender; import org.junit.jupiter.api.Assertions; diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtil.java b/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtil.java new file mode 100644 index 00000000..7f332bf8 --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtil.java @@ -0,0 +1,100 @@ +package com.wizardlybump17.wlib.test.util; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.util.CollectionUtil; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.AssertionFailureBuilder; + +import java.util.Collection; +import java.util.List; + +public final class AssertionUtil { + + private AssertionUtil() { + } + + public static void assertCommandsEqualsIgnoreExecutor(@Nullable List expected, @Nullable List actual) { + if (expected == null) { + if (actual != null) { + AssertionFailureBuilder.assertionFailure() + .expected(null) + .actual(actual) + .buildAndThrow(); + } + return; + } + + if (expected != null) { + if (actual == null) { + AssertionFailureBuilder.assertionFailure() + .expected(expected) + .actual(null) + .buildAndThrow(); + return; + } + } + + if (expected.size() != actual.size()) { + AssertionFailureBuilder.assertionFailure() + .expected(expected) + .actual(actual) + .message("Expected and actual lists have different sizes") + .buildAndThrow(); + return; + } + + for (int i = 0; i < expected.size(); i++) { + Command expectedCommand = expected.get(i); + Command actualCommand = actual.get(i); + + if (!expectedCommand.equalsIgnoreExecutor(actualCommand)) { + AssertionFailureBuilder.assertionFailure() + .expected(expectedCommand) + .actual(actualCommand) + .message("Commands at index " + i + " are not equal") + .buildAndThrow(); + return; + } + } + } + + public static void assertContentEquals(@Nullable Collection expected, @Nullable Collection actual) { + if (expected == null) { + if (actual != null) { + AssertionFailureBuilder.assertionFailure() + .expected(null) + .actual(actual) + .buildAndThrow(); + } + return; + } + + if (expected != null) { + if (actual == null) { + AssertionFailureBuilder.assertionFailure() + .expected(expected) + .actual(null) + .buildAndThrow(); + return; + } + } + + if (expected.size() != actual.size()) { + AssertionFailureBuilder.assertionFailure() + .expected(expected) + .actual(actual) + .message("Expected and actual collections have different sizes") + .buildAndThrow(); + return; + } + + if (!CollectionUtil.contentEquals(expected, actual)) { + AssertionFailureBuilder.assertionFailure() + .expected(expected) + .actual(actual) + .message("Expected and actual collections do not contain the same elements") + .buildAndThrow(); + return; + } + } +} diff --git a/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtilTests.java b/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtilTests.java new file mode 100644 index 00000000..eb1548ea --- /dev/null +++ b/commands/src/test/java/com/wizardlybump17/wlib/test/util/AssertionUtilTests.java @@ -0,0 +1,282 @@ +package com.wizardlybump17.wlib.test.util; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import com.wizardlybump17.wlib.command.result.CommandResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + +import java.util.List; +import java.util.Set; + +class AssertionUtilTests { + + @Test + void testCommandsEqualsIgnoreExecutorTrue() { + List left = List.of( + new Command( + new LiteralCommandNode( + "test0", + List.of(), + context -> CommandResult.successful(context, "Hi"), + "test0" + ) + ), + new Command( + new LiteralCommandNode( + "test1", + List.of( + new LiteralCommandNode( + "test1_0", + List.of( + new LiteralCommandNode( + "test1_0_0", + List.of(), + context -> CommandResult.successful(context, "Hello world"), + null + ) + ), + context -> CommandResult.successful(context, "Hello there"), + null + ), + new LiteralCommandNode( + "test1_1", + List.of(), + context -> CommandResult.successful(context, "I ran out of ideas"), + null + ) + ), + context -> CommandResult.successful(context, "Hello"), + "test1" + ) + ) + ); + List right = List.of( + new Command( + new LiteralCommandNode( + "test0", + List.of(), + null, + "test0" + ) + ), + new Command( + new LiteralCommandNode( + "test1", + List.of( + new LiteralCommandNode( + "test1_0", + List.of( + new LiteralCommandNode( + "test1_0_0", + List.of(), + context -> CommandResult.successful(context, "Welcome"), + null + ) + ), + CommandResult::genericError, + null + ), + new LiteralCommandNode( + "test1_1", + List.of(), + CommandResult::noPermission, + null + ) + ), + context -> CommandResult.successful(context, "Hello"), + "test1" + ) + ) + ); + + Assertions.assertDoesNotThrow(() -> AssertionUtil.assertCommandsEqualsIgnoreExecutor(left, right)); + } + + @Test + void testCommandsEqualsIgnoreExecutorFalse() { + List left = List.of( + new Command( + new LiteralCommandNode( + "test0", + List.of(), + context -> CommandResult.successful(context, "Hi"), + "test0" + ) + ), + new Command( + new LiteralCommandNode( + "test1", + List.of( + new LiteralCommandNode( + "test1_0", + List.of( + new LiteralCommandNode( + "test1_0_0", + List.of(), + context -> CommandResult.successful(context, "Hello world"), + null + ) + ), + context -> CommandResult.successful(context, "Hello there"), + null + ), + new LiteralCommandNode( + "test1_1", + List.of(), + context -> CommandResult.successful(context, "I ran out of ideas"), + null + ) + ), + context -> CommandResult.successful(context, "Hello"), + "test1" + ) + ) + ); + List right = List.of( + new Command( + new LiteralCommandNode( + "test1", + List.of(), + null, + "test0" + ) + ), + new Command( + new LiteralCommandNode( + "test1", + List.of( + new LiteralCommandNode( + "test1_0", + List.of( + new LiteralCommandNode( + "test1_0_0", + List.of(), + context -> CommandResult.successful(context, "Welcome"), + null + ) + ), + CommandResult::genericError, + null + ), + new LiteralCommandNode( + "test1_3", + List.of(), + CommandResult::noPermission, + null + ) + ), + context -> CommandResult.successful(context, "Hello"), + "test1" + ) + ) + ); + + Assertions.assertThrows(AssertionFailedError.class, () -> AssertionUtil.assertCommandsEqualsIgnoreExecutor(left, right)); + } + + @Test + void testContentEqualsTrueLists() { + List right = List.of( + "hi", + "welcome", + "hello", + "nice to see you" + ); + List left = List.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertDoesNotThrow(() -> AssertionUtil.assertContentEquals(right, left)); + } + + @Test + void testContentEqualsTrueListAndSet() { + List right = List.of( + "hi", + "welcome", + "hello", + "nice to see you" + ); + Set left = Set.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertDoesNotThrow(() -> AssertionUtil.assertContentEquals(right, left)); + } + + @Test + void testContentEqualsFalseExtra() { + List right = List.of( + "hi", + "welcome", + "hello", + "nice to see you", + "nice to see you" + ); + List left = List.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertThrows(AssertionFailedError.class, () -> AssertionUtil.assertContentEquals(right, left)); + } + + @Test + void testContentEqualsFalseMissing() { + List right = List.of( + "hi", + "welcome", + "hello" + ); + List left = List.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertThrows(AssertionFailedError.class, () -> AssertionUtil.assertContentEquals(right, left)); + } + + @Test + void testContentEqualsFalseDifferent0() { + List right = List.of( + "test0", + "test1" + ); + List left = List.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertThrows(AssertionFailedError.class, () -> AssertionUtil.assertContentEquals(right, left)); + } + + @Test + void testContentEqualsFalseDifferent1() { + List right = List.of( + "test0", + "test1" + ); + Set left = Set.of( + "welcome", + "hi", + "nice to see you", + "hello" + ); + + Assertions.assertThrows(AssertionFailedError.class, () -> AssertionUtil.assertContentEquals(right, left)); + } +} diff --git a/core/build.gradle b/core/build.gradle index 41d1f181..bdf67d9e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,8 +1,10 @@ +var gson = "2.13.2" + dependencies { compileOnly( 'org.projectlombok:lombok:1.18.32', 'org.jetbrains:annotations:23.1.0', - "io.papermc.paper:paper-api:1.20.6-R0.1-20240702.153951-124", + "io.papermc.paper:paper-api:1.20.6-R0.1-SNAPSHOT", ) annotationProcessor('org.projectlombok:lombok:1.18.32') @@ -10,25 +12,33 @@ dependencies { project(':utils'), project(':versions:adapter'), - project(':versions:v1_20_R4'), - project(':versions:v1_21_R1'), - project(':versions:v1_21_R3'), - project(':versions:v1_21_R5'), - project(':database'), project(':objects'), - project(':commands'), project(':config'), - project(':bukkit-utils'), + project(':bukkit-utils') ) + + implementation(project(':commands')) { + exclude group: "com.google.code.gson", module: "gson" + } + + implementation(project(":versions:v1_20_r4")) + implementation(project(":versions:v1_21_r1")) + implementation(project(":versions:v1_21_r3")) + implementation(project(":versions:v1_21_r5")) + + compileOnly("com.google.code.gson:gson:${gson}") } processResources { filesMatching('**/plugin.yml') { - expand 'version': project.version + expand(Map.of( + "version", project.version, + "gson", gson + )) } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/WLib.java b/core/src/main/java/com/wizardlybump17/wlib/WLib.java index 56079ba8..9bd1bb26 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/WLib.java +++ b/core/src/main/java/com/wizardlybump17/wlib/WLib.java @@ -1,11 +1,20 @@ package com.wizardlybump17.wlib; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.wizardlybump17.wlib.adapter.AttributeAdapter; import com.wizardlybump17.wlib.adapter.ItemAdapter; import com.wizardlybump17.wlib.adapter.command.CommandMapAdapter; import com.wizardlybump17.wlib.adapter.player.PlayerAdapter; -import com.wizardlybump17.wlib.command.args.ArgsReaderRegistry; -import com.wizardlybump17.wlib.command.reader.*; +import com.wizardlybump17.wlib.command.WLibCommandExecutor; +import com.wizardlybump17.wlib.command.extractor.method.MethodCommandExtractor; +import com.wizardlybump17.wlib.command.extractor.method.factory.OfflinePlayerMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.PlayerMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.extractor.method.factory.object.JsonElementMethodCommandNodeFactory; +import com.wizardlybump17.wlib.command.listener.BukkitCommandManagerListener; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.registry.MethodCommandNodeFactoryRegistry; +import com.wizardlybump17.wlib.command.sender.BukkitCommandSender; import com.wizardlybump17.wlib.config.holder.BukkitConfigHolderFactory; import com.wizardlybump17.wlib.config.registry.ConfigHandlerRegistry; import com.wizardlybump17.wlib.config.registry.ConfigHolderFactoryRegistry; @@ -26,24 +35,30 @@ import com.wizardlybump17.wlib.util.bukkit.config.wrapper.potion.PotionDataWrapper; import com.wizardlybump17.wlib.util.bukkit.config.wrapper.potion.PotionEffectWrapper; import com.wizardlybump17.wlib.util.bukkit.particle.*; -import lombok.Getter; -import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; -@Getter public class WLib extends JavaPlugin { private final SaveControllersTask saveControllersTask = new SaveControllersTask(getLogger()); + private MethodCommandExtractor methodCommandExtractor; + private MethodCommandNodeFactoryRegistry methodCommandNodeFactoryRegistry; + private CommandManager commandManager; + private WLibCommandExecutor commandExecutor; + private BukkitCommandManagerListener commandManagerListener; + private Gson gson; + @Override public void onLoad() { + gson = new GsonBuilder().create(); + + initCommandSystem(); ItemMetaHandlerModel.initModels(); initAdapters(); initSerializables(); - initCommandSystem(); DatabaseRegister databaseRegister = DatabaseRegister.getInstance(); databaseRegister.registerDatabaseModel(new MySQLDatabaseModel()); @@ -52,6 +67,23 @@ public void onLoad() { initConfigs(); } + private void initCommandSystem() { + methodCommandNodeFactoryRegistry = new MethodCommandNodeFactoryRegistry(); + methodCommandExtractor = new MethodCommandExtractor(methodCommandNodeFactoryRegistry); + + methodCommandNodeFactoryRegistry.registerDefaults(); + methodCommandNodeFactoryRegistry.addFactory(new OfflinePlayerMethodCommandNodeFactory()); + methodCommandNodeFactoryRegistry.addFactory(new PlayerMethodCommandNodeFactory()); + methodCommandNodeFactoryRegistry.addFactory(new JsonElementMethodCommandNodeFactory(gson)); + + commandManager = new CommandManager(); + + commandExecutor = new WLibCommandExecutor(commandManager, getLogger()); + + commandManagerListener = new BukkitCommandManagerListener(commandExecutor); + commandManager.addListener(commandManagerListener); + } + protected void initConfigs() { getLogger().info("Initializing configs..."); @@ -73,20 +105,31 @@ public void onEnable() { @Override public void onDisable() { + gson = null; + + clearCommandSystem(); HandlerList.unregisterAll(this); saveControllersTask.cancel(); } - private void initCommandSystem() { - ArgsReaderRegistry.INSTANCE.add(new PlayerReader()); - ArgsReaderRegistry.INSTANCE.add(new OfflinePlayerReader()); - ArgsReaderRegistry.INSTANCE.add(new EntityTypeArgsReader()); - ArgsReaderRegistry.INSTANCE.add(new MaterialReader()); - ArgsReaderRegistry.INSTANCE.add(new BlockDataArgsReader()); - ArgsReaderRegistry.INSTANCE.add(new MapJsonArgsReader()); - ArgsReaderRegistry.INSTANCE.add(new PotionEffectTypeReader()); - ArgsReaderRegistry.INSTANCE.add(new EnchantmentReader()); - ArgsReaderRegistry.INSTANCE.add(new NamespacedKeyReader()); + private void clearCommandSystem() { + if (methodCommandNodeFactoryRegistry != null) + methodCommandNodeFactoryRegistry.clear(); + methodCommandNodeFactoryRegistry = null; + + methodCommandExtractor = null; + + if (commandManager != null) { + commandManager.clear(); + commandManager.clearListeners(); + } + commandManager = null; + + commandExecutor = null; + + commandManagerListener = null; + + BukkitCommandSender.clearCache(); } private void initSerializables() { @@ -149,7 +192,31 @@ public static WLib getInstance() { return getPlugin(WLib.class); } - public static @NonNull String getServerVersion() { - return Bukkit.getServer().getClass().getName().split("\\.")[3]; + public MethodCommandExtractor getMethodCommandExtractor() { + return methodCommandExtractor; + } + + public MethodCommandNodeFactoryRegistry getMethodCommandNodeFactoryRegistry() { + return methodCommandNodeFactoryRegistry; + } + + public WLibCommandExecutor getCommandExecutor() { + return commandExecutor; + } + + public BukkitCommandManagerListener getCommandManagerListener() { + return commandManagerListener; + } + + public CommandManager getCommandManager() { + return commandManager; + } + + public Gson getGson() { + return gson; + } + + public SaveControllersTask getSaveControllersTask() { + return saveControllersTask; } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/BukkitCommandExecutor.java b/core/src/main/java/com/wizardlybump17/wlib/command/BukkitCommandExecutor.java deleted file mode 100644 index c878081f..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/BukkitCommandExecutor.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.wizardlybump17.wlib.command; - -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.sender.BukkitCommandSender; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.RequiredArgsConstructor; -import org.bukkit.command.Command; -import org.bukkit.command.TabExecutor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.logging.Level; - -@RequiredArgsConstructor -public class BukkitCommandExecutor implements TabExecutor, com.wizardlybump17.wlib.command.holder.CommandExecutor { - - private final CommandManager manager; - - @Override - public boolean onCommand(@NotNull org.bukkit.command.CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { - try { - execute(new BukkitCommandSender(sender), command.getName(), args); - } catch (CommandException e) { - manager.getHolder().getLogger().log(Level.SEVERE, "Error while executing a command", e); - } - return false; - } - - @Override - public void execute(CommandSender sender, String commandName, String[] args) throws CommandException { - String commandExecution = commandName + " " + String.join(" ", args); - manager.execute(sender, commandExecution); - } - - @Nullable - @Override - public List onTabComplete(@NotNull org.bukkit.command.CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - return null; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/WLibCommandExecutor.java b/core/src/main/java/com/wizardlybump17/wlib/command/WLibCommandExecutor.java new file mode 100644 index 00000000..43cd0530 --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/WLibCommandExecutor.java @@ -0,0 +1,87 @@ +package com.wizardlybump17.wlib.command; + +import com.wizardlybump17.wlib.command.exception.SuggesterException; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.result.CommandResult; +import com.wizardlybump17.wlib.command.result.SuccessResult; +import com.wizardlybump17.wlib.command.result.error.*; +import com.wizardlybump17.wlib.command.sender.BukkitCommandSender; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class WLibCommandExecutor implements CommandExecutor, TabCompleter { + + private final @NotNull CommandManager commandManager; + private final @NotNull Logger logger; + + public WLibCommandExecutor(@NotNull CommandManager commandManager, @NotNull Logger logger) { + this.commandManager = commandManager; + this.logger = logger; + } + + public @NotNull CommandManager getCommandManager() { + return commandManager; + } + + public @NotNull Logger getLogger() { + return logger; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + com.wizardlybump17.wlib.command.sender.CommandSender wlibSender = BukkitCommandSender.from(sender); + + String wlibArgs = command.getName() + " " + String.join(" ", args); + + CommandResult result = commandManager.execute(wlibSender, wlibArgs); + switch (result) { + case SuccessResult successResult -> {} + case ExceptionResult exceptionResult -> { + sender.sendMessage("§cAn internal error occurred while executing this command: " + exceptionResult.exception() + "."); + logger.log(Level.SEVERE, "Error while " + sender + " tried to execute " + wlibArgs, exceptionResult.exception()); + } + case OutOfRangeInputResult outOfRangeInputResult -> sender.sendMessage("§cInvalid input at index " + outOfRangeInputResult.lastInputIndex() + "."); + case ExtraArgumentsResult extraArgumentsResult -> sender.sendMessage("§cExtra arguments provided at index " + extraArgumentsResult.lastInputIndex() + "."); + case InsufficientArgumentsResult insufficientArgumentsResult -> sender.sendMessage("§cInsufficient arguments provided."); + case ParseInputExceptionResult parseInputExceptionResult -> sender.sendMessage("§cInvalid input at index " + parseInputExceptionResult.lastInputIndex() + ": " + parseInputExceptionResult.exception().getMessage()); + case CommandNodeExecutorNotFoundResult notFoundResult -> sender.sendMessage("§cNo executor found for this command."); + case GenericErrorResult genericErrorResult -> sender.sendMessage("§cAn error occurred while executing the command: " + genericErrorResult.message() + "."); + case NoPermissionResult noPermissionResult -> sender.sendMessage("§cYou do not have permission to execute this command."); + case CommandNotFoundResult notFoundResult -> sender.sendMessage("§cCommand not found."); + case InvalidSenderResult invalidSenderResult -> sender.sendMessage("§cYou can not execute this command."); + default -> {} + } + + return false; + } + + @Override + public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + com.wizardlybump17.wlib.command.sender.CommandSender wlibSender = BukkitCommandSender.from(sender); + + String[] wlibArgs = new String[args.length + 1]; + wlibArgs[0] = command.getName(); + System.arraycopy(args, 0, wlibArgs, 1, args.length); + + String current = args.length == 1 ? args[0] : args[args.length - 1]; + String currentLowerCase = current.toLowerCase(); + + try { + return commandManager.getSuggestions(wlibSender, wlibArgs) + .stream() + .filter(suggestion -> suggestion.toLowerCase().startsWith(currentLowerCase)) + .toList(); + } catch (SuggesterException e) { + logger.log(Level.SEVERE, "Error while getting suggestions for " + sender + ": " + Arrays.toString(wlibArgs), e); + return List.of(); + } + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/BukkitCommand.java b/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/BukkitCommand.java new file mode 100644 index 00000000..5bfa6c5d --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/BukkitCommand.java @@ -0,0 +1,24 @@ +package com.wizardlybump17.wlib.command.bukkit; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.node.LiteralCommandNode; +import org.jetbrains.annotations.NotNull; + +public class BukkitCommand extends Command { + + private final @NotNull InternalBukkitCommand internalCommand; + + public BukkitCommand(@NotNull LiteralCommandNode root) { + super(root); + internalCommand = new InternalBukkitCommand(this); + } + + public @NotNull InternalBukkitCommand getInternalCommand() { + return internalCommand; + } + + @Override + public @NotNull BukkitCommand merge(@NotNull Command other) { + return new BukkitCommand(getRoot().merge(other.getRoot())); + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/InternalBukkitCommand.java b/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/InternalBukkitCommand.java new file mode 100644 index 00000000..c11ba911 --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/bukkit/InternalBukkitCommand.java @@ -0,0 +1,51 @@ +package com.wizardlybump17.wlib.command.bukkit; + +import com.wizardlybump17.wlib.command.WLibCommandExecutor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@ApiStatus.Internal +public class InternalBukkitCommand extends Command { + + private final @NotNull BukkitCommand command; + private WLibCommandExecutor executor; + + public InternalBukkitCommand(@NotNull BukkitCommand command) { + super(command.getName()); + this.command = command; + } + + @Override + public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { + if (executor == null) + return false; + + executor.onCommand(sender, this, commandLabel, args); + return false; + } + + @Override + public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException { + if (executor == null) + return List.of(); + return executor.onTabComplete(sender, this, alias, args); + } + + public @NotNull BukkitCommand getCommand() { + return command; + } + + public WLibCommandExecutor getExecutor() { + return executor; + } + + public void setExecutor(WLibCommandExecutor executor) { + this.executor = executor; + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/completer/EntityTypeArgumentCompleter.java b/core/src/main/java/com/wizardlybump17/wlib/command/completer/EntityTypeArgumentCompleter.java deleted file mode 100644 index 2068365d..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/completer/EntityTypeArgumentCompleter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.wizardlybump17.wlib.command.completer; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.entity.EntityType; - -import java.util.Arrays; -import java.util.List; - -public class EntityTypeArgumentCompleter implements ArgumentCompleter { - - public static final @NonNull List ENTITIES = Arrays.stream(EntityType.values()) - .map(Enum::name) - .toList(); - - @Override - public @NonNull List complete(@NonNull CommandSender sender, @NonNull String @NonNull [] args) { - return ENTITIES; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/completer/MaterialArgumentCompleter.java b/core/src/main/java/com/wizardlybump17/wlib/command/completer/MaterialArgumentCompleter.java deleted file mode 100644 index 8c72508f..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/completer/MaterialArgumentCompleter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.wizardlybump17.wlib.command.completer; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Material; - -import java.util.Arrays; -import java.util.List; - -public class MaterialArgumentCompleter implements ArgumentCompleter { - - public static final List MATERIALS = Arrays.stream(Material.values()) - .map(Enum::name) - .toList(); - - @Override - public @NonNull List complete(@NonNull CommandSender sender, @NonNull String @NonNull [] args) { - return MATERIALS; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/completer/PlayerArgumentCompleter.java b/core/src/main/java/com/wizardlybump17/wlib/command/completer/PlayerArgumentCompleter.java deleted file mode 100644 index 29145e42..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/completer/PlayerArgumentCompleter.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.wizardlybump17.wlib.command.completer; - -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.List; - -public class PlayerArgumentCompleter implements ArgumentCompleter { - - @Override - public @NonNull List complete(@NonNull CommandSender sender, @NonNull String @NonNull [] args) { - return Bukkit.getOnlinePlayers().stream() - .map(Player::getName) - .toList(); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/OfflinePlayerMethodCommandNodeFactory.java b/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/OfflinePlayerMethodCommandNodeFactory.java new file mode 100644 index 00000000..90a0df9b --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/OfflinePlayerMethodCommandNodeFactory.java @@ -0,0 +1,41 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.annotation.NonNullInput; +import com.wizardlybump17.wlib.command.input.AllowedOfflinePlayerInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.OfflinePlayerCommandNode; +import com.wizardlybump17.wlib.command.suggestion.OfflinePlayerSuggester; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class OfflinePlayerMethodCommandNodeFactory extends MethodCommandNodeFactory { + + @Override + public @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + return new OfflinePlayerCommandNode( + name, + root == null ? List.of() : List.of(root), + parameter.isAnnotationPresent(NonNullInput.class) ? AllowedOfflinePlayerInputs.anyNotNull() : AllowedOfflinePlayerInputs.anyNullable(), + OfflinePlayerSuggester.online(), + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {OfflinePlayer.class, Player.class}; + } + + @Override + public boolean isStrict() { + return false; + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/PlayerMethodCommandNodeFactory.java b/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/PlayerMethodCommandNodeFactory.java new file mode 100644 index 00000000..7c9b3893 --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/extractor/method/factory/PlayerMethodCommandNodeFactory.java @@ -0,0 +1,40 @@ +package com.wizardlybump17.wlib.command.extractor.method.factory; + +import com.wizardlybump17.wlib.command.annotation.Command; +import com.wizardlybump17.wlib.command.annotation.NonNullInput; +import com.wizardlybump17.wlib.command.input.AllowedPlayerInputs; +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.node.PlayerCommandNode; +import com.wizardlybump17.wlib.command.suggestion.PlayerSuggester; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +public class PlayerMethodCommandNodeFactory extends MethodCommandNodeFactory { + + @Override + public @NotNull CommandNode create(@NotNull Object object, @NotNull Method method, @NotNull Command commandAnnotation, @NotNull Parameter parameter, @NotNull String name, @Nullable CommandNode root) { + return new PlayerCommandNode( + name, + root == null ? List.of() : List.of(root), + parameter.isAnnotationPresent(NonNullInput.class) ? AllowedPlayerInputs.anyNotNull() : AllowedPlayerInputs.anyNullable(), + PlayerSuggester.online(), + null, + null + ); + } + + @Override + public @NotNull Class @NotNull [] getSupportedTypes() { + return new Class[] {Player.class}; + } + + @Override + public boolean isStrict() { + return false; + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommand.java b/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommand.java deleted file mode 100644 index 5b115f0b..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.wizardlybump17.wlib.command.holder; - -import com.wizardlybump17.wlib.command.BukkitCommandExecutor; -import com.wizardlybump17.wlib.command.CommandManager; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.bukkit.command.PluginCommand; - -@RequiredArgsConstructor -public class BukkitCommand implements Command { - - private final PluginCommand command; - @Getter - private CommandExecutor executor; - - @Override - public void setExecutor(CommandExecutor executor) { - if (executor instanceof org.bukkit.command.CommandExecutor bukkitExecutor) - command.setExecutor(bukkitExecutor); - this.executor = executor; - } - - @Override - public CommandExecutor getDefaultExecutor(CommandManager manager, String name) { - return new BukkitCommandExecutor(manager); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommandHolder.java b/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommandHolder.java deleted file mode 100644 index 0c716fb4..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/holder/BukkitCommandHolder.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.wizardlybump17.wlib.command.holder; - -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.logging.Logger; - -@RequiredArgsConstructor -@Getter -public class BukkitCommandHolder implements CommandHolder { - - private final JavaPlugin handle; - - public static BukkitCommandHolder of(JavaPlugin plugin) { - return new BukkitCommandHolder(plugin); - } - - @Override - public Command getCommand(String name) { - return new BukkitCommand(handle.getCommand(name)); - } - - @Override - public @NonNull Logger getLogger() { - return handle.getLogger(); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedOfflinePlayerInputs.java b/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedOfflinePlayerInputs.java new file mode 100644 index 00000000..f5c3a01d --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedOfflinePlayerInputs.java @@ -0,0 +1,136 @@ +package com.wizardlybump17.wlib.command.input; + +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedOfflinePlayerInputs extends AllowedInputs { + + static @NotNull AllowedOfflinePlayerInputs.Value value(@NotNull OfflinePlayer value) { + return new AllowedOfflinePlayerInputs.Value(value); + } + + static @NotNull AllowedOfflinePlayerInputs.Values values(@NotNull List values) { + return new AllowedOfflinePlayerInputs.Values(List.copyOf(values)); + } + + static @NotNull AllowedOfflinePlayerInputs.Values values(@NotNull OfflinePlayer @NotNull ... values) { + return new AllowedOfflinePlayerInputs.Values(List.of(values)); + } + + static @NotNull AllowedOfflinePlayerInputs.Any anyNullable() { + return AllowedOfflinePlayerInputs.Any.NULLABLE; + } + + static @NotNull AllowedOfflinePlayerInputs.Any anyNotNull() { + return AllowedOfflinePlayerInputs.Any.NOT_NULL; + } + + final class Value implements AllowedOfflinePlayerInputs, SingleValueInput { + + private final @NotNull OfflinePlayer value; + + Value(@NotNull OfflinePlayer value) { + this.value = value; + } + + @Override + public @NotNull OfflinePlayer value() { + return value; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return Objects.equals(value, value1.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public String toString() { + return "AllowedOfflinePlayerInputs$Value{" + + "value=" + value + + '}'; + } + } + + final class Values implements AllowedOfflinePlayerInputs, AllowedListInputs { + + private final @NotNull List values; + + private Values(@NotNull List values) { + this.values = values; + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedOfflinePlayerInputs.Values values1 = (AllowedOfflinePlayerInputs.Values) object; + return Objects.equals(values, values1.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + @Override + public String toString() { + return "AllowedOfflinePlayerInputs$Values{" + + "values=" + values + + '}'; + } + } + + final class Any implements AllowedOfflinePlayerInputs { + + private static final @NotNull AllowedOfflinePlayerInputs.Any NULLABLE = new AllowedOfflinePlayerInputs.Any(true); + private static final @NotNull AllowedOfflinePlayerInputs.Any NOT_NULL = new AllowedOfflinePlayerInputs.Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + @Override + public boolean isAllowed(@Nullable OfflinePlayer input) { + return nullable || input != null; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedOfflinePlayerInputs.Any any = (AllowedOfflinePlayerInputs.Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedOfflinePlayerInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedPlayerInputs.java b/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedPlayerInputs.java new file mode 100644 index 00000000..14430ccb --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/input/AllowedPlayerInputs.java @@ -0,0 +1,136 @@ +package com.wizardlybump17.wlib.command.input; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; + +public interface AllowedPlayerInputs extends AllowedInputs { + + static @NotNull AllowedPlayerInputs.Value value(@NotNull Player value) { + return new AllowedPlayerInputs.Value(value); + } + + static @NotNull AllowedPlayerInputs.Values values(@NotNull List values) { + return new AllowedPlayerInputs.Values(List.copyOf(values)); + } + + static @NotNull AllowedPlayerInputs.Values values(@NotNull Player @NotNull ... values) { + return new AllowedPlayerInputs.Values(List.of(values)); + } + + static @NotNull AllowedPlayerInputs.Any anyNullable() { + return AllowedPlayerInputs.Any.NULLABLE; + } + + static @NotNull AllowedPlayerInputs.Any anyNotNull() { + return AllowedPlayerInputs.Any.NOT_NULL; + } + + final class Value implements AllowedPlayerInputs, SingleValueInput { + + private final @NotNull Player value; + + Value(@NotNull Player value) { + this.value = value; + } + + @Override + public @NotNull Player value() { + return value; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + Value value1 = (Value) object; + return Objects.equals(value, value1.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + @Override + public String toString() { + return "AllowedPlayerInputs$Value{" + + "value=" + value + + '}'; + } + } + + final class Values implements AllowedPlayerInputs, AllowedListInputs { + + private final @NotNull List values; + + private Values(@NotNull List values) { + this.values = values; + } + + @Override + public @NotNull List allowedValues() { + return values; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedPlayerInputs.Values values1 = (AllowedPlayerInputs.Values) object; + return Objects.equals(values, values1.values); + } + + @Override + public int hashCode() { + return Objects.hashCode(values); + } + + @Override + public String toString() { + return "AllowedPlayerInputs$Values{" + + "values=" + values + + '}'; + } + } + + final class Any implements AllowedPlayerInputs { + + private static final @NotNull AllowedPlayerInputs.Any NULLABLE = new AllowedPlayerInputs.Any(true); + private static final @NotNull AllowedPlayerInputs.Any NOT_NULL = new AllowedPlayerInputs.Any(false); + + private final boolean nullable; + + private Any(boolean nullable) { + this.nullable = nullable; + } + + @Override + public boolean isAllowed(@Nullable Player input) { + return nullable || input != null; + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) + return false; + AllowedPlayerInputs.Any any = (AllowedPlayerInputs.Any) object; + return nullable == any.nullable; + } + + @Override + public int hashCode() { + return Objects.hashCode(nullable); + } + + @Override + public String toString() { + return "AllowedPlayerInputs$Any{" + + "nullable=" + nullable + + '}'; + } + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/listener/BukkitCommandManagerListener.java b/core/src/main/java/com/wizardlybump17/wlib/command/listener/BukkitCommandManagerListener.java new file mode 100644 index 00000000..0383696a --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/listener/BukkitCommandManagerListener.java @@ -0,0 +1,83 @@ +package com.wizardlybump17.wlib.command.listener; + +import com.wizardlybump17.wlib.command.Command; +import com.wizardlybump17.wlib.command.WLibCommandExecutor; +import com.wizardlybump17.wlib.command.bukkit.BukkitCommand; +import com.wizardlybump17.wlib.command.bukkit.InternalBukkitCommand; +import com.wizardlybump17.wlib.command.manager.CommandManager; +import com.wizardlybump17.wlib.command.manager.listener.CommandManagerListener; +import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.logging.Level; + +public class BukkitCommandManagerListener implements CommandManagerListener { + + private final @NotNull WLibCommandExecutor commandExecutor; + + public BukkitCommandManagerListener(@NotNull WLibCommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + } + + @Override + public void onRegister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + if (!(holder instanceof JavaPlugin plugin)) + return; + + if (command instanceof BukkitCommand bukkitCommand) { + InternalBukkitCommand internalCommand = bukkitCommand.getInternalCommand(); + internalCommand.setExecutor(commandExecutor); + + Bukkit.getCommandMap().register(command.getName(), identifier, internalCommand); + } else { + PluginCommand pluginCommand = plugin.getCommand(command.getName()); + if (pluginCommand == null) { + plugin.getLogger().log(Level.WARNING, "Command not found on plugin.yml while trying to register it to WLib: " + command.getName()); + return; + } + + pluginCommand.setExecutor(commandExecutor); + } + } + + @Override + public void onPreClear(@NotNull CommandManager manager) { + Map holdersByFullName = manager.getHoldersByFullName(); + manager.getCommandsByFullName().forEach((name, command) -> { + Object holder = holdersByFullName.get(name); + onUnregister(name.substring(0, name.indexOf(CommandManager.SEPARATOR)), command, holder, manager); + }); + } + + @Override + public void onPostClear(@NotNull CommandManager manager) { + } + + @Override + public void onUnregister(@NotNull String identifier, @NotNull Command command, @Nullable Object holder, @NotNull CommandManager manager) { + if (!(holder instanceof JavaPlugin plugin)) + return; + + if (command instanceof BukkitCommand bukkitCommand) { + bukkitCommand.getInternalCommand().setExecutor(null); + + Map knownCommands = Bukkit.getCommandMap().getKnownCommands(); + knownCommands.remove(identifier + CommandManager.SEPARATOR + command.getName()); + knownCommands.remove(command.getName()); + } else { + PluginCommand pluginCommand = plugin.getCommand(command.getName()); + if (pluginCommand == null) + return; + + pluginCommand.setExecutor(null); + } + } + + public @NotNull WLibCommandExecutor getCommandExecutor() { + return commandExecutor; + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/node/OfflinePlayerCommandNode.java b/core/src/main/java/com/wizardlybump17/wlib/command/node/OfflinePlayerCommandNode.java new file mode 100644 index 00000000..cd6e91bd --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/node/OfflinePlayerCommandNode.java @@ -0,0 +1,56 @@ +package com.wizardlybump17.wlib.command.node; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.UUID; + +public class OfflinePlayerCommandNode extends CommandNode { + + public OfflinePlayerCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable OfflinePlayer parse(@Nullable String input) throws InputParsingException { + if (input == null) + throw new InputParsingException("The input cannot be null"); + + OfflinePlayer player = Bukkit.getOfflinePlayerIfCached(input); + if (player != null) + return player; + + if (input.length() == 36) { + try { + return Bukkit.getOfflinePlayer(UUID.fromString(input)); + } catch (IllegalArgumentException e) { + throw new InputParsingException("Could not parse as UUID to get an OfflinePlayer: " + input, e); + } + } + + return null; + } + + @Override + public @NotNull OfflinePlayerCommandNode withChildren(@NotNull List> children) { + return new OfflinePlayerCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull OfflinePlayerCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new OfflinePlayerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull OfflinePlayerCommandNode withPermission(@Nullable String permission) { + return new OfflinePlayerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/node/PlayerCommandNode.java b/core/src/main/java/com/wizardlybump17/wlib/command/node/PlayerCommandNode.java new file mode 100644 index 00000000..45d56cad --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/node/PlayerCommandNode.java @@ -0,0 +1,56 @@ +package com.wizardlybump17.wlib.command.node; + +import com.wizardlybump17.wlib.command.exception.InputParsingException; +import com.wizardlybump17.wlib.command.executor.CommandNodeExecutor; +import com.wizardlybump17.wlib.command.input.AllowedInputs; +import com.wizardlybump17.wlib.command.suggestion.Suggester; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; +import java.util.UUID; + +public class PlayerCommandNode extends CommandNode { + + public PlayerCommandNode(@NotNull String name, @NotNull @Unmodifiable List> children, @NotNull AllowedInputs allowedInputs, @Nullable Suggester suggester, @Nullable CommandNodeExecutor executor, @Nullable String permission) { + super(name, children, allowedInputs, suggester, executor, permission); + } + + @Override + public @Nullable Player parse(@Nullable String input) throws InputParsingException { + if (input == null) + throw new InputParsingException("The input cannot be null"); + + Player player = Bukkit.getPlayerExact(input); + if (player != null) + return player; + + if (input.length() == 36) { + try { + return Bukkit.getPlayer(UUID.fromString(input)); + } catch (IllegalArgumentException e) { + throw new InputParsingException("Could not parse as UUID to get an Player: " + input, e); + } + } + + return null; + } + + @Override + public @NotNull PlayerCommandNode withChildren(@NotNull List> children) { + return new PlayerCommandNode(getName(), children, getAllowedInputs(), getSuggester(), getExecutor(), getPermission()); + } + + @Override + public @NotNull PlayerCommandNode withExecutor(@Nullable CommandNodeExecutor executor) { + return new PlayerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), executor, getPermission()); + } + + @Override + public @NotNull PlayerCommandNode withPermission(@Nullable String permission) { + return new PlayerCommandNode(getName(), getChildren(), getAllowedInputs(), getSuggester(), getExecutor(), permission); + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/BlockDataArgsReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/BlockDataArgsReader.java deleted file mode 100644 index 8b9ca548..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/BlockDataArgsReader.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.args.reader.ArgsReaderException; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; - -import java.util.Arrays; -import java.util.List; - -public class BlockDataArgsReader extends ArgsReader { - - public static final List SUGGESTIONS = Arrays.stream(Material.values()) - .filter(Material::isBlock) - .map(material -> material.createBlockData().getAsString(true)) - .toList(); - - @Override - public Class getType() { - return BlockData.class; - } - - @Override - public BlockData read(String string) throws ArgsReaderException { - try { - return Bukkit.createBlockData(string); - } catch (IllegalArgumentException e) { - throw new ArgsReaderException(e); - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/EnchantmentReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/EnchantmentReader.java deleted file mode 100644 index dcd0d1ad..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/EnchantmentReader.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.enchantments.Enchantment; -import org.jetbrains.annotations.Nullable; - -public class EnchantmentReader extends ArgsReader { - - @Override - public @Nullable Class getType() { - return Enchantment.class; - } - - @Override - public Enchantment read(String string) { - NamespacedKey key = NamespacedKey.fromString(string); - return key == null ? null : Registry.ENCHANTMENT.get(key); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/EntityTypeArgsReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/EntityTypeArgsReader.java deleted file mode 100644 index c6986ffb..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/EntityTypeArgsReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.entity.EntityType; - -import java.util.Arrays; -import java.util.List; - -public class EntityTypeArgsReader extends ArgsReader { - - public static final List SUGGESTIONS = Arrays.stream(EntityType.values()) - .filter(EntityType::isAlive) - .map(EntityType::name) - .toList(); - - @Override - public Class getType() { - return EntityType.class; - } - - @Override - public EntityType read(String string) { - try { - return EntityType.valueOf(string.toUpperCase()); - } catch (IllegalArgumentException e) { - return null; - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/MapJsonArgsReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/MapJsonArgsReader.java deleted file mode 100644 index d7c82251..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/MapJsonArgsReader.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.args.reader.ArgsReaderException; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; - -import java.util.List; -import java.util.Map; - -public class MapJsonArgsReader extends ArgsReader> { - - public static final List SUGGESTIONS = List.of("{key=value,key2=value2}"); - - private final Gson gson = new Gson(); - - @Override - public Class> getType() { - return null; - } - - @SuppressWarnings("unchecked") - @Override - public Map read(String string) throws ArgsReaderException { - try { - return gson.fromJson(string, Map.class); - } catch (JsonSyntaxException exception) { - throw new ArgsReaderException("expected a valid JSON", exception); - } - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/MaterialReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/MaterialReader.java deleted file mode 100644 index 2daf7f90..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/MaterialReader.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Material; - -import java.util.Arrays; -import java.util.List; - -public class MaterialReader extends ArgsReader { - - public static final List SUGGESTION = Arrays.stream(Material.values()).map(Material::name).toList(); - - @Override - public Class getType() { - return Material.class; - } - - @Override - public Material read(String string) { - return Material.matchMaterial(string); - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTION; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/NamespacedKeyReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/NamespacedKeyReader.java deleted file mode 100644 index 82154cc5..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/NamespacedKeyReader.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.NamespacedKey; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class NamespacedKeyReader extends ArgsReader { - - private static final @NotNull List SUGGESTIONS = List.of("key", "namespace:key"); - - @Override - public @NotNull Class getType() { - return NamespacedKey.class; - } - - @Override - public NamespacedKey read(String string) { - return NamespacedKey.fromString(string.toLowerCase()); - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - return SUGGESTIONS; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/OfflinePlayerReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/OfflinePlayerReader.java deleted file mode 100644 index 46c29ea4..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/OfflinePlayerReader.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -public class OfflinePlayerReader extends ArgsReader { - - @Override - public Class getType() { - return OfflinePlayer.class; - } - - @Override - public OfflinePlayer read(String string) { - Player player = Bukkit.getPlayerExact(string); - if (player != null) - return player; - - try { - UUID uuid = UUID.fromString(string); - return Bukkit.getOfflinePlayer(uuid); - } catch (IllegalArgumentException ignored) { - for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) - if (string.equalsIgnoreCase(offlinePlayer.getName())) - return offlinePlayer; - } - - return null; - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - OfflinePlayer[] players = Bukkit.getOfflinePlayers(); - List suggestions = new ArrayList<>(players.length); - for (OfflinePlayer player : players) - suggestions.add(player.getName()); - return suggestions; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/PlayerReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/PlayerReader.java deleted file mode 100644 index 1521ff37..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/PlayerReader.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import com.wizardlybump17.wlib.command.sender.CommandSender; -import lombok.NonNull; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class PlayerReader extends ArgsReader { - - @Override - public Class getType() { - return Player.class; - } - - @Override - public Player read(String string) { - return Bukkit.getPlayerExact(string); - } - - @Override - public @NonNull List<@NonNull String> autoComplete(@NonNull CommandSender sender, @NonNull String current) { - Collection players = Bukkit.getOnlinePlayers(); - List suggestions = new ArrayList<>(players.size()); - for (Player player : players) - suggestions.add(player.getName()); - return suggestions; - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/reader/PotionEffectTypeReader.java b/core/src/main/java/com/wizardlybump17/wlib/command/reader/PotionEffectTypeReader.java deleted file mode 100644 index f6454523..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/reader/PotionEffectTypeReader.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wizardlybump17.wlib.command.reader; - -import com.wizardlybump17.wlib.command.args.reader.ArgsReader; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.potion.PotionEffectType; -import org.jetbrains.annotations.Nullable; - -public class PotionEffectTypeReader extends ArgsReader { - - @Override - public @Nullable Class getType() { - return PotionEffectType.class; - } - - @Override - public PotionEffectType read(String string) { - NamespacedKey key = NamespacedKey.fromString(string); - return key == null ? null : Registry.POTION_EFFECT_TYPE.get(key); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredBukkitCommand.java b/core/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredBukkitCommand.java deleted file mode 100644 index 46ea8992..00000000 --- a/core/src/main/java/com/wizardlybump17/wlib/command/registered/RegisteredBukkitCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.wizardlybump17.wlib.command.registered; - -import com.wizardlybump17.wlib.adapter.command.CommandMapAdapter; -import com.wizardlybump17.wlib.command.CommandManager; -import com.wizardlybump17.wlib.command.args.ArgsNode; -import com.wizardlybump17.wlib.command.data.CommandData; -import com.wizardlybump17.wlib.command.exception.CommandException; -import com.wizardlybump17.wlib.command.executor.CommandExecutor; -import com.wizardlybump17.wlib.command.sender.BukkitCommandSender; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class RegisteredBukkitCommand extends RegisteredCommand { - - private final @NotNull String fallback; - - public RegisteredBukkitCommand(@NotNull CommandData command, @NotNull List nodes, @NotNull CommandExecutor executor, @NotNull String fallback) { - super(command, nodes, executor); - this.fallback = fallback; - } - - public @NotNull String getFallback() { - return fallback; - } - - @Override - public void onRegister(@NotNull CommandManager manager) { - CommandMap commandMap = CommandMapAdapter.getInstance().getCommandMap(); - String name = getCommand().getName(); - Logger logger = manager.getHolder().getLogger(); - - commandMap.register(name, fallback, new Command(name) { - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { - try { - manager.execute(new BukkitCommandSender(sender), name + " " + String.join(" ", args)); - } catch (CommandException e) { - logger.log(Level.SEVERE, "Error while executing a command.", e); - } - return false; - } - }); - } - - @Override - public void onUnregister(@NotNull CommandManager manager) { - CommandMapAdapter adapter = CommandMapAdapter.getInstance(); - - String name = getCommand().getName(); - adapter.unregisterCommand(name); - adapter.unregisterCommand(fallback + ":" + name); - } -} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/sender/BukkitCommandSender.java b/core/src/main/java/com/wizardlybump17/wlib/command/sender/BukkitCommandSender.java index e1f3fc9a..c4382172 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/command/sender/BukkitCommandSender.java +++ b/core/src/main/java/com/wizardlybump17/wlib/command/sender/BukkitCommandSender.java @@ -1,15 +1,28 @@ package com.wizardlybump17.wlib.command.sender; +import com.wizardlybump17.wlib.util.bukkit.collector.ComponentCollector; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import org.bukkit.Bukkit; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; public class BukkitCommandSender implements CommandSender { + public static final @NotNull BukkitCommandSender CONSOLE = new BukkitCommandSender(Bukkit.getConsoleSender()); + private static final @NotNull Map SENDERS_BY_ID = new ConcurrentHashMap<>(); + private final @NotNull org.bukkit.command.CommandSender handle; public BukkitCommandSender(@NotNull org.bukkit.command.CommandSender handle) { @@ -31,6 +44,36 @@ public void sendMessage(String... message) { handle.sendMessage(String.join("\n", message)); } + @Override + public void sendMessage(@Nullable Object message) { + switch (message) { + case null -> handle.sendMessage("null"); + case ComponentLike component -> handle.sendMessage(component); + case String string -> handle.sendMessage(string); + default -> handle.sendMessage(String.valueOf(message)); + } + } + + @Override + public void sendMessage(@Nullable Object @Nullable ... messages) { + if (messages == null) { + sendMessage((Object) null); + return; + } + + List components = new ArrayList<>(messages.length); + for (Object message : messages) { + switch (message) { + case null -> components.add(Component.text("null")); + case ComponentLike componentLike -> components.add(componentLike.asComponent()); + case String string -> components.add(Component.text(string)); + default -> components.add(Component.text(String.valueOf(message))); + } + } + + handle.sendMessage(components.stream().collect(ComponentCollector.NEW_LINE)); + } + @Override public String getName() { return handle.getName(); @@ -57,4 +100,22 @@ public boolean hasPermission(String permission) { public boolean hasId(@NotNull UUID id) { return handle instanceof Entity entity && entity.getUniqueId().equals(id); } + + @ApiStatus.Internal + public static void clearCache() { + SENDERS_BY_ID.clear(); + } + + public static @NotNull BukkitCommandSender from(@NotNull org.bukkit.command.CommandSender sender) { + return switch (sender) { + case ConsoleCommandSender ignored -> BukkitCommandSender.CONSOLE; + case Entity entity -> SENDERS_BY_ID.computeIfAbsent(entity.getUniqueId(), $ -> new BukkitCommandSender(sender)); + default -> new BukkitCommandSender(sender); + }; + } + + @ApiStatus.Internal + public static void removeFromCache(@NotNull UUID id) { + SENDERS_BY_ID.remove(id); + } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/OfflinePlayerSuggester.java b/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/OfflinePlayerSuggester.java new file mode 100644 index 00000000..5fe61e6a --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/OfflinePlayerSuggester.java @@ -0,0 +1,55 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface OfflinePlayerSuggester extends Suggester { + + @Override + default @NotNull String getStringRepresentation(@NotNull OfflinePlayer value) { + String name = value.getName(); + return name == null ? value.getUniqueId().toString() : name; + } + + static @NotNull Online online() { + return Online.INSTANCE; + } + + static @NotNull Cached cached() { + return Cached.INSTANCE; + } + + final class Online implements OfflinePlayerSuggester { + + private static final @NotNull Online INSTANCE = new Online(); + + private Online() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return Bukkit.getOnlinePlayers() + .stream() + .map(OfflinePlayer.class::cast) + .toList(); + } + } + + final class Cached implements OfflinePlayerSuggester { + + private static final @NotNull Cached INSTANCE = new Cached(); + + private Cached() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return List.of(Bukkit.getOfflinePlayers()); + } + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/PlayerSuggester.java b/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/PlayerSuggester.java new file mode 100644 index 00000000..db9c608c --- /dev/null +++ b/core/src/main/java/com/wizardlybump17/wlib/command/suggestion/PlayerSuggester.java @@ -0,0 +1,40 @@ +package com.wizardlybump17.wlib.command.suggestion; + +import com.wizardlybump17.wlib.command.node.CommandNode; +import com.wizardlybump17.wlib.command.sender.CommandSender; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public interface PlayerSuggester extends Suggester { + + @Override + default @NotNull String getStringRepresentation(@NotNull Player value) { + return value.getName(); + } + + static @NotNull Online online() { + return Online.INSTANCE; + } + + final class Online implements PlayerSuggester { + + private static final @NotNull Online INSTANCE = new Online(); + + private Online() { + } + + @Override + public @NotNull List getSuggestions(@NotNull CommandSender sender, @NotNull List input, @NotNull String current, @NotNull CommandNode currentNode) { + return new ArrayList<>(Bukkit.getOnlinePlayers()); + } + + @Override + public String toString() { + return "PlayerSuggester$Online{}"; + } + } +} diff --git a/core/src/main/java/com/wizardlybump17/wlib/inventory/paginated/PaginatedInventoryBuilder.java b/core/src/main/java/com/wizardlybump17/wlib/inventory/paginated/PaginatedInventoryBuilder.java index 60586060..e199dfe0 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/inventory/paginated/PaginatedInventoryBuilder.java +++ b/core/src/main/java/com/wizardlybump17/wlib/inventory/paginated/PaginatedInventoryBuilder.java @@ -2,9 +2,11 @@ import com.wizardlybump17.wlib.inventory.CustomInventory; import com.wizardlybump17.wlib.inventory.CustomInventoryHolder; +import com.wizardlybump17.wlib.inventory.item.ClickAction; import com.wizardlybump17.wlib.inventory.item.InventoryNavigator; import com.wizardlybump17.wlib.inventory.item.ItemButton; import com.wizardlybump17.wlib.inventory.listener.InventoryListener; +import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.util.MapUtils; import com.wizardlybump17.wlib.util.ObjectUtil; import lombok.AccessLevel; @@ -21,6 +23,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -104,6 +107,101 @@ public PaginatedInventoryBuilder shapeReplacements(@NonNull Map itemSupplier) { + for (ItemButton button : shapeReplacements.values()) + if (Objects.equals(button.getCustomData().get(key), value)) + button.setItem(itemSupplier); + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemStackByCustomData(@NotNull Object key, @Nullable Object value, @NotNull ItemStack item) { + Supplier itemSupplier = () -> item; + for (ItemButton button : shapeReplacements.values()) + if (Objects.equals(button.getCustomData().get(key), value)) + button.setItem(itemSupplier); + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemStackByCustomData(@NotNull Object key, @Nullable Object value, @NotNull Function replacer) { + for (ItemButton button : shapeReplacements.values()) { + ItemStack originalItem = button.getItem().get(); + if (Objects.equals(button.getCustomData().get(key), value)) { + button.setItem(() -> replacer.apply(originalItem)); + } + } + return this; + } + + //ItemBuilder + + public @NotNull PaginatedInventoryBuilder setReplacementItemByCustomData(@NotNull Object key, @Nullable Object value, @NotNull Supplier itemSupplier) { + Supplier itemStackSupplier = () -> itemSupplier.get().build(); + for (ItemButton button : shapeReplacements.values()) + if (Objects.equals(button.getCustomData().get(key), value)) + button.setItem(itemStackSupplier); + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemByCustomData(@NotNull Object key, @Nullable Object value, @NotNull ItemBuilder item) { + Supplier itemSupplier = item::build; + for (ItemButton button : shapeReplacements.values()) + if (Objects.equals(button.getCustomData().get(key), value)) + button.setItem(itemSupplier); + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemByCustomData(@NotNull Object key, @Nullable Object value, @NotNull Function replacer) { + for (ItemButton button : shapeReplacements.values()) { + ItemStack originalItem = button.getItem().get(); + if (Objects.equals(button.getCustomData().get(key), value)) { + button.setItem(() -> replacer.apply(ItemBuilder.fromItemStack(originalItem)).build()); + } + } + return this; + } + + //ItemButton + + public @NotNull PaginatedInventoryBuilder setReplacementItemButtonByCustomData(@NotNull Object key, @Nullable Object value, @NotNull Supplier itemSupplier) { + ItemButton newButton = itemSupplier.get(); + for (Map.Entry entry : shapeReplacements.entrySet()) { + ItemButton button = entry.getValue(); + if (Objects.equals(button.getCustomData().get(key), value)) + entry.setValue(newButton); + } + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemButtonByCustomData(@NotNull Object key, @Nullable Object value, @NotNull ItemButton item) { + for (Map.Entry entry : shapeReplacements.entrySet()) { + ItemButton button = entry.getValue(); + if (Objects.equals(button.getCustomData().get(key), value)) + entry.setValue(item); + } + return this; + } + + public @NotNull PaginatedInventoryBuilder setReplacementItemButtonByCustomData(@NotNull Object key, @Nullable Object value, @NotNull Function replacer) { + for (Map.Entry entry : shapeReplacements.entrySet()) { + ItemButton button = entry.getValue(); + if (Objects.equals(button.getCustomData().get(key), value)) + entry.setValue(replacer.apply(button)); + } + return this; + } + + //rest of the code + public PaginatedInventoryBuilder content(@Nullable List content) { this.content = content == null ? new ArrayList<>() : content; checkNullContent(); diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/ItemBuilder.java b/core/src/main/java/com/wizardlybump17/wlib/item/ItemBuilder.java index e55b5657..3e519624 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/ItemBuilder.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/ItemBuilder.java @@ -41,61 +41,79 @@ @SerializableAs("item-builder") public class ItemBuilder implements ConfigurationSerializable, Cloneable { - private @NotNull Material type; - private int amount; - private final @NotNull Map customData; + private @NotNull Material type = Material.AIR; + private int amount = 1; + private final @NotNull Map customData = new TreeMap<>(); private @Nullable ItemMetaHandler metaHandler; private @Nullable ItemMeta itemMeta; - public ItemBuilder(@NotNull Material type, int amount, @NotNull Map customData, @Nullable ItemMeta itemMeta) { - this.type = type; - this.amount = amount; - this.customData = customData; - - ItemMetaHandlerModel metaHandlerModel = ItemMetaHandlerModel.getApplicableModel(type); - if (metaHandlerModel != null) - this.metaHandler = metaHandlerModel.createHandler(this); - - this.itemMeta = itemMeta; + public ItemBuilder() { } - public ItemBuilder(@NotNull Material type, int amount, @NotNull Map customData) { - this(type, amount, customData, null); + public ItemBuilder(@NotNull Material type) { + this.type = type; + itemMeta = Bukkit.getItemFactory().getItemMeta(type); + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); } public ItemBuilder(@NotNull Material type, int amount) { - this(type, amount, new HashMap<>()); + this.type = type; + this.amount = amount; + itemMeta = Bukkit.getItemFactory().getItemMeta(type); + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); } - public ItemBuilder(@NotNull Material type) { - this(type, 1); + public ItemBuilder(@NotNull Material type, int amount, @NotNull Map customData) { + this.type = type; + this.amount = amount; + itemMeta = Bukkit.getItemFactory().getItemMeta(type); + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); + this.customData.putAll(customData); } - public ItemBuilder(@Nullable ItemStack item, @NotNull Map customData) { - this( - item == null ? Material.AIR : item.getType(), - item == null ? 1 : item.getAmount(), - customData, - item == null ? null : item.getItemMeta() - ); + public ItemBuilder(@NotNull Material type, int amount, @NotNull Map customData, @Nullable ItemMeta itemMeta) { + this.type = type; + this.amount = amount; + this.itemMeta = itemMeta; + if (itemMeta != null) { + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); + } + this.customData.putAll(customData); } - public ItemBuilder(@Nullable ItemStack item) { - this( - item == null ? Material.AIR : item.getType(), - item == null ? 1 : item.getAmount(), - new HashMap<>(), - item == null ? null : item.getItemMeta() - ); + /** + * @deprecated use {@link #fromItemStack(ItemStack)} instead + */ + @Deprecated(forRemoval = true) + public ItemBuilder(@NotNull ItemStack item) { + type = item.getType(); + amount = item.getAmount(); + itemMeta = item.getItemMeta(); + if (itemMeta != null) { + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); + } } - public ItemBuilder() { - this( - Material.AIR, - 1, - new HashMap<>(), - null - ); + public ItemBuilder(@NotNull ItemStack item, @NotNull Map customData) { + type = item.getType(); + amount = item.getAmount(); + itemMeta = item.getItemMeta(); + if (itemMeta != null) { + metaHandler = ItemMetaHandlerModel.getApplicableModelOptional(type) + .map(model -> model.createHandler(itemMeta)) + .orElse(null); + } + this.customData.putAll(customData); } @SuppressWarnings("unchecked") @@ -145,7 +163,7 @@ public ItemBuilder type(@NonNull Material material) { itemMeta = itemMeta == null ? itemFactory.getItemMeta(type) : itemFactory.asMetaFor(itemMeta, type); ItemMetaHandlerModel model = ItemMetaHandlerModel.getApplicableModel(type); - metaHandler = model == null ? null : model.createHandler(this); + metaHandler = model == null || itemMeta == null ? null : model.createHandler(itemMeta); return this; } @@ -495,7 +513,7 @@ public ItemBuilder clone() { return new ItemBuilder( type, amount, - new HashMap<>(customData), + customData, itemMeta == null ? null : itemMeta.clone() ); } @@ -521,7 +539,13 @@ public ItemBuilder clone() { * @return a new builder with the data from the given item */ public static ItemBuilder fromItemStack(@Nullable ItemStack item) { - return new ItemBuilder(item, new HashMap<>()); + if (item == null) + return empty(); + return new ItemBuilder(item.getType(), item.getAmount(), Map.of(), item.getItemMeta()); + } + + public static @NotNull ItemBuilder empty() { + return new ItemBuilder(); } public static ItemBuilder deserialize(Map map) { @@ -559,7 +583,7 @@ public static ItemBuilder deserialize(Map map) { .ifPresent(result::itemCustomData); ItemMetaHandlerModel metaHandlerModel = ItemMetaHandlerModel.getApplicableModel(result.type()); - result.metaHandler(metaHandlerModel == null ? null : metaHandlerModel.createHandler(result)); + result.metaHandler(metaHandlerModel == null || result.itemMeta == null ? null : metaHandlerModel.createHandler(result.itemMeta)); if (result.metaHandler != null) result.metaHandler.deserialize(map); diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/FireworkMetaHandler.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/FireworkMetaHandler.java index 724bd80f..278d4717 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/FireworkMetaHandler.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/FireworkMetaHandler.java @@ -1,57 +1,66 @@ package com.wizardlybump17.wlib.item.handler; -import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.model.FireworkMetaHandlerModel; import org.bukkit.FireworkEffect; import org.bukkit.inventory.meta.FireworkMeta; +import org.jetbrains.annotations.NotNull; -import java.util.Collections; import java.util.List; import java.util.Map; public class FireworkMetaHandler extends ItemMetaHandler { - public FireworkMetaHandler(FireworkMetaHandlerModel model, ItemBuilder builder) { - super(model, builder); + public FireworkMetaHandler(FireworkMetaHandlerModel model, @NotNull FireworkMeta itemMeta) { + super(model, itemMeta); + } + + @Override + public @NotNull FireworkMeta getItemMeta() { + return (FireworkMeta) super.getItemMeta(); } @Override public void serialize(Map map) { - List effects = getBuilder().getFromMeta(FireworkMeta::getEffects, Collections.emptyList()); - map.put("effects", effects.isEmpty() ? null : effects); - int power = getBuilder().getFromMeta(FireworkMeta::getPower, 0); - map.put("power", power == 0 ? null : power); + FireworkMeta itemMeta = getItemMeta(); + + List effects = itemMeta.getEffects(); + if (!effects.isEmpty()) + map.put("effects", effects); + + int power = itemMeta.getPower(); + map.put("power", power); } - @SuppressWarnings("unchecked") @Override public void deserialize(Map map) { - getBuilder().consumeMeta(meta -> { - meta.addEffects((List) map.getOrDefault("effects", Collections.emptyList())); - meta.setPower((Integer) map.getOrDefault("power", 0)); - }); + FireworkMeta itemMeta = getItemMeta(); + + itemMeta.addEffects((FireworkEffect) map.getOrDefault("effects", List.of())); + itemMeta.setPower((int) map.getOrDefault("power", 0)); } public FireworkMetaHandler effects(FireworkEffect... effects) { - getBuilder().consumeMeta(meta -> meta.addEffects(effects)); + FireworkMeta itemMeta = getItemMeta(); + itemMeta.clearEffects(); + itemMeta.addEffects(effects); return this; } public FireworkMetaHandler power(int power) { - getBuilder().consumeMeta(meta -> meta.setPower(power)); + getItemMeta().setPower(power); return this; } public FireworkMetaHandler clearEffects() { - getBuilder().consumeMeta(FireworkMeta::clearEffects); + getItemMeta().clearEffects(); return this; } public List effects() { - return getBuilder().getFromMeta(FireworkMeta::getEffects, Collections.emptyList()); + return getItemMeta().getEffects(); } public int power() { - return getBuilder().getFromMeta(FireworkMeta::getPower, 0); + return getItemMeta().getPower(); } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/ItemMetaHandler.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/ItemMetaHandler.java index 6c137fa5..8a9bc9e4 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/ItemMetaHandler.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/ItemMetaHandler.java @@ -1,8 +1,9 @@ package com.wizardlybump17.wlib.item.handler; -import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.model.ItemMetaHandlerModel; import lombok.Data; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -10,9 +11,13 @@ public abstract class ItemMetaHandler> { private final M model; - private final ItemBuilder builder; + private final @NotNull ItemMeta itemMeta; public abstract void serialize(Map map); public abstract void deserialize(Map map); + + public @NotNull ItemMeta getItemMeta() { + return itemMeta; + } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/LeatherArmorMetaHandler.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/LeatherArmorMetaHandler.java index 981091fc..4781c604 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/LeatherArmorMetaHandler.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/LeatherArmorMetaHandler.java @@ -1,35 +1,44 @@ package com.wizardlybump17.wlib.item.handler; -import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.model.LeatherArmorMetaHandlerModel; import org.bukkit.Color; import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.jetbrains.annotations.NotNull; import java.util.Map; public class LeatherArmorMetaHandler extends ItemMetaHandler { - public LeatherArmorMetaHandler(LeatherArmorMetaHandlerModel model, ItemBuilder builder) { - super(model, builder); + public LeatherArmorMetaHandler(LeatherArmorMetaHandlerModel model, LeatherArmorMeta itemMeta) { + super(model, itemMeta); } @Override - public void serialize(Map map) { - map.put("color", getBuilder().getFromMeta(LeatherArmorMeta::getColor, (Color) null)); + public @NotNull LeatherArmorMeta getItemMeta() { + return (LeatherArmorMeta) super.getItemMeta(); } @Override - public void deserialize(Map map) { - getBuilder().consumeMeta(meta -> meta.setColor(getColor(map.get("color")))); + public void serialize(@NotNull Map map) { + LeatherArmorMeta itemMeta = getItemMeta(); + + map.put("color", itemMeta.getColor()); + } + + @Override + public void deserialize(@NotNull Map map) { + LeatherArmorMeta itemMeta = getItemMeta(); + + itemMeta.setColor(getColor(map.get("color"))); } - public LeatherArmorMetaHandler color(Color color) { - getBuilder().consumeMeta(meta -> meta.setColor(color)); + public @NotNull LeatherArmorMetaHandler color(@NotNull Color color) { + getItemMeta().setColor(color); return this; } - public Color color() { - return getBuilder().getFromMeta(LeatherArmorMeta::getColor, (Color) null); + public @NotNull Color color() { + return getItemMeta().getColor(); } private static Color getColor(Object object) { diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/PotionMetaHandler.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/PotionMetaHandler.java index 3d752ea9..837a6e0e 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/PotionMetaHandler.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/PotionMetaHandler.java @@ -1,6 +1,5 @@ package com.wizardlybump17.wlib.item.handler; -import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.model.PotionMetaHandlerModel; import com.wizardlybump17.wlib.util.bukkit.config.wrapper.potion.PotionDataWrapper; import lombok.NonNull; @@ -9,6 +8,7 @@ import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; @@ -17,8 +17,13 @@ public class PotionMetaHandler extends ItemMetaHandler { - public PotionMetaHandler(PotionMetaHandlerModel model, ItemBuilder builder) { - super(model, builder); + public PotionMetaHandler(@NotNull PotionMetaHandlerModel model, @NotNull PotionMeta itemMeta) { + super(model, itemMeta); + } + + @Override + public @NotNull PotionMeta getItemMeta() { + return (PotionMeta) super.getItemMeta(); } @Override @@ -40,48 +45,48 @@ public void deserialize(Map map) { } public PotionMetaHandler basePotionData(@NonNull PotionData data) { - getBuilder().consumeMeta(meta -> meta.setBasePotionData(data)); + getItemMeta().setBasePotionData(data); return this; } public PotionData basePotionData() { - return getBuilder().getFromMeta(PotionMeta::getBasePotionData, (PotionData) null); + return getItemMeta().getBasePotionData(); } public boolean hasCustomEffects() { - return getBuilder().getFromMeta(PotionMeta::hasCustomEffects, false); + return getItemMeta().hasCustomEffects(); } public @NonNull List customEffects() { - return getBuilder().getFromMeta(PotionMeta::getCustomEffects, Collections.emptyList()); + return getItemMeta().getCustomEffects(); } public boolean customEffect(@NonNull PotionEffect effect, boolean overwrite) { - return getBuilder().consumeMetaAndReturn(meta -> meta.addCustomEffect(effect, overwrite), false); + return getItemMeta().addCustomEffect(effect, overwrite); } public boolean removeCustomEffect(@NonNull PotionEffectType type) { - return getBuilder().consumeMetaAndReturn(meta -> meta.removeCustomEffect(type), false); + return getItemMeta().removeCustomEffect(type); } public boolean hasCustomEffect(@NonNull PotionEffectType type) { - return getBuilder().getFromMeta(meta -> meta.hasCustomEffect(type), false); + return getItemMeta().hasCustomEffect(type); } public boolean clearCustomEffects() { - return getBuilder().consumeMetaAndReturn(PotionMeta::clearCustomEffects, false); + return getItemMeta().clearCustomEffects(); } public boolean hasColor() { - return getBuilder().getFromMeta(PotionMeta::hasColor, false); + return getItemMeta().hasColor(); } public @Nullable Color color() { - return getBuilder().getFromMeta(PotionMeta::getColor, (Color) null); + return getItemMeta().getColor(); } public PotionMetaHandler color(@Nullable Color color) { - getBuilder().consumeMeta(meta -> meta.setColor(color)); + getItemMeta().setColor(color); return this; } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/SkullMetaHandler.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/SkullMetaHandler.java index fcb05fef..523d601e 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/SkullMetaHandler.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/SkullMetaHandler.java @@ -1,79 +1,102 @@ package com.wizardlybump17.wlib.item.handler; import com.destroystokyo.paper.profile.PlayerProfile; -import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.model.SkullMetaHandlerModel; -import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.profile.PlayerTextures; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; public class SkullMetaHandler extends ItemMetaHandler { - private final @NonNull Map profileCache = new ConcurrentHashMap<>(); + public SkullMetaHandler(@NotNull SkullMetaHandlerModel model, @NotNull SkullMeta itemMeta) { + super(model, itemMeta); + } - public SkullMetaHandler(SkullMetaHandlerModel model, ItemBuilder builder) { - super(model, builder); + @Override + public @NotNull SkullMeta getItemMeta() { + return (SkullMeta) super.getItemMeta(); } @Override public void serialize(Map map) { - if (skullUrl() != null) - map.put("skull", skullUrl()); - if (skullOwner() != null) - map.put("owner", skullOwner().getUniqueId().toString()); + String skullUrl = skullUrl(); + if (skullUrl != null) + map.put("skull", skullUrl); + + OfflinePlayer skullOwner = skullOwner(); + if (skullOwner != null) + map.put("owner", skullOwner.getUniqueId().toString()); } @Override public void deserialize(Map map) { String skull = (String) map.get("skull"); - if (skull == null) - return; - - try { - skull(Bukkit.getOfflinePlayer(UUID.fromString(skull))); - } catch (IllegalArgumentException ignored) { + if (skull != null) { skull(skull); + return; } + + String owner = (String) map.get("owner"); + if (owner != null) + skull(Bukkit.getOfflinePlayer(UUID.fromString(owner))); } - public String skullUrl() { - return getBuilder().getFromMeta(meta -> { - PlayerProfile profile = meta.getPlayerProfile(); - if (profile == null) - return null; + public @Nullable String skullUrl() { + PlayerProfile profile = getItemMeta().getPlayerProfile(); + if (profile == null) + return null; - URL skin = profile.getTextures().getSkin(); - return skin == null ? null : skin.toString(); - }, () -> null); + URL skin = profile.getTextures().getSkin(); + return skin == null ? null : skin.toString(); } - public SkullMetaHandler skull(String url) { - getBuilder().consumeMeta(meta -> meta.setPlayerProfile(profileCache.computeIfAbsent(url, $ -> { - try { - PlayerProfile profile = Bukkit.createProfile(UUID.nameUUIDFromBytes(url.getBytes())); - profile.getTextures().setSkin(URI.create(url).toURL()); - return profile; - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Invalid URL " + url, e); - } - }))); - return this; + public @NotNull SkullMetaHandler skull(@Nullable String url) { + SkullMeta itemMeta = getItemMeta(); + + if (url == null) { + PlayerProfile profile = itemMeta.getPlayerProfile(); + if (profile == null) + return this; + + PlayerTextures textures = profile.getTextures(); + + textures.setSkin(null); + profile.setTextures(textures); + + itemMeta.setPlayerProfile(profile); + return this; + } + + try { + PlayerProfile profile = Bukkit.createProfile(UUID.nameUUIDFromBytes(url.getBytes())); + + PlayerTextures textures = profile.getTextures(); + textures.setSkin(URI.create(url).toURL()); + + profile.setTextures(textures); + + itemMeta.setPlayerProfile(profile); + return this; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL " + url, e); + } } - public OfflinePlayer skullOwner() { - return getBuilder().getFromMeta(SkullMeta::getOwningPlayer, (OfflinePlayer) null); + public @Nullable OfflinePlayer skullOwner() { + return getItemMeta().getOwningPlayer(); } - public SkullMetaHandler skull(OfflinePlayer owner) { - getBuilder().consumeMeta(meta -> meta.setOwningPlayer(owner)); + public @NotNull SkullMetaHandler skull(@Nullable OfflinePlayer owner) { + getItemMeta().setOwningPlayer(owner); return this; } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/FireworkMetaHandlerModel.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/FireworkMetaHandlerModel.java index f732074c..2eeebf66 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/FireworkMetaHandlerModel.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/FireworkMetaHandlerModel.java @@ -3,6 +3,9 @@ import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.FireworkMetaHandler; import org.bukkit.Material; +import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -14,6 +17,11 @@ public FireworkMetaHandlerModel() { @Override public FireworkMetaHandler createHandler(ItemBuilder builder) { - return new FireworkMetaHandler(this, builder); + return new FireworkMetaHandler(this, builder.getItemMeta()); + } + + @Override + public @NotNull FireworkMetaHandler createHandler(@NotNull ItemMeta itemMeta) { + return new FireworkMetaHandler(this, (FireworkMeta) itemMeta); } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/ItemMetaHandlerModel.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/ItemMetaHandlerModel.java index 74b3a487..2b6ad166 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/ItemMetaHandlerModel.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/ItemMetaHandlerModel.java @@ -4,10 +4,13 @@ import com.wizardlybump17.wlib.item.handler.ItemMetaHandler; import lombok.Data; import org.bukkit.Material; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.EnumMap; import java.util.Map; +import java.util.Optional; import java.util.Set; @Data @@ -23,8 +26,14 @@ public ItemMetaHandlerModel(Set applicableMaterials) { registerModel(this); } + /** + * @deprecated use {@link #createHandler(ItemMeta)} instead + */ + @Deprecated(forRemoval = true) public abstract H createHandler(ItemBuilder builder); + public abstract @NotNull H createHandler(@NotNull ItemMeta itemMeta); + public boolean isApplicable(Material material) { return applicableMaterials.contains(material); } @@ -34,6 +43,10 @@ public static ItemMetaHandlerModel getApplicableModel(Material material) { return MODELS.get(material); } + public static @NotNull Optional> getApplicableModelOptional(@NotNull Material material) { + return Optional.ofNullable(getApplicableModel(material)); + } + public static void registerModel(ItemMetaHandlerModel model) { for (Material material : model.getApplicableMaterials()) MODELS.put(material, model); diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/LeatherArmorMetaHandlerModel.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/LeatherArmorMetaHandlerModel.java index a4cff9dc..078702ef 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/LeatherArmorMetaHandlerModel.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/LeatherArmorMetaHandlerModel.java @@ -3,6 +3,9 @@ import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.LeatherArmorMetaHandler; import org.bukkit.Material; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -14,6 +17,11 @@ public LeatherArmorMetaHandlerModel() { @Override public LeatherArmorMetaHandler createHandler(ItemBuilder builder) { - return new LeatherArmorMetaHandler(this, builder); + return new LeatherArmorMetaHandler(this, builder.getItemMeta()); + } + + @Override + public @NotNull LeatherArmorMetaHandler createHandler(@NotNull ItemMeta itemMeta) { + return new LeatherArmorMetaHandler(this, (LeatherArmorMeta) itemMeta); } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/PotionMetaHandlerModel.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/PotionMetaHandlerModel.java index b119af08..652bbcfe 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/PotionMetaHandlerModel.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/PotionMetaHandlerModel.java @@ -3,6 +3,9 @@ import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.PotionMetaHandler; import org.bukkit.Material; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -14,6 +17,11 @@ public PotionMetaHandlerModel() { @Override public PotionMetaHandler createHandler(ItemBuilder builder) { - return new PotionMetaHandler(this, builder); + return new PotionMetaHandler(this, builder.getItemMeta()); + } + + @Override + public @NotNull PotionMetaHandler createHandler(@NotNull ItemMeta itemMeta) { + return new PotionMetaHandler(this, (PotionMeta) itemMeta); } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/SkullMetaHandlerModel.java b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/SkullMetaHandlerModel.java index 2445e3de..8bdf022b 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/SkullMetaHandlerModel.java +++ b/core/src/main/java/com/wizardlybump17/wlib/item/handler/model/SkullMetaHandlerModel.java @@ -3,6 +3,9 @@ import com.wizardlybump17.wlib.item.ItemBuilder; import com.wizardlybump17.wlib.item.handler.SkullMetaHandler; import org.bukkit.Material; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -14,6 +17,11 @@ public SkullMetaHandlerModel() { @Override public SkullMetaHandler createHandler(ItemBuilder builder) { - return new SkullMetaHandler(this, builder); + return new SkullMetaHandler(this, builder.getItemMeta()); + } + + @Override + public @NotNull SkullMetaHandler createHandler(@NotNull ItemMeta itemMeta) { + return new SkullMetaHandler(this, (SkullMeta) itemMeta); } } diff --git a/core/src/main/java/com/wizardlybump17/wlib/listener/EntityListener.java b/core/src/main/java/com/wizardlybump17/wlib/listener/EntityListener.java index 28382af8..62545845 100644 --- a/core/src/main/java/com/wizardlybump17/wlib/listener/EntityListener.java +++ b/core/src/main/java/com/wizardlybump17/wlib/listener/EntityListener.java @@ -1,5 +1,7 @@ package com.wizardlybump17.wlib.listener; +import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent; +import com.wizardlybump17.wlib.command.sender.BukkitCommandSender; import com.wizardlybump17.wlib.inventory.CustomInventory; import com.wizardlybump17.wlib.inventory.CustomInventoryHolder; import com.wizardlybump17.wlib.inventory.item.ClickAction; @@ -15,6 +17,7 @@ import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; @RequiredArgsConstructor public class EntityListener implements Listener { @@ -63,4 +66,9 @@ public void onClose(InventoryCloseEvent event) { paginatedInventory.stopListeners(); } + + @EventHandler + public void onRemove(@NotNull EntityRemoveFromWorldEvent event) { + BukkitCommandSender.removeFromCache(event.getEntity().getUniqueId()); + } } diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 35676d43..f4a373e2 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -8,3 +8,4 @@ libraries: - net.kyori:adventure-api:4.18.0 - net.kyori:adventure-text-minimessage:4.18.0 - net.kyori:adventure-platform-bukkit:4.3.4 + - com.google.code.gson:gson:${gson} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5c82cb03..d706aba6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 37c4d078..0adbac28 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,11 +20,12 @@ include 'bukkit-utils' include 'versions:adapter' findProject(':versions:adapter')?.name = 'adapter' + include 'versions:v1_20_R4' -findProject(':versions:v1_20_R4')?.name = 'v1_20_R4' +findProject(':versions:v1_20_R4')?.name = 'v1_20_r4' include 'versions:v1_21_R1' -findProject(':versions:v1_21_R1')?.name = 'v1_21_R1' +findProject(':versions:v1_21_R1')?.name = 'v1_21_r1' include 'versions:v1_21_R3' -findProject(':versions:v1_21_R3')?.name = 'v1_21_R3' +findProject(':versions:v1_21_R3')?.name = 'v1_21_r3' include 'versions:v1_21_R5' -findProject(':versions:v1_21_R5')?.name = 'v1_21_R5' \ No newline at end of file +findProject(':versions:v1_21_R5')?.name = 'v1_21_r5' diff --git a/utils/src/main/java/com/wizardlybump17/wlib/util/CollectionUtil.java b/utils/src/main/java/com/wizardlybump17/wlib/util/CollectionUtil.java index 8c355141..09b4ac26 100644 --- a/utils/src/main/java/com/wizardlybump17/wlib/util/CollectionUtil.java +++ b/utils/src/main/java/com/wizardlybump17/wlib/util/CollectionUtil.java @@ -2,6 +2,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -235,4 +236,10 @@ public static > T deepClone(Supplier supplier, T o public static @NonNull List sort(@NonNull Collection collection) { return sort(collection, null); } + + public static boolean contentEquals(@NotNull Collection left, @NotNull Collection right) { + if (left.size() != right.size()) + return false; + return left.containsAll(right) && right.containsAll(left); + } } diff --git a/utils/src/main/java/com/wizardlybump17/wlib/util/StringUtil.java b/utils/src/main/java/com/wizardlybump17/wlib/util/StringUtil.java index cf848b11..d7b88239 100644 --- a/utils/src/main/java/com/wizardlybump17/wlib/util/StringUtil.java +++ b/utils/src/main/java/com/wizardlybump17/wlib/util/StringUtil.java @@ -3,6 +3,7 @@ import com.wizardlybump17.wlib.util.exception.PlaceholderException; import com.wizardlybump17.wlib.util.exception.QuotedStringException; import lombok.NonNull; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -301,7 +302,15 @@ public static String fixName(String name) { * * {@code Hello "Beautiful World"} * {@code [Hello, Beautiful World]} - * + * + * + * {@code Hello World } (there is an extra space at the end) + * {@code [Hello, World, ]} + * + * + * Empty String + * {@code []} + * * *

* @@ -312,8 +321,6 @@ public static String fixName(String name) { * @return the {@link List} of {@link String}s * @throws QuotedStringException if any of the following situations happen: *
    - *
  • A quoted string is started right after a normal (or escaped) {@code char};
  • - *
  • A quoted string is ended right before a normal (or escaped) {@code char};
  • *
  • A escape {@code char} is found at the end of the input;
  • *
  • A quoted string is not closed.
  • *
@@ -323,70 +330,86 @@ public static String fixName(String name) { public static @NonNull List parseQuotedStrings(@NonNull String input, char quote, char escape, char delimiter) throws QuotedStringException { List strings = new ArrayList<>(); + if (input.isEmpty()) + return strings; + char[] chars = input.toCharArray(); StringBuilder builder = new StringBuilder(); - StringBuilder quoted = new StringBuilder(); + StringBuilder quotes = new StringBuilder(); boolean escaped = false; - boolean delimited = true; - boolean hadQuote = false; - for (char current : chars) { - if (current == escape && !escaped) { // start of an escaped char + for (char currentChar : chars) { + if (!escaped && currentChar == escape) { escaped = true; continue; } - if (escaped) { // end of the escaped char - (quoted.isEmpty() ? builder : quoted).append(current); + if (escaped) { + (quotes.isEmpty() ? builder : quotes).append(currentChar); escaped = false; continue; } - if (current == quote) { - if (!delimited) // the previous char was not the delimiter. Example case: string"quoted" - throw new QuotedStringException(QuotedStringException.QUOTED_WITHOUT_DELIMITER); - - if (quoted.isEmpty()) { // begin of quoted string - quoted.append(quote); - continue; + if (currentChar == quote) { + if (!quotes.isEmpty()) { + quotes.deleteCharAt(0); + builder.append(quotes); + quotes.setLength(0); + } else { + quotes.append(quote); } - - // end of quoted string - strings.add(quoted.substring(1)); - delimited = false; - hadQuote = true; - quoted.setLength(0); continue; } - if (current == delimiter && quoted.isEmpty()) { // delimiter (space) - if (!builder.isEmpty()) { - strings.add(builder.toString()); - builder.setLength(0); - } - delimited = true; - hadQuote = false; + if (!quotes.isEmpty()) { + quotes.append(currentChar); continue; } - if (hadQuote) // the previous char was a quote. Example case: "quoted"string - throw new QuotedStringException(QuotedStringException.NON_QUOTED_AFTER_QUOTED); + if (currentChar == delimiter) { + strings.add(builder.toString()); + builder.setLength(0); + continue; + } - (quoted.isEmpty() ? builder : quoted).append(current); // any char - if (quoted.isEmpty()) - delimited = false; + builder.append(currentChar); } if (escaped) throw new QuotedStringException(QuotedStringException.INVALID_ESCAPE); - if (!quoted.isEmpty()) + if (!quotes.isEmpty()) throw new QuotedStringException(QuotedStringException.UNCLOSED_QUOTE); if (!builder.isEmpty()) strings.add(builder.toString()); + return strings; } + public static boolean isProperlyQuoted(@NotNull String input, char quote, char escape) throws QuotedStringException { + char[] chars = input.toCharArray(); + boolean escaped = false; + boolean onQuotes = false; + + for (char currentChar : chars) { + if (currentChar == escape) { + escaped = true; + continue; + } + + escaped = false; + + if (currentChar == quote) + onQuotes = !onQuotes; + } + + return !escaped && !onQuotes; + } + + public static boolean isProperlyQuoted(@NotNull String input) throws QuotedStringException { + return isProperlyQuoted(input, QUOTE, QUOTE_ESCAPE); + } + /** *

* Removes any extra space from the {@link String}. @@ -445,4 +468,66 @@ public static String fixName(String name) { return builder.toString(); } + + /** + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
InputOutput
Hello"Hello"
Hello World"Hello\ World"
"Hello World""\"Hello\ World\""
\"\\"
\Hello World"\\Hello\ World"
+ */ + public static @NotNull String escapeString(@NotNull String input, char quote, char escape) { + StringBuilder result = new StringBuilder(input.length()); + result.append(quote); + + for (char currentChar : input.toCharArray()) { + if (currentChar == quote || currentChar == escape) { + result.append(escape).append(currentChar); + } else { + result.append(currentChar); + } + } + + result.append(quote); + return result.toString(); + } + + public static @NotNull String escapeString(@NotNull String input) { + return escapeString(input, QUOTE, ESCAPE); + } + + public static @NotNull String pascalToCamel(@NotNull String input) { + StringBuilder builder = new StringBuilder(input.length()); + for (char currentChar : input.toCharArray()) { + if (currentChar >= 'A' && currentChar <= 'Z') { + if (!builder.isEmpty()) + builder.append('_'); + builder.append(Character.toLowerCase(currentChar)); + continue; + } + builder.append(currentChar); + } + return builder.toString(); + } } diff --git a/utils/src/main/java/com/wizardlybump17/wlib/util/exception/QuotedStringException.java b/utils/src/main/java/com/wizardlybump17/wlib/util/exception/QuotedStringException.java index ed8713b5..4701ac19 100644 --- a/utils/src/main/java/com/wizardlybump17/wlib/util/exception/QuotedStringException.java +++ b/utils/src/main/java/com/wizardlybump17/wlib/util/exception/QuotedStringException.java @@ -4,8 +4,6 @@ public class QuotedStringException extends RuntimeException { - public static final @NonNull String QUOTED_WITHOUT_DELIMITER = "Can not have quoted strings without the delimiter"; - public static final @NonNull String NON_QUOTED_AFTER_QUOTED = "Can not have non-quoted strings after quoted strings"; public static final @NonNull String INVALID_ESCAPE = "Invalid escape sequence"; public static final @NonNull String UNCLOSED_QUOTE = "Unclosed quote"; diff --git a/utils/src/test/java/com/wizardlybump17/wlib/util/test/QuotedStringsTests.java b/utils/src/test/java/com/wizardlybump17/wlib/util/test/QuotedStringsTests.java index 4108d47c..121c2403 100644 --- a/utils/src/test/java/com/wizardlybump17/wlib/util/test/QuotedStringsTests.java +++ b/utils/src/test/java/com/wizardlybump17/wlib/util/test/QuotedStringsTests.java @@ -2,6 +2,7 @@ import com.wizardlybump17.wlib.util.StringUtil; import com.wizardlybump17.wlib.util.exception.QuotedStringException; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.List; @@ -26,7 +27,7 @@ void testNoQuotes() { StringUtil.parseQuotedStrings("Hello Beautiful World", QUOTE, ESCAPE, DELIMITER) ); assertEquals( - List.of("Hello", "World", "Hi"), + List.of("Hello", "World", "", "", "Hi"), StringUtil.parseQuotedStrings("Hello World Hi", QUOTE, ESCAPE, DELIMITER) ); } @@ -104,20 +105,142 @@ void testEscapeInTheEndException() { } @Test - void testQuotedStringAfterNonQuotedStringException() { - assertThrows( - QuotedStringException.class, - () -> StringUtil.parseQuotedStrings("Hello World\"Hi\"", QUOTE, ESCAPE, DELIMITER), - QuotedStringException.QUOTED_WITHOUT_DELIMITER + void testQuotedStringAfterNonQuotedStringSuccess() { + Assertions.assertEquals( + List.of("Hello", "WorldHi"), + StringUtil.parseQuotedStrings("Hello World\"Hi\"", QUOTE, ESCAPE, DELIMITER) ); } @Test - void testNonQuotedStringAfterQuotedStringException() { - assertThrows( - QuotedStringException.class, - () -> StringUtil.parseQuotedStrings("\"Hello\"World", QUOTE, ESCAPE, DELIMITER), - QuotedStringException.NON_QUOTED_AFTER_QUOTED + void testNonQuotedStringAfterQuotedStringSuccess() { + Assertions.assertEquals( + List.of("HelloWorld"), + StringUtil.parseQuotedStrings("\"Hello\"World", QUOTE, ESCAPE, DELIMITER) + ); + } + + @Test + void testEmpty() { + Assertions.assertEquals( + List.of(), + StringUtil.parseQuotedStrings("", QUOTE, ESCAPE, DELIMITER) + ); + } + + @Test + void testEndingWithSpace0() { + Assertions.assertEquals( + List.of(""), + StringUtil.parseQuotedStrings(" ", QUOTE, ESCAPE, DELIMITER) + ); + } + + @Test + void testEndingWithSpace1() { + Assertions.assertEquals( + List.of("Hello", "World"), + StringUtil.parseQuotedStrings("Hello World ", QUOTE, ESCAPE, DELIMITER) + ); + } + + @Test + void testStringInsideString() { + Assertions.assertEquals( + List.of("Hello There \"Hi There\" Cool"), + StringUtil.parseQuotedStrings("\"Hello There \\\"Hi There\\\" Cool\"") + ); + } + + @Test + void testProperlyQuotedTrue() { + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello World", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello \"World\"", QUOTE, ESCAPE)); + Assertions.assertTrue(StringUtil.isProperlyQuoted("\"Hello World\"", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello \"World \"", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello \"World\" Hi There", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello \"World\"Hi There", QUOTE, ESCAPE)); + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello\"World\" Hi There", QUOTE, ESCAPE)); + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello\"World\"Hi There", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("", QUOTE, ESCAPE)); + Assertions.assertTrue(StringUtil.isProperlyQuoted("\"\"", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("Hello\\ World", QUOTE, ESCAPE)); + + Assertions.assertTrue(StringUtil.isProperlyQuoted("\"Hello There \\\"Hi There\\\" Cool\"")); + } + + @Test + void testProperlyQuotedFalse() { + Assertions.assertFalse(StringUtil.isProperlyQuoted("Hello \"World", QUOTE, ESCAPE)); + Assertions.assertFalse(StringUtil.isProperlyQuoted("Hello \"World\" Hi \"There", QUOTE, ESCAPE)); + + Assertions.assertFalse(StringUtil.isProperlyQuoted("\"", QUOTE, ESCAPE)); + + Assertions.assertFalse(StringUtil.isProperlyQuoted("Hello \"World\" \\", QUOTE, ESCAPE)); + } + + @Test + void testEscape() { + Assertions.assertEquals( + List.of("Hello World"), + StringUtil.parseQuotedStrings("\"Hello World\"", QUOTE, ESCAPE, DELIMITER) + ); + Assertions.assertEquals( + List.of("Hello World"), + StringUtil.parseQuotedStrings("Hello\\ World", QUOTE, ESCAPE, DELIMITER) + ); + + Assertions.assertEquals( + List.of("\\Hello", "World"), + StringUtil.parseQuotedStrings("\\\\Hello World", QUOTE, ESCAPE, DELIMITER) + ); + + Assertions.assertEquals( + List.of("\\"), + StringUtil.parseQuotedStrings("\\\\", QUOTE, ESCAPE, DELIMITER) + ); + Assertions.assertEquals( + List.of("\\\\"), + StringUtil.parseQuotedStrings("\\\\\\\\", QUOTE, ESCAPE, DELIMITER) + ); + } + + @Test + void testAddEscape() { + Assertions.assertEquals( + "\"Hello World\"", //"Hello World" + StringUtil.escapeString("Hello World", QUOTE, ESCAPE) + ); + + Assertions.assertEquals( + "\"Hello World \\\\\"", //"Hello World \\" + StringUtil.escapeString("Hello World \\", QUOTE, ESCAPE) + ); + + Assertions.assertEquals( + "\" \"", //" " + StringUtil.escapeString(" ", QUOTE, ESCAPE) + ); + + Assertions.assertEquals( + "\"\\\"\\\"\\\"\"", //"\"\"\"" + StringUtil.escapeString("\"\"\"", QUOTE, ESCAPE) + ); + + Assertions.assertEquals( + "\"\"", + StringUtil.escapeString("", QUOTE, ESCAPE) + ); + + Assertions.assertEquals( + "\"\\\"\"", // "\"" + StringUtil.escapeString("\"", QUOTE, ESCAPE) ); } } diff --git a/versions/v1_21_R5/build.gradle.kts b/versions/v1_21_R5/build.gradle.kts index 96a822fc..a9f0149e 100644 --- a/versions/v1_21_R5/build.gradle.kts +++ b/versions/v1_21_R5/build.gradle.kts @@ -4,7 +4,7 @@ plugins { apply(plugin = "io.papermc.paperweight.userdev") -val paper = "1.21.7-R0.1-SNAPSHOT" +val paper = "1.21.8-R0.1-SNAPSHOT" val jetbrainsAnnotations = "26.0.2" dependencies {