9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-28 19:39:22 +00:00

Better Leaves config command

This commit is contained in:
violetc
2025-02-26 20:26:14 +08:00
parent b20b504223
commit b6933897b8
17 changed files with 491 additions and 200 deletions

View File

@@ -8,9 +8,9 @@ import org.bukkit.command.Command;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.command.LeavesCommand;
import org.leavesmc.leaves.config.GlobalConfig;
import org.leavesmc.leaves.config.GlobalConfigCategory;
import org.leavesmc.leaves.config.RemovedConfig;
import org.leavesmc.leaves.config.annotations.GlobalConfig;
import org.leavesmc.leaves.config.annotations.GlobalConfigCategory;
import org.leavesmc.leaves.config.annotations.RemovedConfig;
import org.leavesmc.leaves.config.GlobalConfigManager;
import org.leavesmc.leaves.region.RegionFileFormat;
import org.leavesmc.leaves.util.MathUtils;
@@ -75,6 +75,22 @@ public final class LeavesConfig {
registerCommand("leaves", new LeavesCommand("leaves"));
}
public static void reload() {
if (!LeavesConfig.configFile.exists()) {
throw new RuntimeException("Leaves config file not found, please restart the server");
}
try {
config.load(LeavesConfig.configFile);
} catch (final Exception ex) {
LeavesLogger.LOGGER.severe("Failure to reload leaves config", ex);
SneakyThrow.sneaky(ex);
throw new RuntimeException(ex);
}
GlobalConfigManager.reload();
}
public static void save() {
try {
config.save(LeavesConfig.configFile);
@@ -768,7 +784,7 @@ public final class LeavesConfig {
private static class AlternativePlaceValidator extends EnumConfigValidator<AlternativePlaceType> {
@Override
public void runAfterLoader(AlternativePlaceType value, boolean firstLoad) {
public void runAfterLoader(AlternativePlaceType value, boolean reload) {
if (value != AlternativePlaceType.NONE) {
LeavesConfig.modify.disableDistanceCheckForUseItem = true;
}
@@ -805,8 +821,8 @@ public final class LeavesConfig {
private static class AutoUpdateValidator extends BooleanConfigValidator {
@Override
public void runAfterLoader(Boolean value, boolean firstLoad) {
if (firstLoad) {
public void runAfterLoader(Boolean value, boolean reload) {
if (reload) {
org.leavesmc.leaves.util.LeavesUpdateHelper.init();
if (value) {
LeavesLogger.LOGGER.warning("Auto-Update is not completely safe. Enabling it may cause data security problems!");

View File

@@ -1,6 +1,9 @@
package org.leavesmc.leaves.command;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.text.Component;
import net.minecraft.Util;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -15,19 +18,23 @@ import org.leavesmc.leaves.command.subcommands.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
public final class LeavesCommand extends Command {
static final String BASE_PERM = "bukkit.command.leaves.";
public final class LeavesCommand extends Command implements LeavesSuggestionCommand {
public static final String BASE_PERM = "bukkit.command.leaves.";
// subcommand label -> subcommand
private static final Map<String, LeavesSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, LeavesSubcommand> commands = new HashMap<>();
@@ -41,7 +48,6 @@ public final class LeavesCommand extends Command {
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
});
private static final Set<String> COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet());
public LeavesCommand(final String name) {
super(name);
@@ -68,11 +74,10 @@ public final class LeavesCommand extends Command {
}
@NotNull
@Override
public List<String> tabComplete(final @NotNull CommandSender sender, final @NotNull String alias, final String[] args, final @Nullable Location location) throws IllegalArgumentException {
if (args.length <= 1) {
return LeavesCommandUtil.getListMatchingLast(sender, args, COMPLETABLE_SUBCOMMANDS);
return LeavesCommandUtil.getListMatchingLast(sender, args, usableSubcommands());
}
final @Nullable Pair<String, LeavesSubcommand> subCommand = resolveCommand(args[0]);
@@ -83,6 +88,18 @@ public final class LeavesCommand extends Command {
return Collections.emptyList();
}
@Nullable
@Override
public CompletableFuture<Suggestions> tabSuggestion(final @NotNull CommandSender sender, final @NotNull String alias, final @NotNull String @NotNull [] args, final @Nullable Location location, final @NotNull SuggestionsBuilder builder) throws IllegalArgumentException {
if (args.length > 1) {
final @Nullable Pair<String, LeavesSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand != null) {
return subCommand.second().tabSuggestion(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length), location, builder);
}
}
return null;
}
@Override
public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final String @NotNull [] args) {
if (!testPermission(sender)) {
@@ -90,13 +107,13 @@ public final class LeavesCommand extends Command {
}
if (args.length == 0) {
sender.sendMessage(text("Usage: " + this.usageMessage, RED));
sender.sendMessage(unknownMessage());
return false;
}
final Pair<String, LeavesSubcommand> subCommand = resolveCommand(args[0]);
if (subCommand == null) {
sender.sendMessage(text("Usage: " + this.usageMessage, RED));
sender.sendMessage(unknownMessage());
return false;
}
@@ -107,6 +124,20 @@ public final class LeavesCommand extends Command {
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
}
private Collection<String> usableSubcommands() {
List<String> subcommands = new ArrayList<>();
for (var entry : SUBCOMMANDS.entrySet()) {
if (entry.getValue().tabCompletes()) {
subcommands.add(entry.getKey());
}
}
return subcommands;
}
public Component unknownMessage() {
return text("Usage: /bot [" + String.join(" | ", usableSubcommands()) + "]", RED);
}
@Nullable
private static Pair<String, LeavesSubcommand> resolveCommand(String label) {
label = label.toLowerCase(Locale.ENGLISH);
@@ -118,4 +149,5 @@ public final class LeavesCommand extends Command {
return null;
}
}

View File

@@ -7,12 +7,17 @@ import net.minecraft.resources.ResourceLocation;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@DefaultQualifier(NonNull.class)
public class LeavesCommandUtil {
@@ -78,4 +83,106 @@ public class LeavesCommandUtil {
return results;
}
// end copy stuff
public static List<String> getListClosestMatchingLast(
final CommandSender sender,
final String last,
final Collection<?> collection,
final String overridePermission
) {
ArrayList<Candidate> candidates = Lists.newArrayList();
if (collection.isEmpty() || !sender.hasPermission(overridePermission)) {
return Collections.emptyList();
}
String lastLower = last.toLowerCase();
for (String item : Iterables.transform(collection, Functions.toStringFunction())) {
String itemLower = item.toLowerCase();
if (itemLower.startsWith(lastLower)) {
candidates.add(Candidate.of(item, 0));
} else if (itemLower.contains(lastLower)) {
candidates.add(Candidate.of(item, damerauLevenshteinDistance(lastLower, itemLower)));
}
}
candidates.sort(Comparator.comparingInt(c -> c.score));
List<String> results = new ArrayList<>(candidates.size());
for (Candidate candidate : candidates) {
results.add(candidate.item);
}
return results;
}
private record Candidate(String item, int score) {
private static Candidate of(String item, int score) {
return new Candidate(item, score);
}
}
// Copy from org/bukkit/command/defaults/HelpCommand.java
/**
* Computes the Dameraur-Levenshtein Distance between two strings. Adapted
* from the algorithm at <a href="http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance">Wikipedia: DamerauLevenshtein distance</a>
*
* @param s1 The first string being compared.
* @param s2 The second string being compared.
* @return The number of substitutions, deletions, insertions, and
* transpositions required to get from s1 to s2.
*/
@SuppressWarnings("DuplicatedCode")
private static int damerauLevenshteinDistance(@Nullable String s1, @Nullable String s2) {
if (s1 == null && s2 == null) {
return 0;
}
if (s1 != null && s2 == null) {
return s1.length();
}
if (s1 == null && s2 != null) {
return s2.length();
}
int s1Len = s1.length();
int s2Len = s2.length();
int[][] H = new int[s1Len + 2][s2Len + 2];
int INF = s1Len + s2Len;
H[0][0] = INF;
for (int i = 0; i <= s1Len; i++) {
H[i + 1][1] = i;
H[i + 1][0] = INF;
}
for (int j = 0; j <= s2Len; j++) {
H[1][j + 1] = j;
H[0][j + 1] = INF;
}
Map<Character, Integer> sd = new HashMap<>();
for (char Letter : (s1 + s2).toCharArray()) {
if (!sd.containsKey(Letter)) {
sd.put(Letter, 0);
}
}
for (int i = 1; i <= s1Len; i++) {
int DB = 0;
for (int j = 1; j <= s2Len; j++) {
int i1 = sd.get(s2.charAt(j - 1));
int j1 = DB;
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
H[i + 1][j + 1] = H[i][j];
DB = j;
} else {
H[i + 1][j + 1] = Math.min(H[i][j], Math.min(H[i + 1][j], H[i][j + 1])) + 1;
}
H[i + 1][j + 1] = Math.min(H[i + 1][j + 1], H[i1][j1] + (i - i1 - 1) + 1 + (j - j1 - 1));
}
sd.put(s1.charAt(i - 1), i);
}
return H[s1Len + 1][s2Len + 1];
}
}

View File

@@ -1,10 +1,13 @@
package org.leavesmc.leaves.command;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface LeavesSubcommand {
boolean execute(CommandSender sender, String subCommand, String[] args);
@@ -13,6 +16,10 @@ public interface LeavesSubcommand {
return Collections.emptyList();
}
default CompletableFuture<Suggestions> tabSuggestion(final CommandSender sender, final String subCommand, final String[] args, final Location location, final SuggestionsBuilder builder) {
return null;
}
default boolean tabCompletes() {
return true;
}

View File

@@ -0,0 +1,15 @@
package org.leavesmc.leaves.command;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public interface LeavesSuggestionCommand {
@Nullable
CompletableFuture<Suggestions> tabSuggestion(@NotNull CommandSender sender, @NotNull String alias, @NotNull String @NotNull [] args, @Nullable Location location, @NotNull SuggestionsBuilder builder) throws IllegalArgumentException;
}

View File

@@ -1,17 +1,22 @@
package org.leavesmc.leaves.command.subcommands;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.command.LeavesCommandUtil;
import org.leavesmc.leaves.command.LeavesSubcommand;
import org.leavesmc.leaves.config.GlobalConfigManager;
import org.leavesmc.leaves.config.VerifiedConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ConfigCommand implements LeavesSubcommand {
@@ -22,12 +27,12 @@ public class ConfigCommand implements LeavesSubcommand {
return true;
}
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
if (verifiedConfig == null) {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" is Not Found.", NamedTextColor.GRAY)
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" is Not Found.", NamedTextColor.GRAY)
));
return true;
}
@@ -36,25 +41,25 @@ public class ConfigCommand implements LeavesSubcommand {
try {
verifiedConfig.set(args[1]);
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" changed to ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" changed to ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
} catch (IllegalArgumentException exception) {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" modify error by ", NamedTextColor.GRAY),
Component.text(exception.getMessage(), NamedTextColor.RED)
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.RED),
Component.text(" modify error by ", NamedTextColor.GRAY),
Component.text(exception.getMessage(), NamedTextColor.RED)
));
}
} else {
sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" value is ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
Component.text("Config ", NamedTextColor.GRAY),
Component.text(args[0], NamedTextColor.AQUA),
Component.text(" value is ", NamedTextColor.GRAY),
Component.text(verifiedConfig.getString(), NamedTextColor.AQUA)
));
}
@@ -63,22 +68,27 @@ public class ConfigCommand implements LeavesSubcommand {
@Override
public List<String> tabComplete(CommandSender sender, String subCommand, String[] args, Location location) {
switch (args.length) {
case 1 -> {
List<String> list = new ArrayList<>(GlobalConfigManager.getVerifiedConfigPaths());
return LeavesCommandUtil.getListMatchingLast(sender, args, list);
}
case 2 -> {
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
if (verifiedConfig != null) {
return LeavesCommandUtil.getListMatchingLast(sender, args, verifiedConfig.validator().valueSuggest());
} else {
return Collections.singletonList("<ERROR CONFIG>");
}
if (args.length == 2) {
VerifiedConfig verifiedConfig = GlobalConfigManager.getVerifiedConfig(args[0]);
if (verifiedConfig != null) {
return LeavesCommandUtil.getListMatchingLast(sender, args, verifiedConfig.validator().valueSuggest());
} else {
return Collections.singletonList("<ERROR CONFIG>");
}
}
return Collections.emptyList();
}
@Override
public CompletableFuture<Suggestions> tabSuggestion(CommandSender sender, String subCommand, String @NotNull [] args, @Nullable Location location, @NotNull SuggestionsBuilder builder) {
if (args.length == 1) {
String arg = args[0];
int dotIndex = arg.lastIndexOf(".");
builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + dotIndex + 2);
LeavesCommandUtil.getListClosestMatchingLast(sender, arg.substring(dotIndex + 1), GlobalConfigManager.getVerifiedConfigSubPaths(arg), "bukkit.command.leaves.config")
.forEach(builder::suggest);
return builder.buildFuture();
}
return null;
}
}

View File

@@ -1,12 +1,9 @@
package org.leavesmc.leaves.command.subcommands;
import net.minecraft.server.MinecraftServer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.command.LeavesSubcommand;
import java.io.File;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
@@ -14,9 +11,8 @@ import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
public class ReloadCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
MinecraftServer server = MinecraftServer.getServer();
LeavesConfig.init((File) server.options.valueOf("leaves-settings"));
Command.broadcastCommandMessage(sender, text("Leaves config reload complete.", GREEN));
LeavesConfig.reload();
sender.sendMessage(text("Leaves config reload complete.", GREEN));
return false;
}
}

View File

@@ -1,6 +1,7 @@
package org.leavesmc.leaves.command.subcommands;
import org.bukkit.ChatColor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.CommandSender;
import org.leavesmc.leaves.command.LeavesSubcommand;
import org.leavesmc.leaves.util.LeavesUpdateHelper;
@@ -9,7 +10,7 @@ public class UpdateCommand implements LeavesSubcommand {
@Override
public boolean execute(CommandSender sender, String subCommand, String[] args) {
sender.sendMessage(ChatColor.GRAY + "Trying to update Leaves, see the console for more info.");
sender.sendMessage(Component.text("Trying to update Leaves, see the console for more info.", NamedTextColor.GRAY));
LeavesUpdateHelper.tryUpdateLeaves();
return true;
}

View File

@@ -10,6 +10,6 @@ public interface ConfigValidator<E> extends ConfigConverter<E> {
return List.of("<value>");
}
default void runAfterLoader(E value, boolean firstLoad) {
default void runAfterLoader(E value, boolean reload) {
}
}

View File

@@ -4,6 +4,8 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.config.annotations.GlobalConfig;
import org.leavesmc.leaves.config.annotations.GlobalConfigCategory;
import java.io.File;
import java.io.IOException;
@@ -75,7 +77,7 @@ public class GlobalConfigCreator {
private static void initConfig(@NotNull Field field, GlobalConfig globalConfig, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
GlobalConfigManager.VerifiedConfig verifiedConfig = GlobalConfigManager.VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
VerifiedConfig verifiedConfig = VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
config.set(verifiedConfig.path(), verifiedConfig.validator().saveConvert(field.get(upstreamField)));
} catch (Exception e) {
e.printStackTrace();

View File

@@ -1,13 +1,15 @@
package org.leavesmc.leaves.config;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.config.annotations.GlobalConfig;
import org.leavesmc.leaves.config.annotations.GlobalConfigCategory;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -16,19 +18,34 @@ public class GlobalConfigManager {
public final static String CONFIG_START = "settings.";
private static boolean firstLoad = true;
private static final Map<String, VerifiedConfig> verifiedConfigs = new HashMap<>();
private static final ConfigNode rootNode = new ConfigNode("");
private static boolean loaded = false;
public static void init() {
verifiedConfigs.clear();
if (loaded) {
return;
}
for (Field field : LeavesConfig.class.getDeclaredFields()) {
initField(field, null, CONFIG_START);
}
verifiedConfigs.forEach((path, config) -> config.validator().runAfterLoader(config.get(), false));
LeavesConfig.save();
verifiedConfigs.forEach((path, config) -> config.validator.runAfterLoader(config.get(), firstLoad));
loaded = true;
}
firstLoad = false;
public static void reload() {
if (!loaded) {
return;
}
for (Field field : LeavesConfig.class.getDeclaredFields()) {
initField(field, null, CONFIG_START);
}
verifiedConfigs.forEach((path, config) -> config.validator().runAfterLoader(config.get(), true));
LeavesConfig.save();
}
@@ -39,6 +56,7 @@ public class GlobalConfigManager {
for (Field field : categoryField.getType().getDeclaredFields()) {
initField(field, category, categoryPath);
}
traverseToNodeOrCreate(categoryPath.substring(CONFIG_START.length()));
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config" + upstreamPath, e);
}
@@ -48,8 +66,8 @@ public class GlobalConfigManager {
if (upstreamField != null || Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
for (RemovedConfig config : field.getAnnotationsByType(RemovedConfig.class)) {
RemovedVerifiedConfig.build(config, field, upstreamField).run();
for (org.leavesmc.leaves.config.annotations.RemovedConfig config : field.getAnnotationsByType(org.leavesmc.leaves.config.annotations.RemovedConfig.class)) {
VerifiedRemovedConfig.build(config, field, upstreamField).run();
}
GlobalConfig globalConfig = field.getAnnotation(GlobalConfig.class);
@@ -67,19 +85,20 @@ public class GlobalConfigManager {
private static void initConfig(@NotNull Field field, GlobalConfig globalConfig, @Nullable Object upstreamField, @NotNull String upstreamPath) {
try {
VerifiedConfig verifiedConfig = VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
if (globalConfig.lock() && !firstLoad) {
verifiedConfigs.put(verifiedConfig.path.substring(CONFIG_START.length()), verifiedConfig);
if (loaded && globalConfig.lock()) {
return;
}
ConfigValidator<? super Object> validator = verifiedConfig.validator;
VerifiedConfig verifiedConfig = VerifiedConfig.build(globalConfig, field, upstreamField, upstreamPath);
ConfigValidator<? super Object> validator = verifiedConfig.validator();
String path = verifiedConfig.path();
Object defValue = validator.saveConvert(field.get(upstreamField));
LeavesConfig.config.addDefault(verifiedConfig.path, defValue);
LeavesConfig.config.addDefault(path, defValue);
try {
Object savedValue = LeavesConfig.config.get(verifiedConfig.path);
Object savedValue = LeavesConfig.config.get(path);
if (savedValue == null) {
throw new IllegalArgumentException("?");
}
@@ -92,11 +111,12 @@ public class GlobalConfigManager {
field.set(upstreamField, savedValue);
} catch (IllegalArgumentException | ClassCastException e) {
LeavesConfig.config.set(verifiedConfig.path, defValue);
LeavesConfig.config.set(path, defValue);
LeavesLogger.LOGGER.warning(e.getMessage() + ", reset to " + defValue);
}
verifiedConfigs.put(verifiedConfig.path.substring(CONFIG_START.length()), verifiedConfig);
verifiedConfigs.put(path.substring(CONFIG_START.length()), verifiedConfig);
traverseToNodeOrCreate(path.substring(CONFIG_START.length()));
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config", e);
throw new RuntimeException();
@@ -107,143 +127,49 @@ public class GlobalConfigManager {
return verifiedConfigs.get(path);
}
@Contract(pure = true)
public static @NotNull Set<String> getVerifiedConfigPaths() {
return verifiedConfigs.keySet();
}
private static class ConfigNode {
String name;
Map<String, ConfigNode> children;
public record RemovedVerifiedConfig(ConfigTransformer<? super Object, ? super Object> transformer, boolean transform, Field field, Object upstreamField, String path) {
public void run() {
if (transform) {
if (LeavesConfig.config.contains(path)) {
Object savedValue = LeavesConfig.config.get(path);
if (savedValue != null) {
try {
if (savedValue.getClass() != transformer.getFieldClass()) {
savedValue = transformer.loadConvert(savedValue);
}
savedValue = transformer.transform(savedValue);
field.set(upstreamField, savedValue);
} catch (IllegalAccessException | IllegalArgumentException e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
} else {
LeavesLogger.LOGGER.warning("Failed to convert saved value for " + path + ", reset to default");
}
}
}
LeavesConfig.config.set(path, null);
}
@Contract("_, _, _ -> new")
public static @NotNull RemovedVerifiedConfig build(@NotNull RemovedConfig config, @NotNull Field field, @Nullable Object upstreamField) {
StringBuilder path = new StringBuilder("settings.");
for (String category : config.category()) {
path.append(category).append(".");
}
path.append(config.name());
ConfigTransformer<? super Object, ? super Object> transformer = null;
try {
transformer = createTransformer(config.transformer(), field);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
return new RemovedVerifiedConfig(transformer, config.transform(), field, upstreamField, path.toString());
public ConfigNode(String name) {
this.name = name;
this.children = new HashMap<>();
}
}
public record VerifiedConfig(ConfigValidator<? super Object> validator, boolean lock, Field field, Object upstreamField, String path) {
private static void traverseToNodeOrCreate(@NotNull String path) {
String[] parts = path.split("\\.");
ConfigNode current = rootNode;
public void set(String stringValue) throws IllegalArgumentException {
Object value;
try {
value = validator.stringConvert(stringValue);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("value parse error: " + e.getMessage());
for (String part : parts) {
if (!part.isEmpty()) {
current = current.children.computeIfAbsent(part, ConfigNode::new);
}
validator.verify(this.get(), value);
try {
LeavesConfig.config.set(path, validator.saveConvert(value));
LeavesConfig.save();
if (lock) {
throw new IllegalArgumentException("locked, will load after restart");
}
field.set(upstreamField, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
public Object get() {
try {
return field.get(upstreamField);
} catch (IllegalAccessException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
return "<VALUE ERROR>";
}
}
public String getString() {
Object value = this.get();
Object savedValue = LeavesConfig.config.get(path);
try {
if (savedValue != null) {
if (validator.getFieldClass() != savedValue.getClass()) {
savedValue = validator.loadConvert(savedValue);
}
if (!savedValue.equals(value)) {
return value.toString() + "(" + savedValue + " after restart)";
}
}
} catch (IllegalArgumentException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
}
return value.toString();
}
@NotNull
@Contract("_, _, _, _ -> new")
public static VerifiedConfig build(@NotNull GlobalConfig config, @NotNull Field field, @Nullable Object upstreamField, @NotNull String upstreamPath) {
String path = upstreamPath + config.value();
ConfigValidator<? super Object> validator;
try {
validator = createValidator(config.validator(), field);
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config" + path, e);
throw new RuntimeException();
}
return new VerifiedConfig(validator, config.lock(), field, upstreamField, path);
}
}
@SuppressWarnings("unchecked")
private static ConfigValidator<? super Object> createValidator(@NotNull Class<? extends ConfigValidator<?>> clazz, Field field) throws Exception {
if (clazz.equals(AutoConfigValidator.class)) {
return (ConfigValidator<? super Object>) AutoConfigValidator.createValidator(field);
} else {
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return (ConfigValidator<? super Object>) constructor.newInstance();
@Nullable
private static ConfigNode traverseToNode(@NotNull String path) {
int index = path.lastIndexOf('.');
String[] parts = index == -1 ? new String[0] : path.substring(0, index).split("\\.");
ConfigNode current = rootNode;
for (String part : parts) {
current = current.children.get(part);
if (current == null) {
return null;
}
}
return current;
}
@SuppressWarnings("unchecked")
private static ConfigTransformer<? super Object, ? super Object> createTransformer(@NotNull Class<? extends ConfigTransformer<?, ?>> clazz, Field field) throws Exception {
if (clazz.equals(AutoConfigTransformer.class)) {
return (ConfigTransformer<? super Object, ? super Object>) AutoConfigTransformer.createValidator(field);
} else {
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return (ConfigTransformer<? super Object, ? super Object>) constructor.newInstance();
@NotNull
public static Set<String> getVerifiedConfigSubPaths(@NotNull String prefix) {
ConfigNode current = traverseToNode(prefix);
if (current == null) {
return Collections.emptySet();
}
return current.children.keySet();
}
}

View File

@@ -0,0 +1,91 @@
package org.leavesmc.leaves.config;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.config.annotations.GlobalConfig;
import java.lang.reflect.Field;
public record VerifiedConfig(ConfigValidator<? super Object> validator, boolean lock, Field field, Object upstreamField, String path) {
public void set(String stringValue) throws IllegalArgumentException {
Object value;
try {
value = validator.stringConvert(stringValue);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("value parse error: " + e.getMessage());
}
validator.verify(this.get(), value);
try {
LeavesConfig.config.set(path, validator.saveConvert(value));
LeavesConfig.save();
if (lock) {
throw new IllegalArgumentException("locked, will load after restart");
}
field.set(upstreamField, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
public Object get() {
try {
return field.get(upstreamField);
} catch (IllegalAccessException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
return "<VALUE ERROR>";
}
}
public String getString() {
Object value = this.get();
Object savedValue = LeavesConfig.config.get(path);
try {
if (savedValue != null) {
if (validator.getFieldClass() != savedValue.getClass()) {
savedValue = validator.loadConvert(savedValue);
}
if (!savedValue.equals(value)) {
return value.toString() + "(" + savedValue + " after restart)";
}
}
} catch (IllegalArgumentException e) {
LeavesLogger.LOGGER.severe("Failure to get " + path + " value", e);
}
return value.toString();
}
@NotNull
@Contract("_, _, _, _ -> new")
public static VerifiedConfig build(@NotNull GlobalConfig config, @NotNull Field field, @Nullable Object upstreamField, @NotNull String upstreamPath) {
String path = upstreamPath + config.value();
ConfigValidator<? super Object> validator;
try {
validator = createValidator(config.validator(), field);
} catch (Exception e) {
LeavesLogger.LOGGER.severe("Failure to load leaves config" + path, e);
throw new RuntimeException();
}
return new VerifiedConfig(validator, config.lock(), field, upstreamField, path);
}
@SuppressWarnings("unchecked")
private static ConfigValidator<? super Object> createValidator(@NotNull Class<? extends ConfigValidator<?>> clazz, Field field) throws Exception {
if (clazz.equals(AutoConfigValidator.class)) {
return (ConfigValidator<? super Object>) AutoConfigValidator.createValidator(field);
} else {
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return (ConfigValidator<? super Object>) constructor.newInstance();
}
}
}

View File

@@ -0,0 +1,63 @@
package org.leavesmc.leaves.config;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.LeavesLogger;
import java.lang.reflect.Field;
public record VerifiedRemovedConfig(ConfigTransformer<? super Object, ? super Object> transformer, boolean transform, Field field, Object upstreamField, String path) {
public void run() {
if (transform) {
if (LeavesConfig.config.contains(path)) {
Object savedValue = LeavesConfig.config.get(path);
if (savedValue != null) {
try {
if (savedValue.getClass() != transformer.getFieldClass()) {
savedValue = transformer.loadConvert(savedValue);
}
savedValue = transformer.transform(savedValue);
field.set(upstreamField, savedValue);
} catch (IllegalAccessException | IllegalArgumentException e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
} else {
LeavesLogger.LOGGER.warning("Failed to convert saved value for " + path + ", reset to default");
}
}
}
LeavesConfig.config.set(path, null);
}
@Contract("_, _, _ -> new")
public static @NotNull VerifiedRemovedConfig build(@NotNull org.leavesmc.leaves.config.annotations.RemovedConfig config, @NotNull Field field, @Nullable Object upstreamField) {
StringBuilder path = new StringBuilder("settings.");
for (String category : config.category()) {
path.append(category).append(".");
}
path.append(config.name());
ConfigTransformer<? super Object, ? super Object> transformer = null;
try {
transformer = createTransformer(config.transformer(), field);
} catch (Exception e) {
LeavesLogger.LOGGER.warning("Failure to load leaves config" + path, e);
}
return new VerifiedRemovedConfig(transformer, config.transform(), field, upstreamField, path.toString());
}
@SuppressWarnings("unchecked")
private static ConfigTransformer<? super Object, ? super Object> createTransformer(@NotNull Class<? extends ConfigTransformer<?, ?>> clazz, Field field) throws Exception {
if (clazz.equals(AutoConfigTransformer.class)) {
return (ConfigTransformer<? super Object, ? super Object>) AutoConfigTransformer.createValidator(field);
} else {
var constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return (ConfigTransformer<? super Object, ? super Object>) constructor.newInstance();
}
}
}

View File

@@ -1,4 +1,7 @@
package org.leavesmc.leaves.config;
package org.leavesmc.leaves.config.annotations;
import org.leavesmc.leaves.config.AutoConfigValidator;
import org.leavesmc.leaves.config.ConfigValidator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@@ -1,4 +1,4 @@
package org.leavesmc.leaves.config;
package org.leavesmc.leaves.config.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@@ -1,4 +1,7 @@
package org.leavesmc.leaves.config;
package org.leavesmc.leaves.config.annotations;
import org.leavesmc.leaves.config.AutoConfigTransformer;
import org.leavesmc.leaves.config.ConfigTransformer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;