Repository navigation
ConfigDefaultsInserter
ConfigDefaultsInserter adds keys that your bundled default file has but the server's copy lacks, directly in the file's raw text. Each new key arrives with the comments above it, and every byte the admin already had stays exactly as it was. A YamlConfiguration load/save round-trip would instead rewrite the whole file (indentation, quoting, blank lines).
- A new release adds settings, and admins should find them, documented, in their existing
config.yml. -
copyDefaults(true)+saveConfig()would reformat the admin's whole file. - You want to report exactly which keys were added.
- Inserts each missing key after its previous sibling from the default file, otherwise before its next sibling, otherwise under its parent key (or at the end of the file for top-level keys).
- A missing section is inserted as a whole, with its nested comments and lists.
- Every YAML key counts, also with capitals, umlauts, dots or spaces, or in quotes. A default without a value (
key:orkey: ~) is added as written, and a section the admin wrote assection:without a value takes the new keys below it. - Indentation follows the admin's file (e.g. 2 spaces even if your default file uses 4); the text of a block scalar (
|,>) moves with its key and keeps its own indentation. - Works on the raw bytes: the rest of the file keeps its encoding, BOM and line endings, and the new lines use the file's line ending (e.g. CRLF). A byte order mark stays the file's first bytes; a key added at the top goes in after it. The new content is written to a hidden temporary file next to it first and then replaces the file, so a crash while writing normally doesn't leave it half written (see Limitations).
- All missing keys are added in one pass and checked once, so even thousands of new keys take well under a second.
- Safety net: the result is parsed again before writing; if it isn't valid YAML, an existing value reads back differently or a new key is missing, the file is left unchanged.
- Only adds: changed default values never reach existing files, keys you dropped from the defaults stay in the admin's file, and new entries of an existing list aren't added. A key the admin deleted comes back on every call, except inside open sections (see Open Sections below).
-
Plain-value parents: keys whose parent is a plain value or a list in the admin's file (e.g.
section: off) are skipped without a log line. Inside an open section they aren't recorded as offered, so they are added once the parent is a section. -
Inline and alias sections: sections the admin wrote inline (
update_check: {enabled: false}, also spread over several lines) or as an alias (section: *base) can't take new lines, so new keys inside them are skipped. OneWARNINGper call and kind names these keys and sections and asks for one key per line; inside an open section they aren't recorded as offered, so they are added once the section is written that way. -
Lists of sections: keys inside list items (
- name: ...) are never added. - Comments: only the comment lines directly above a key come along; a blank line before them is kept as a separator. Comments further up or at the end of a section stay behind.
- All or nothing: if the check after inserting fails, nothing is added in that call, and the same warning comes back on every call.
-
Encodings: the added text is encoded like the file: UTF-8 if the file is valid UTF-8, otherwise Windows-1252; other code pages are treated as Windows-1252. A text value with characters that encoding can't store, also one spread over several lines, is written as one double-quoted line with
\uXXXXescapes, which reads back unchanged; in comments and list items such characters become?, with aWARNING. The line ending is taken from the file's first line. - Missing file: returns an empty list without creating the file or logging anything.
-
Writing and threads: the file is replaced through a temporary file next to it, which is flushed to disk first; if the replace fails (e.g. a file lock on Windows), it's written in place, with a
WARNING. A symlink becomes a regular file. Nothing is locked, so don't call it on the same file from several threads, and.seen-defaults.ymlisn't locked either.
- Rename legacy keys first (
ConfigKeyMigrator), thenreloadConfig(). - Call
ConfigDefaultsInserter.insertMissingKeys(...)with the bundled resource. - If it returned keys, call
reloadConfig()again and report them. - If you use
ConfigWatcher, callrefreshBaseline()afterwards so the write isn't treated as an external edit.
| Method | Purpose |
|---|---|
insertMissingKeys(File file, InputStream defaults, Logger logger, String fileLabel) |
Inserts every key of defaults that file doesn't set yet. Returns the added dotted paths in document order; empty if nothing was added. |
insertMissingKeys(File file, InputStream defaults, Logger logger, String fileLabel, Collection<String> openSections) |
Same, but inside openSections each default key is offered only once, so entries the admin deleted stay deleted (see below). |
-
defaultsis read as UTF-8; the caller closes it. -
loggermay benull. Problems never throw: read/write failures, invalid YAML (in the file or in the bundled defaults, each named in the warning), a failed safety check or any unexpected error log aWARNINGand return an empty list, leaving the file untouched. Nothing is logged atINFO, so the caller decides how to report the added keys. -
fileLabelis only used in log messages (e.g."config.yml"). - It does not reload anything; call
reloadConfig()yourself when keys were added.
Some sections are lists the admin maintains freely, e.g. allowed materials under soils: (DIRT: true, SAND: false, ...). With the plain overload, a default entry the admin deleted there comes back on every reload. Pass such sections as openSections (dotted paths) and:
- a default key inside an existing open section is added only if it was never offered for this file before;
- every default key of the open sections is recorded as offered in
.seen-defaults.ymlnext to the file (one list per file name; ConfigKeyMigrator keeps its once-only warnings there too); - the first time a file has no record (new install, clean upgrade via
LegacyDataUpgrade, first use of the overload), all current defaults count as offered and nothing inside the open sections is added (so an admin who copies an older file back by hand doesn't get its missing defaults added in that one run); - a whole missing open section is still inserted with all its entries.
So a new default in a later release (e.g. a new sapling) is added once; if the admin deletes it afterwards, it stays deleted. Without openSections no record file is written.
import net.kroet.turtlelib.helper.ConfigDefaultsInserter;
private void addNewDefaults() {
File file = new File(getDataFolder(), "config.yml");
try (InputStream in = getResource("config.yml")) {
List<String> added = ConfigDefaultsInserter.insertMissingKeys(file, in, getLogger(), "config.yml");
if (!added.isEmpty()) {
reloadConfig();
getLogger().info("Added new settings to config.yml: " + String.join(", ", added));
}
} catch (IOException e) {
getLogger().warning("Could not read bundled config.yml: " + e.getMessage());
}
}- A key counts as missing when the file neither sets it nor has a line for it, so a key written as
key:with no value is never added twice. - It is safe to call on every startup and reload; a complete file is never rewritten.
- Run
ConfigKeyMigratorfirst, or new snake_case keys would be added next to not-yet-renamed legacy keys. -
ConfigWatcher#refreshBaseline()keeps the watcher from reacting to this write.