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.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<String> 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<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 Set<Completion> completions = command.getCompletions();
if (completions == null || completions.isEmpty())
return results;
if (args.length == 0)
{

View File

@ -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.
*/
<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

View File

@ -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 <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
@ApiStatus.Internal
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
@ -195,20 +249,22 @@ public final class GenericConfiguration implements Configuration
@Override
public <T> Optional<T> get(String path, Class<T> 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> T getOrDefault(String path, Class<T> clazz, T fallback)
{
return this.get(path, clazz).orElse(fallback);
return this.get(path, clazz)
.orElse(fallback);
}
@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);
}

View File

@ -94,7 +94,7 @@ public final class WrappedBukkitConfiguration implements Configuration
}
@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);
}
@ -132,7 +132,7 @@ public final class WrappedBukkitConfiguration implements Configuration
@Override
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

View File

@ -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> 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),
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 <T> List<@Nullable T> getList(final List<String> resolvable, final Class<T> clazz)
{
final List<T> 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;
}
}

View File

@ -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()

View File

@ -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()