diff --git a/README.md b/README.md index 21985c7..d854de8 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,10 @@ ### Configuration File: [config.yml](https://github.com/TWME-TW/DebugStickPro/blob/main/src/main/resources/config.yml) +Missing settings in an existing `config.yml` and missing entries in language files +are filled automatically from the bundled defaults when the plugin loads. Existing +values and translations are preserved. + `BlockDataFilter.AllowUnsafeBisectedData` is disabled by default to prevent invalid double-block structures and duplicate drops. Enable it only when multi-block `Bisected` properties are intentionally needed. diff --git a/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java b/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java index d62b4ba..6b92257 100644 --- a/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java +++ b/src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java @@ -1,9 +1,10 @@ package dev.twme.debugstickpro.config; import java.io.File; -import java.text.SimpleDateFormat; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Date; import java.util.HashSet; import org.bukkit.Material; @@ -11,6 +12,7 @@ import dev.twme.debugstickpro.DebugStickPro; import dev.twme.debugstickpro.utils.Log; +import dev.twme.debugstickpro.utils.YamlDefaultMerger; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -39,32 +41,39 @@ public void load() { Log.warning(e.getMessage()); } + YamlConfiguration defaults = loadDefaults(); + boolean changed = defaults != null && YamlDefaultMerger.addMissingValues(config, defaults); + if (config.getInt("ConfigVersion") < DebugStickPro.CONFIG_VERSION) { + config.set("ConfigVersion", DebugStickPro.CONFIG_VERSION); + changed = true; + } else if (config.getInt("ConfigVersion") > DebugStickPro.CONFIG_VERSION) { + Log.warning("Config file is newer than this version of the plugin."); + } ConfigFile.ConfigVersion = config.getInt("ConfigVersion"); - if (!checkConfigVersion()) { - load(); - return; + if (changed) { + Log.info("Updated config.yml with missing default values."); + save(); } loadValues(); } - private boolean checkConfigVersion() { - if (ConfigFile.ConfigVersion != DebugStickPro.CONFIG_VERSION) { - Log.warning("Config file version is not compatible with this version of the plugin."); - Date date = new Date(); - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - String strDate = formatter.format(date); - String backupFileName = file.getAbsolutePath().replace("config", "config-" + strDate); - File newFile = new File(backupFileName); - if (file.renameTo(newFile)) { - Log.warning("Old config file has been backed up to " + newFile.getName()); - } else { - Log.warning("Failed to backup old config file"); + private YamlConfiguration loadDefaults() { + try (InputStream stream = DebugStickPro.getInstance().getResource("config.yml")) { + if (stream == null) { + Log.warning("Bundled config.yml not found."); + return null; } - return false; + + YamlConfiguration defaults = new YamlConfiguration(); + defaults.options().parseComments(true); + defaults.load(new InputStreamReader(stream, StandardCharsets.UTF_8)); + return defaults; + } catch (Exception e) { + Log.warning("Failed to load bundled config.yml: " + e.getMessage()); + return null; } - return true; } private void loadValues() { diff --git a/src/main/java/dev/twme/debugstickpro/localization/LangFileReader.java b/src/main/java/dev/twme/debugstickpro/localization/LangFileReader.java index ca44e65..0a7b52e 100644 --- a/src/main/java/dev/twme/debugstickpro/localization/LangFileReader.java +++ b/src/main/java/dev/twme/debugstickpro/localization/LangFileReader.java @@ -2,11 +2,13 @@ import dev.twme.debugstickpro.DebugStickPro; import dev.twme.debugstickpro.utils.Log; +import dev.twme.debugstickpro.utils.YamlDefaultMerger; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; @@ -63,9 +65,40 @@ public void load() throws IllegalArgumentException { Log.warning(e.getMessage()); } + YamlConfiguration defaults = loadDefaults(); + boolean changed = defaults != null && YamlDefaultMerger.addMissingValues(this.langFile, defaults); + if (this.langFile.getInt(Lang.LangFileVersion) < DebugStickPro.LANG_VERSION) { + this.langFile.set(Lang.LangFileVersion, DebugStickPro.LANG_VERSION); + changed = true; + } else if (this.langFile.getInt(Lang.LangFileVersion) > DebugStickPro.LANG_VERSION) { + Log.warning(locale + ".yml is newer than this version of the plugin."); + } langFileVersion = this.langFile.getInt(Lang.LangFileVersion); - if (!checkLangFileVersion()) { - return; + + if (changed) { + Log.info("Updated " + locale + ".yml with missing default values."); + save(); + } + } + + private YamlConfiguration loadDefaults() { + String resourcePath = "lang/" + locale + ".yml"; + InputStream stream = DebugStickPro.getInstance().getResource(resourcePath); + if (stream == null && !"en_US".equals(locale)) { + stream = DebugStickPro.getInstance().getResource("lang/en_US.yml"); + } + if (stream == null) { + return null; + } + + try (InputStream defaultsStream = stream) { + YamlConfiguration defaults = new YamlConfiguration(); + defaults.options().parseComments(true); + defaults.load(new InputStreamReader(defaultsStream, StandardCharsets.UTF_8)); + return defaults; + } catch (Exception e) { + Log.warning("Failed to load bundled defaults for " + locale + ".yml: " + e.getMessage()); + return null; } } @@ -75,22 +108,7 @@ public void load() throws IllegalArgumentException { * @return true if the version is compatible */ public boolean checkLangFileVersion() { - if (langFileVersion != DebugStickPro.LANG_VERSION) { - Log.warning("Lang file version is not compatible with this version of the plugin."); - Date date = new Date(); - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - String strDate = formatter.format(date); - String backupFileName = file.getAbsolutePath().replace(locale, locale + strDate); - - File newFile = new File(backupFileName); - if (file.renameTo(newFile)) { - Log.warning("Old lang file has been backed up to " + newFile.getName()); - } else { - Log.warning("Failed to backed up old lang file to " + newFile.getName()); - } - return false; - } - return true; + return langFileVersion == DebugStickPro.LANG_VERSION; } /** diff --git a/src/main/java/dev/twme/debugstickpro/utils/YamlDefaultMerger.java b/src/main/java/dev/twme/debugstickpro/utils/YamlDefaultMerger.java new file mode 100644 index 0000000..c7f154b --- /dev/null +++ b/src/main/java/dev/twme/debugstickpro/utils/YamlDefaultMerger.java @@ -0,0 +1,42 @@ +package dev.twme.debugstickpro.utils; + +import org.bukkit.configuration.ConfigurationSection; + +/** + * Adds values that are present in a bundled YAML resource but missing from a + * user-managed YAML file without replacing any existing values. + */ +public final class YamlDefaultMerger { + + private YamlDefaultMerger() { + } + + /** + * Merge missing values from {@code defaults} into {@code target}. + * + * @return true when the target was changed + */ + public static boolean addMissingValues(ConfigurationSection target, ConfigurationSection defaults) { + boolean changed = false; + + for (String key : defaults.getKeys(false)) { + if (target.isSet(key)) { + if (target.isConfigurationSection(key) && defaults.isConfigurationSection(key)) { + changed |= addMissingValues(target.getConfigurationSection(key), defaults.getConfigurationSection(key)); + } + continue; + } + + ConfigurationSection defaultSection = defaults.getConfigurationSection(key); + if (defaultSection != null) { + ConfigurationSection targetSection = target.createSection(key); + changed |= addMissingValues(targetSection, defaultSection); + } else { + target.set(key, defaults.get(key)); + } + changed = true; + } + + return changed; + } +} diff --git a/src/test/java/dev/twme/debugstickpro/utils/YamlDefaultMergerTest.java b/src/test/java/dev/twme/debugstickpro/utils/YamlDefaultMergerTest.java new file mode 100644 index 0000000..6f3ab7a --- /dev/null +++ b/src/test/java/dev/twme/debugstickpro/utils/YamlDefaultMergerTest.java @@ -0,0 +1,65 @@ +package dev.twme.debugstickpro.utils; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class YamlDefaultMergerTest { + + @Test + public void addsMissingNestedValuesWithoutOverwritingExistingValues() throws Exception { + YamlConfiguration defaults = yaml(""" + ConfigVersion: 8 + Feature: + Enabled: true + NewValue: default + Limits: + Maximum: 10 + Messages: + - one + - two + """); + YamlConfiguration target = yaml(""" + ConfigVersion: 7 + Feature: + Enabled: false + Limits: + Maximum: 0 + Messages: + - custom + """); + + assertTrue(YamlDefaultMerger.addMissingValues(target, defaults)); + assertEquals(7, target.getInt("ConfigVersion")); + assertFalse(target.getBoolean("Feature.Enabled")); + assertEquals("default", target.getString("Feature.NewValue")); + assertEquals(0, target.getInt("Feature.Limits.Maximum")); + assertEquals(List.of("custom"), target.getStringList("Messages")); + } + + @Test + public void doesNotChangeCompleteConfiguration() throws Exception { + YamlConfiguration defaults = yaml(""" + Feature: + Enabled: true + """); + YamlConfiguration target = yaml(""" + Feature: + Enabled: false + """); + + assertFalse(YamlDefaultMerger.addMissingValues(target, defaults)); + assertFalse(target.getBoolean("Feature.Enabled")); + } + + private static YamlConfiguration yaml(String value) throws Exception { + YamlConfiguration configuration = new YamlConfiguration(); + configuration.loadFromString(value); + return configuration; + } +}