Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 28 additions & 19 deletions src/main/java/dev/twme/debugstickpro/config/ConfigLoader.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
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;
import org.bukkit.configuration.file.YamlConfiguration;

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;
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}

Expand All @@ -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;
}

/**
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/dev/twme/debugstickpro/utils/YamlDefaultMerger.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading