From 84bf81419235020ae4284cdcac92913fa5f1187f Mon Sep 17 00:00:00 2001 From: MATRIX-feather Date: Tue, 8 Oct 2024 20:54:16 +0800 Subject: [PATCH] misc: Use StandaloneYamlConfigManager to manage recipe config --- .../morph/misc/recipe/RecipeManager.java | 131 +++--------------- .../morph/misc/recipe/RecipeOptions.java | 1 - .../misc/recipe/RecipeYamlConfigManager.java | 75 ++++++++++ .../recipe/StandaloneYamlConfigManager.java | 120 +++++++++++----- src/main/resources/recipes.yml | 2 + 5 files changed, 186 insertions(+), 143 deletions(-) create mode 100644 src/main/java/xiamomc/morph/misc/recipe/RecipeYamlConfigManager.java diff --git a/src/main/java/xiamomc/morph/misc/recipe/RecipeManager.java b/src/main/java/xiamomc/morph/misc/recipe/RecipeManager.java index 3a9f2ecc..d1af851c 100644 --- a/src/main/java/xiamomc/morph/misc/recipe/RecipeManager.java +++ b/src/main/java/xiamomc/morph/misc/recipe/RecipeManager.java @@ -1,6 +1,5 @@ package xiamomc.morph.misc.recipe; -import com.google.common.base.Charsets; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.kyori.adventure.text.Component; @@ -8,14 +7,12 @@ import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.inventory.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import xiamomc.morph.MorphPluginObject; import xiamomc.morph.config.MorphConfigManager; import xiamomc.morph.utilities.ItemUtils; -import xiamomc.morph.utilities.PluginAssetUtils; import xiamomc.pluginbase.Annotations.Initializer; import java.io.*; @@ -25,10 +22,7 @@ public class RecipeManager extends MorphPluginObject { - @Nullable - private YamlConfiguration yamlConfiguration; - - private final File configFile = new File(plugin.getDataFolder(), "recipes.yml"); + private final StandaloneYamlConfigManager configManager = new RecipeYamlConfigManager(new File(plugin.getDataFolder(), "recipes.yml"), "recipes.yml"); private boolean allowCrafting = false; private boolean unShaped = false; @@ -38,87 +32,33 @@ public class RecipeManager extends MorphPluginObject private String resultName = "~UNSET"; private List resultLore = new ObjectArrayList<>(); - public void reload() - { - var newConfig = new YamlConfiguration(); - - if (!configFile.exists()) - { - if (!copyInternalRecipeResource()) - { - logger.error("Can't create file to save configuration! Not reloading recipes..."); - return; - } - } - - try - { - newConfig.load(configFile); - } - catch (Throwable e) - { - logger.error("Unable to load recipe configuration: " + e.getMessage()); - return; - } - - this.yamlConfiguration = newConfig; - readValuesFromConfig(newConfig); - prepareRecipe(); - } - - private void readValuesFromConfig(YamlConfiguration config) + @Initializer + private void load(MorphConfigManager configManager) { - allowCrafting = config.getBoolean(RecipeOptions.ALLOW_DISGUISE_TOOL_CRAFTING.toString(), false); - unShaped = config.getBoolean(RecipeOptions.DISGUISE_TOOL_CRAFTING_UNSHAPED.toString(), false); - shape = config.getStringList(RecipeOptions.DISGUISE_TOOL_CRAFTING_SHAPE.toString()); - resultMaterialId = config.getString(RecipeOptions.DISGUISE_TOOL_RESULT_MATERIAL.toString(), "~UNSET"); - resultName = config.getString(RecipeOptions.DISGUISE_TOOL_RESULT_NAME.toString(), "~UNSET"); - resultLore = config.getStringList(RecipeOptions.DISGUISE_TOOL_RESULT_LORE.toString()); - - var materialSection = config.getConfigurationSection(RecipeOptions.DISGUISE_TOOL_CRAFTING_MATERIALS.toString()); - - if (materialSection != null) - { - materialSection.getKeys(false).forEach(key -> - { - var value = materialSection.getString(key, "~UNSET"); - materials.put(key, value); - }); - } + reload(); } - private boolean copyInternalRecipeResource() + public void reload() { - try - { - if (!configFile.createNewFile()) - return false; + this.configManager.reload(); - try (var writer = new OutputStreamWriter(new FileOutputStream(configFile), Charsets.UTF_8)) - { - writer.write(PluginAssetUtils.getFileStrings("recipes.yml")); - } - catch (Throwable t) - { - logger.error("Can't write content: " + t.getMessage()); - return false; - } - - return true; - } - catch (Throwable t) - { - logger.error("Can't create config file: " + t.getMessage()); - } - - return true; + readValuesFromConfig(this.configManager); + prepareRecipe(); } - @Initializer - private void load(MorphConfigManager configManager) + private void readValuesFromConfig(StandaloneYamlConfigManager configManager) { - if (this.yamlConfiguration == null) - reload(); + allowCrafting = configManager.getOrDefault(RecipeOptions.ALLOW_DISGUISE_TOOL_CRAFTING); + unShaped = configManager.getOrDefault(RecipeOptions.DISGUISE_TOOL_CRAFTING_UNSHAPED); + shape = configManager.getList(RecipeOptions.DISGUISE_TOOL_CRAFTING_SHAPE); + resultMaterialId = configManager.getOrDefault(RecipeOptions.DISGUISE_TOOL_RESULT_MATERIAL); + resultName = configManager.getOrDefault(RecipeOptions.DISGUISE_TOOL_RESULT_NAME); + resultLore = configManager.getList(RecipeOptions.DISGUISE_TOOL_RESULT_LORE); + var material = configManager.getMap(RecipeOptions.DISGUISE_TOOL_CRAFTING_MATERIALS); + this.materials.clear(); + + if (material != null) + this.materials.putAll(material); } @Nullable @@ -239,35 +179,4 @@ private void test_dumpExsampleConfig() materials.put("A", Material.BEDROCK.key().asString()); materials.put("B", Material.ACACIA_BOAT.key().asString()); } - - private void test_saveConfig() - { - if (yamlConfiguration == null) - { - logger.error("Null config!"); - return; - } - - yamlConfiguration.set(RecipeOptions.ALLOW_DISGUISE_TOOL_CRAFTING.toString(), allowCrafting); - yamlConfiguration.set(RecipeOptions.DISGUISE_TOOL_CRAFTING_UNSHAPED.toString(), this.unShaped); - yamlConfiguration.set(RecipeOptions.DISGUISE_TOOL_CRAFTING_SHAPE.toString(), this.shape); - yamlConfiguration.set(RecipeOptions.DISGUISE_TOOL_RESULT_MATERIAL.toString(), this.resultMaterialId); - yamlConfiguration.set(RecipeOptions.DISGUISE_TOOL_RESULT_NAME.toString(), this.resultName); - yamlConfiguration.set(RecipeOptions.DISGUISE_TOOL_RESULT_LORE.toString(), this.resultLore); - - this.materials.forEach((str, id) -> - { - var node = RecipeOptions.DISGUISE_TOOL_CRAFTING_MATERIALS.toString() + "." + str; - yamlConfiguration.set(node, id); - }); - - try - { - yamlConfiguration.save(configFile); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/xiamomc/morph/misc/recipe/RecipeOptions.java b/src/main/java/xiamomc/morph/misc/recipe/RecipeOptions.java index 8cfd6b21..bfaa553d 100644 --- a/src/main/java/xiamomc/morph/misc/recipe/RecipeOptions.java +++ b/src/main/java/xiamomc/morph/misc/recipe/RecipeOptions.java @@ -17,7 +17,6 @@ public class RecipeOptions public static final ConfigOption DISGUISE_TOOL_RESULT_MATERIAL = new ConfigOption<>(skillItemNode().append("result_material"), "minecraft:feather"); public static final ConfigOption DISGUISE_TOOL_RESULT_NAME = new ConfigOption<>(skillItemNode().append("result_item_name"), "~UNSET"); public static final ConfigOption> DISGUISE_TOOL_RESULT_LORE = new ConfigOption<>(skillItemNode().append("result_item_lore"), new ArrayList<>()); - public static final ConfigOption CONFIG_VERSION = new ConfigOption<>(ConfigNode.create().append("version"), 0); private static ConfigNode craftingNode() { diff --git a/src/main/java/xiamomc/morph/misc/recipe/RecipeYamlConfigManager.java b/src/main/java/xiamomc/morph/misc/recipe/RecipeYamlConfigManager.java new file mode 100644 index 00000000..c7daba54 --- /dev/null +++ b/src/main/java/xiamomc/morph/misc/recipe/RecipeYamlConfigManager.java @@ -0,0 +1,75 @@ +package xiamomc.morph.misc.recipe; + +import com.google.common.base.Charsets; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.jetbrains.annotations.Nullable; +import xiamomc.morph.utilities.PluginAssetUtils; +import xiamomc.pluginbase.Configuration.ConfigOption; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.util.List; + +public class RecipeYamlConfigManager extends StandaloneYamlConfigManager +{ + public RecipeYamlConfigManager(File file, @Nullable String internalResourceName) + { + super(file, internalResourceName); + } + + /** + * Copy internal resource to the location + * + * @return Whether this operation was successful + */ + @Override + protected boolean copyInternalResource() + { + try + { + if (!configFile.createNewFile()) + return false; + + try (var writer = new OutputStreamWriter(new FileOutputStream(configFile), Charsets.UTF_8)) + { + writer.write(PluginAssetUtils.getFileStrings("recipes.yml")); + } + catch (Throwable t) + { + logger.error("Can't write content: " + t.getMessage()); + return false; + } + + return true; + } + catch (Throwable t) + { + logger.error("Can't create config file: " + t.getMessage()); + } + + return true; + } + + @Override + protected int getExpectedConfigVersion() + { + return 1; + } + + private final List> options = new ObjectArrayList<>(List.of( + RecipeOptions.DISGUISE_TOOL_CRAFTING_SHAPE, + RecipeOptions.DISGUISE_TOOL_RESULT_LORE, + RecipeOptions.DISGUISE_TOOL_RESULT_NAME, + RecipeOptions.ALLOW_DISGUISE_TOOL_CRAFTING, + RecipeOptions.DISGUISE_TOOL_CRAFTING_MATERIALS, + RecipeOptions.DISGUISE_TOOL_CRAFTING_UNSHAPED, + RecipeOptions.DISGUISE_TOOL_RESULT_MATERIAL + )); + + @Override + protected List> getAllOptions() + { + return options; + } +} diff --git a/src/main/java/xiamomc/morph/misc/recipe/StandaloneYamlConfigManager.java b/src/main/java/xiamomc/morph/misc/recipe/StandaloneYamlConfigManager.java index ee8a9b8f..d66b69f5 100644 --- a/src/main/java/xiamomc/morph/misc/recipe/StandaloneYamlConfigManager.java +++ b/src/main/java/xiamomc/morph/misc/recipe/StandaloneYamlConfigManager.java @@ -1,30 +1,32 @@ package xiamomc.morph.misc.recipe; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import xiamomc.morph.MorphPluginObject; -import xiamomc.pluginbase.Bindables.Bindable; +import xiamomc.pluginbase.Configuration.ConfigNode; import xiamomc.pluginbase.Configuration.ConfigOption; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; public abstract class StandaloneYamlConfigManager extends MorphPluginObject { - private YamlConfiguration backendConfiguration; + protected YamlConfiguration backendConfiguration; - private final File file; + @NotNull + protected final File configFile; - public StandaloneYamlConfigManager(File file) - { - this.file = file; + @Nullable + private final String internalResourceName; + + public static final ConfigOption CONFIG_VERSION = new ConfigOption<>(ConfigNode.create().append("version"), 0); - reload(); + public StandaloneYamlConfigManager(@NotNull File file, @Nullable String internalResourceName) + { + this.configFile = file; + this.internalResourceName = internalResourceName; } /** @@ -33,11 +35,13 @@ public StandaloneYamlConfigManager(File file) */ protected abstract boolean copyInternalResource(); + protected abstract int getExpectedConfigVersion(); + public void reload() { var newConfig = new YamlConfiguration(); - if (!file.exists()) + if (!configFile.exists()) { if (!copyInternalResource()) { @@ -48,7 +52,7 @@ public void reload() try { - newConfig.load(file); + newConfig.load(configFile); } catch (Throwable e) { @@ -56,32 +60,83 @@ public void reload() return; } + if (this.backendConfiguration == null) + this.backendConfiguration = newConfig; + + var configVersion = newConfig.getInt(CONFIG_VERSION.toString(), 0); + if (configVersion < this.getExpectedConfigVersion()) + this.migrate(this.backendConfiguration, newConfig); + this.backendConfiguration = newConfig; } - private final Map> bindableMap = new ConcurrentHashMap<>(); + @NotNull + protected Map getAllNotDefault(Collection> options) + { + var map = new Object2ObjectOpenHashMap(); + + for (var o : options) + { + Object val; - public Bindable getBindable(ConfigOption option) + if (o.getDefault() instanceof List) + val = getList(o); + else if (o.getDefault() instanceof Map) + val = getMap(o); + else + val = getOrDefault((ConfigOption) o, o.getDefault()); + + if (!o.getDefault().equals(val)) map.put(o.node(), val); + } + + return map; + } + + protected abstract List> getAllOptions(); + + private void migrate(@Nullable YamlConfiguration currentConfig, YamlConfiguration newConfig) { - var cache = bindableMap.get(option.toString()); - if (cache != null) return (Bindable) cache; + var allNotDefault = this.getAllNotDefault(this.getAllOptions()); + + if (internalResourceName != null) + plugin.saveResource(internalResourceName, true); - if (Map.class.isAssignableFrom(option.getDefault().getClass())) - throw new IllegalArgumentException("Maps cannot being used with Bindable"); + this.onMigrate(currentConfig, newConfig, allNotDefault); - var value = this.getOrDefault(option, option.getDefault()); + allNotDefault.forEach((node, val) -> + { + var matching = this.getAllOptions().stream().filter(option -> option.node().equals(node)) + .findFirst().orElse(null); - var bindable = new Bindable(value); - bindableMap.put(option.toString(), bindable); + if (matching == null) + return; - return bindable; + newConfig.set(node.toString(), val); + }); + } + + protected void onMigrate(@Nullable YamlConfiguration currentConfig, YamlConfiguration newConfig, Map nonDefaultValues) + { } + /** + * @return NULL if not found + */ public T get(ConfigOption option) { return getOrDefault(option, null); } + @NotNull + public List getList(ConfigOption option) + { + var node = option.toString(); + return backendConfiguration.getStringList(node); + } + + /** + * @return NULL if the given node doesn't exist in the configuration + */ @Nullable public Map getMap(ConfigOption option) { @@ -99,6 +154,11 @@ public Map getMap(ConfigOption option) return map; } + public T getOrDefault(ConfigOption option) + { + return getOrDefault(option, option.getDefault()); + } + /** * @apiNote List classes will ALWAYS return ArrayList */ @@ -114,13 +174,11 @@ public T getOrDefault(ConfigOption option, T defaultVal) // 对列表单独处理 if (List.class.isAssignableFrom(optionDefault.getClass())) { - var elementClass = optionDefault.getClass(); - var newResult = backendConfiguration.getList(node, new ArrayList<>()); - newResult.removeIf((listVal) -> { - return !elementClass.isInstance(listVal); - }); - - return (T) newResult; + throw new IllegalArgumentException("Use getList() instead."); + } + else if (Map.class.isAssignableFrom(optionDefault.getClass())) + { + throw new IllegalArgumentException("Use getMap() instead."); } else { diff --git a/src/main/resources/recipes.yml b/src/main/resources/recipes.yml index 60ce7f93..bcb086f3 100644 --- a/src/main/resources/recipes.yml +++ b/src/main/resources/recipes.yml @@ -36,4 +36,6 @@ root: crafting_materials: A: minecraft:feather B: minecraft:redstone + + # Don't touch unless you know what you're doing! version: 1