diff --git a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java index 01162c6..46efcf9 100644 --- a/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/fns/patchwork/command/BukkitDelegate.java @@ -32,6 +32,7 @@ import fns.patchwork.utils.logging.FNS4J; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -156,50 +157,19 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC if (argTypes.length > args.length) return; + final Player p = (sender instanceof Player player) ? player : null; + final Object[] objects = new Object[argTypes.length + 1]; - for (int i = 0; i < argTypes.length; i++) - { - final Class argType = argTypes[i]; - final String arg = args[i]; + parseArguments(args, provider, argTypes, objects); - if (argType.equals(String.class)) - { - if (i == argTypes.length - 1) - { - final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1); - final String reason = String.join(" ", reasonArgs); - objects[i] = reason; - } - else - { - continue; - } - } - - if (argType.equals(Location.class)) - { - final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3); - final String location = String.join(" ", locationArgs); - objects[i] = location; - } - - final Object obj = provider.fromString(arg, argType); - if (obj == null) - { - FNS4J.getLogger("Datura") - .error("Failed to parse argument " + arg + " for type " + argType.getName()); - return; - } - objects[i] = obj; - } try { if (noConsole) { command.getSubcommands() .get(node) - .invoke(command, (Player) sender, objects); + .invoke(command, p, objects); } else { @@ -215,11 +185,55 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC } } - @Override - public List tabComplete(final CommandSender sender, final String alias, final String[] args) + private void parseArguments(@NotNull String @NotNull [] args, + ContextProvider provider, + Class[] argTypes, + Object[] objects) + { + for (int i = 0; i < argTypes.length; i++) + { + final Class argType = argTypes[i]; + String arg = args[i]; + + boolean wasResolved = false; + + if (argType.equals(String.class) && (i == argTypes.length - 1)) + { + final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1); + final String reason = String.join(" ", reasonArgs); + objects[i] = reason; + wasResolved = true; + } + + if (argType.equals(Location.class)) + { + final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3); + arg = String.join(" ", locationArgs); + } + + if (!wasResolved) + { + final Optional obj = provider.fromString(arg, argType); + if (obj.isEmpty()) + { + FNS4J.getLogger("Datura") + .error("Failed to parse argument " + arg + " for type " + argType.getName()); + continue; + } + objects[i] = obj.get(); + } + } + } + + @Override + public @NotNull List tabComplete(final @NotNull CommandSender sender, final @NotNull String alias, + final String[] args) { - final Set completions = command.getCompletions(); final List results = new ArrayList<>(); + final Set completions = command.getCompletions(); + + if (completions == null || completions.isEmpty()) + return results; if (args.length == 0) { diff --git a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java index b7b4780..19ef8c2 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/Configuration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/Configuration.java @@ -25,6 +25,7 @@ package fns.patchwork.config; import fns.patchwork.provider.Context; import fns.patchwork.provider.ContextProvider; +import java.util.Collection; import org.jetbrains.annotations.Unmodifiable; import java.io.File; @@ -87,7 +88,7 @@ public interface Configuration * @param clazz The class of the type. * @return The List object. */ - @Unmodifiable List getList(String path, Class clazz); + @Unmodifiable Collection getCollection(String path, Class clazz); /** * Gets a List object from the associated path. The List that is returned will be the String values which are stored diff --git a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java similarity index 59% rename from Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java rename to Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java index 410e791..507eb8f 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/GenericConfiguration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/GenericConfig.java @@ -26,7 +26,9 @@ package fns.patchwork.config; import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.ConfigFormat; import com.electronwill.nightconfig.core.UnmodifiableConfig; +import fns.patchwork.provider.ContextProvider; import fns.patchwork.utils.FileUtils; +import fns.patchwork.utils.logging.FNS4J; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; @@ -34,26 +36,28 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; -public final class GenericConfiguration implements Configuration +public final class GenericConfig implements Configuration { + private static final ContextProvider PROVIDER = new ContextProvider(); private final File configFile; private final String fileName; private final Config config; private final ConfigType configType; - public GenericConfiguration(@NotNull final ConfigType configType, - @Nullable final JavaPlugin plugin, - @NotNull final File dataFolder, - @NotNull final String fileName, - final boolean isConcurrent) throws IOException + public GenericConfig(@NotNull final ConfigType configType, + @Nullable final JavaPlugin plugin, + @NotNull final File dataFolder, + @NotNull final String fileName, + final boolean isConcurrent) throws IOException { if (!fileName.endsWith(configType.getExtension())) throw new IllegalArgumentException("File name must end with " + configType.getExtension() + "!"); @@ -78,20 +82,20 @@ public final class GenericConfiguration implements Configuration this.load(); } - public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName) + public GenericConfig(final ConfigType type, final File dataFolder, final String fileName) throws IOException { this(type, null, dataFolder, fileName, false); } - public GenericConfiguration(final ConfigType type, final JavaPlugin plugin, final String fileName) + public GenericConfig(final ConfigType type, final JavaPlugin plugin, final String fileName) throws IOException { this(type, plugin, plugin.getDataFolder(), fileName, false); } - public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName, - final boolean isConcurrent) + public GenericConfig(final ConfigType type, final File dataFolder, final String fileName, + final boolean isConcurrent) throws IOException { this(type, null, dataFolder, fileName, isConcurrent); @@ -114,8 +118,10 @@ public final class GenericConfiguration implements Configuration } @Override - public void load() throws IOException { - try (final FileReader reader = new FileReader(this.configFile)) { + public void load() throws IOException + { + try (final FileReader reader = new FileReader(this.configFile)) + { this.config.clear(); final UnmodifiableConfig parsed = this.configType.getParser().parse(reader).unmodifiable(); @@ -145,7 +151,7 @@ public final class GenericConfiguration implements Configuration } @Override - public boolean getBoolean(String path) + public boolean getBoolean(final String path) { if (!(this.getConfig().get(path) instanceof Boolean)) throw new IllegalArgumentException(String.format("Value at path %s is not a boolean!", path)); @@ -153,22 +159,70 @@ public final class GenericConfiguration implements Configuration return this.getConfig().get(path); } - @Override - @ApiStatus.Internal - public @Unmodifiable List getList(String path, Class clazz) - { - // TODO: Figure out how to parse lists with Night Config. - return new ArrayList<>(); + /* + * I am pretty sure that this works, but not really. + * This is sort of a shot in the dark because Night Config did specify that they support collections + * in TOML and JSON files, but there is no specific get method for objects that are not primitives. + * Additionally, not all primitives are natively supported. + */ + @Override + public @Unmodifiable Collection getCollection(String path, Class clazz) + { + if (!(this.getConfig().get(path) instanceof Collection)) + throw new IllegalArgumentException(String.format("Value at path %s is not a collection!", path)); + + final Collection collection = this.getConfig().get(path); + final Collection collected = new ArrayList<>(); + + collection.stream() + .map(obj -> + { + final Optional optional; + + if (obj instanceof String string) + optional = PROVIDER.fromString(string, clazz); + else if (clazz.isInstance(obj)) + optional = Optional.of(clazz.cast(obj)); + else + optional = Optional.empty(); + + return optional; + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toCollection(() -> collected)); + + return collected; } + /* + * I am pretty sure that this works, but not really. + * This is sort of a shot in the dark because Night Config did specify that they support collections + * in TOML and JSON files, but there is no specific get method for objects that are not primitives. + * Additionally, not all primitives are natively supported. + */ @Override - @ApiStatus.Internal public @Unmodifiable List getStringList(String path) { - // TODO: Figure out how to parse lists with Night Config. + if (!(this.getConfig().get(path) instanceof Collection c)) + throw new IllegalArgumentException(String.format("Value at path %s is not a collection!", path)); - return new ArrayList<>(); + final Collection collection = this.getConfig().get(path); + final List list = new ArrayList<>(); + + if (c.isEmpty() || !(c.toArray()[0] instanceof String)) + { + FNS4J.PATCHWORK.warn(String.format("Collection at path %s is empty or does not contain strings!", path)); + FNS4J.PATCHWORK.warn("Returning empty list!"); + return list; + } + + collection.stream() + .map(String.class::cast) + .collect(Collectors.toCollection(() -> list)); + + return list; } @Override @@ -195,20 +249,22 @@ public final class GenericConfiguration implements Configuration @Override public Optional get(String path, Class clazz) { - // I love ternary statements, sorry Allink :) - return clazz.isInstance(this.getConfig().get(path)) ? - Optional.of(clazz.cast(this.getConfig().get(path))) : - Optional.empty(); + return this.getConfig() + .getOptional(path) + .filter(clazz::isInstance) + .map(clazz::cast); } @Override public T getOrDefault(String path, Class clazz, T fallback) { - return this.get(path, clazz).orElse(fallback); + return this.get(path, clazz) + .orElse(fallback); } @Override - public void set(final String path, final T value) { + public void set(final String path, final T value) + { this.config.set(path, value); } diff --git a/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java b/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java index f50e146..1baf944 100644 --- a/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java +++ b/Patchwork/src/main/java/fns/patchwork/config/WrappedBukkitConfiguration.java @@ -94,7 +94,7 @@ public final class WrappedBukkitConfiguration implements Configuration } @Override - public List getList(String path, Class clazz) + public List getCollection(String path, Class clazz) { return this.contextProvider.getList(this.getStringList(path), clazz); } @@ -132,7 +132,7 @@ public final class WrappedBukkitConfiguration implements Configuration @Override public Optional get(String path, Class clazz) { - return Optional.ofNullable(this.contextProvider.fromString(path, clazz)); + return this.contextProvider.fromString(path, clazz); } @Override diff --git a/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java b/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java index 9bb86f3..476a3a6 100644 --- a/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java +++ b/Patchwork/src/main/java/fns/patchwork/provider/ContextProvider.java @@ -26,6 +26,7 @@ package fns.patchwork.provider; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -60,7 +61,7 @@ import org.jetbrains.annotations.Nullable; */ public class ContextProvider { - public T fromString(final String string, final Class clazz) + public Optional fromString(final String string, final Class clazz) { return Stream.of(toBoolean(string, clazz), toLong(string, clazz), @@ -74,9 +75,9 @@ public class ContextProvider toLocation(string, clazz), toComponent(string, clazz)) .filter(Objects::nonNull) - .findFirst() + .filter(clazz::isInstance) .map(clazz::cast) - .orElse(null); + .findFirst(); } private @Nullable Boolean toBoolean(final String string, final Class clazz) @@ -227,10 +228,9 @@ public class ContextProvider public @NotNull List<@Nullable T> getList(final List resolvable, final Class clazz) { final List resolved = new ArrayList<>(); - for (final String entry : resolvable) - { - resolved.add(this.fromString(entry, clazz)); - } + + resolvable.forEach(entry -> this.fromString(entry, clazz).ifPresent(resolved::add)); + return resolved; } } diff --git a/Veritas/src/main/java/fns/veritas/Aggregate.java b/Veritas/src/main/java/fns/veritas/Aggregate.java index 51afc09..c01a1a7 100644 --- a/Veritas/src/main/java/fns/veritas/Aggregate.java +++ b/Veritas/src/main/java/fns/veritas/Aggregate.java @@ -28,6 +28,7 @@ import fns.veritas.bukkit.BukkitNative; import fns.veritas.bukkit.ServerListener; import fns.veritas.client.BotClient; import fns.veritas.client.BotConfig; +import java.io.IOException; import org.bukkit.Bukkit; public class Aggregate @@ -40,13 +41,30 @@ public class Aggregate public Aggregate(final Veritas plugin) { + BotClient bot1; this.plugin = plugin; - this.bot = new BotClient(new BotConfig(plugin)); + + try + { + bot1 = new BotClient(new BotConfig(plugin)); + } + catch (IOException ex) + { + getLogger().error("Failed to load bot config! Shutting down..."); + getLogger().error(ex); + this.bot = null; + this.serverListener = null; + this.bukkitNativeListener = null; + Bukkit.getPluginManager().disablePlugin(plugin); + return; + } + this.bukkitNativeListener = new BukkitNative(plugin); this.serverListener = new ServerListener(plugin); Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin); this.getServerListener().minecraftChatBound().subscribe(); + this.bot = bot1; } public static FNS4J getLogger() diff --git a/Veritas/src/main/java/fns/veritas/client/BotConfig.java b/Veritas/src/main/java/fns/veritas/client/BotConfig.java index 08407ad..d2e1783 100644 --- a/Veritas/src/main/java/fns/veritas/client/BotConfig.java +++ b/Veritas/src/main/java/fns/veritas/client/BotConfig.java @@ -24,7 +24,8 @@ package fns.veritas.client; import discord4j.common.util.Snowflake; -import fns.patchwork.config.WrappedBukkitConfiguration; +import fns.patchwork.config.ConfigType; +import fns.patchwork.config.GenericConfig; import fns.veritas.Aggregate; import fns.veritas.Veritas; import java.io.File; @@ -41,12 +42,11 @@ public class BotConfig public static final String GUILD_ID = "guild_id"; @NonNls private static final String BOT_TOKEN = "bot_token"; - private final WrappedBukkitConfiguration config; + private final GenericConfig config; - public BotConfig(final Veritas plugin) + public BotConfig(final Veritas plugin) throws IOException { - this.config = new WrappedBukkitConfiguration(f0(plugin), - new File(plugin.getDataFolder(), "config.yml")); + this.config = new GenericConfig(ConfigType.TOML, plugin, "config.toml"); } public String getToken()