Minor tweaks to GenericConfig & ContextProvider

# Changes:
- Changed Configuration#getList(String, Class) to Configuration#getCollection(String, Class)

- Renamed GenericConfiguration -> GenericConfig

- Implemented semantics for GenericConfig#getCollection and GenericConfig#getStringList

- Adjusted return value of ContextProvider#fromString to return Optional<T> instead of @Nullable T

- Adjusted classes which used previous API methods to use the newly updated ones.
This commit is contained in:
Paul Reilly 2023-08-30 20:49:22 -05:00
parent 26f4e0746b
commit 4681fc9596
7 changed files with 172 additions and 83 deletions

View File

@ -32,6 +32,7 @@ import fns.patchwork.utils.logging.FNS4J;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -156,50 +157,19 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC
if (argTypes.length > args.length) if (argTypes.length > args.length)
return; return;
final Player p = (sender instanceof Player player) ? player : null;
final Object[] objects = new Object[argTypes.length + 1]; final Object[] objects = new Object[argTypes.length + 1];
for (int i = 0; i < argTypes.length; i++) parseArguments(args, provider, argTypes, objects);
{
final Class<?> argType = argTypes[i];
final String arg = args[i];
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 try
{ {
if (noConsole) if (noConsole)
{ {
command.getSubcommands() command.getSubcommands()
.get(node) .get(node)
.invoke(command, (Player) sender, objects); .invoke(command, p, objects);
} }
else else
{ {
@ -215,11 +185,55 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC
} }
} }
@Override private void parseArguments(@NotNull String @NotNull [] args,
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] 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<String> tabComplete(final @NotNull CommandSender sender, final @NotNull String alias,
final String[] args)
{ {
final Set<Completion> completions = command.getCompletions();
final List<String> results = new ArrayList<>(); final List<String> results = new ArrayList<>();
final Set<Completion> completions = command.getCompletions();
if (completions == null || completions.isEmpty())
return results;
if (args.length == 0) if (args.length == 0)
{ {

View File

@ -25,6 +25,7 @@ package fns.patchwork.config;
import fns.patchwork.provider.Context; import fns.patchwork.provider.Context;
import fns.patchwork.provider.ContextProvider; import fns.patchwork.provider.ContextProvider;
import java.util.Collection;
import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.Unmodifiable;
import java.io.File; import java.io.File;
@ -87,7 +88,7 @@ public interface Configuration
* @param clazz The class of the type. * @param clazz The class of the type.
* @return The List object. * @return The List object.
*/ */
<T> @Unmodifiable List<T> getList(String path, Class<T> clazz); <T> @Unmodifiable Collection<T> getCollection(String path, Class<T> clazz);
/** /**
* Gets a List object from the associated path. The List that is returned will be the String values which are stored * Gets a List object from the associated path. The List that is returned will be the String values which are stored

View File

@ -26,7 +26,9 @@ package fns.patchwork.config;
import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.ConfigFormat; import com.electronwill.nightconfig.core.ConfigFormat;
import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig;
import fns.patchwork.provider.ContextProvider;
import fns.patchwork.utils.FileUtils; import fns.patchwork.utils.FileUtils;
import fns.patchwork.utils.logging.FNS4J;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
@ -34,22 +36,24 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable; 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 File configFile;
private final String fileName; private final String fileName;
private final Config config; private final Config config;
private final ConfigType configType; private final ConfigType configType;
public GenericConfiguration(@NotNull final ConfigType configType, public GenericConfig(@NotNull final ConfigType configType,
@Nullable final JavaPlugin plugin, @Nullable final JavaPlugin plugin,
@NotNull final File dataFolder, @NotNull final File dataFolder,
@NotNull final String fileName, @NotNull final String fileName,
@ -78,19 +82,19 @@ public final class GenericConfiguration implements Configuration
this.load(); 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 throws IOException
{ {
this(type, null, dataFolder, fileName, false); 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 throws IOException
{ {
this(type, plugin, plugin.getDataFolder(), fileName, false); this(type, plugin, plugin.getDataFolder(), fileName, false);
} }
public GenericConfiguration(final ConfigType type, final File dataFolder, final String fileName, public GenericConfig(final ConfigType type, final File dataFolder, final String fileName,
final boolean isConcurrent) final boolean isConcurrent)
throws IOException throws IOException
{ {
@ -114,8 +118,10 @@ public final class GenericConfiguration implements Configuration
} }
@Override @Override
public void load() throws IOException { public void load() throws IOException
try (final FileReader reader = new FileReader(this.configFile)) { {
try (final FileReader reader = new FileReader(this.configFile))
{
this.config.clear(); this.config.clear();
final UnmodifiableConfig parsed = this.configType.getParser().parse(reader).unmodifiable(); final UnmodifiableConfig parsed = this.configType.getParser().parse(reader).unmodifiable();
@ -145,7 +151,7 @@ public final class GenericConfiguration implements Configuration
} }
@Override @Override
public boolean getBoolean(String path) public boolean getBoolean(final String path)
{ {
if (!(this.getConfig().get(path) instanceof Boolean)) if (!(this.getConfig().get(path) instanceof Boolean))
throw new IllegalArgumentException(String.format("Value at path %s is not a boolean!", path)); 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); return this.getConfig().get(path);
} }
@Override
@ApiStatus.Internal
public @Unmodifiable <T> List<T> getList(String path, Class<T> 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 <T> Collection<T> getCollection(String path, Class<T> 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<T> collected = new ArrayList<>();
collection.stream()
.map(obj ->
{
final Optional<T> 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 @Override
@ApiStatus.Internal
public @Unmodifiable List<String> getStringList(String path) public @Unmodifiable List<String> 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<String> 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 @Override
@ -195,20 +249,22 @@ public final class GenericConfiguration implements Configuration
@Override @Override
public <T> Optional<T> get(String path, Class<T> clazz) public <T> Optional<T> get(String path, Class<T> clazz)
{ {
// I love ternary statements, sorry Allink :) return this.getConfig()
return clazz.isInstance(this.getConfig().get(path)) ? .getOptional(path)
Optional.of(clazz.cast(this.getConfig().get(path))) : .filter(clazz::isInstance)
Optional.empty(); .map(clazz::cast);
} }
@Override @Override
public <T> T getOrDefault(String path, Class<T> clazz, T fallback) public <T> T getOrDefault(String path, Class<T> clazz, T fallback)
{ {
return this.get(path, clazz).orElse(fallback); return this.get(path, clazz)
.orElse(fallback);
} }
@Override @Override
public <T> void set(final String path, final T value) { public <T> void set(final String path, final T value)
{
this.config.set(path, value); this.config.set(path, value);
} }

View File

@ -94,7 +94,7 @@ public final class WrappedBukkitConfiguration implements Configuration
} }
@Override @Override
public <T> List<T> getList(String path, Class<T> clazz) public <T> List<T> getCollection(String path, Class<T> clazz)
{ {
return this.contextProvider.getList(this.getStringList(path), clazz); return this.contextProvider.getList(this.getStringList(path), clazz);
} }
@ -132,7 +132,7 @@ public final class WrappedBukkitConfiguration implements Configuration
@Override @Override
public <T> Optional<T> get(String path, Class<T> clazz) public <T> Optional<T> get(String path, Class<T> clazz)
{ {
return Optional.ofNullable(this.contextProvider.fromString(path, clazz)); return this.contextProvider.fromString(path, clazz);
} }
@Override @Override

View File

@ -26,6 +26,7 @@ package fns.patchwork.provider;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -60,7 +61,7 @@ import org.jetbrains.annotations.Nullable;
*/ */
public class ContextProvider public class ContextProvider
{ {
public <T> T fromString(final String string, final Class<T> clazz) public <T> Optional<T> fromString(final String string, final Class<T> clazz)
{ {
return Stream.of(toBoolean(string, clazz), return Stream.of(toBoolean(string, clazz),
toLong(string, clazz), toLong(string, clazz),
@ -74,9 +75,9 @@ public class ContextProvider
toLocation(string, clazz), toLocation(string, clazz),
toComponent(string, clazz)) toComponent(string, clazz))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .filter(clazz::isInstance)
.map(clazz::cast) .map(clazz::cast)
.orElse(null); .findFirst();
} }
private @Nullable Boolean toBoolean(final String string, final Class<?> clazz) private @Nullable Boolean toBoolean(final String string, final Class<?> clazz)
@ -227,10 +228,9 @@ public class ContextProvider
public @NotNull <T> List<@Nullable T> getList(final List<String> resolvable, final Class<T> clazz) public @NotNull <T> List<@Nullable T> getList(final List<String> resolvable, final Class<T> clazz)
{ {
final List<T> resolved = new ArrayList<>(); final List<T> resolved = new ArrayList<>();
for (final String entry : resolvable)
{ resolvable.forEach(entry -> this.fromString(entry, clazz).ifPresent(resolved::add));
resolved.add(this.fromString(entry, clazz));
}
return resolved; return resolved;
} }
} }

View File

@ -28,6 +28,7 @@ import fns.veritas.bukkit.BukkitNative;
import fns.veritas.bukkit.ServerListener; import fns.veritas.bukkit.ServerListener;
import fns.veritas.client.BotClient; import fns.veritas.client.BotClient;
import fns.veritas.client.BotConfig; import fns.veritas.client.BotConfig;
import java.io.IOException;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
public class Aggregate public class Aggregate
@ -40,13 +41,30 @@ public class Aggregate
public Aggregate(final Veritas plugin) public Aggregate(final Veritas plugin)
{ {
BotClient bot1;
this.plugin = plugin; 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.bukkitNativeListener = new BukkitNative(plugin);
this.serverListener = new ServerListener(plugin); this.serverListener = new ServerListener(plugin);
Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin); Bukkit.getServer().getPluginManager().registerEvents(this.getBukkitNativeListener(), plugin);
this.getServerListener().minecraftChatBound().subscribe(); this.getServerListener().minecraftChatBound().subscribe();
this.bot = bot1;
} }
public static FNS4J getLogger() public static FNS4J getLogger()

View File

@ -24,7 +24,8 @@
package fns.veritas.client; package fns.veritas.client;
import discord4j.common.util.Snowflake; 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.Aggregate;
import fns.veritas.Veritas; import fns.veritas.Veritas;
import java.io.File; import java.io.File;
@ -41,12 +42,11 @@ public class BotConfig
public static final String GUILD_ID = "guild_id"; public static final String GUILD_ID = "guild_id";
@NonNls @NonNls
private static final String BOT_TOKEN = "bot_token"; 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), this.config = new GenericConfig(ConfigType.TOML, plugin, "config.toml");
new File(plugin.getDataFolder(), "config.yml"));
} }
public String getToken() public String getToken()