Class ServerPropertiesManager
Why a panel edit cannot overwrite operator content (maintainer decisions of 2026-10-04, "operator-written
configuration is never overwritten" and "what code may write, by file type": a panel edit writes only the named
key; UltiKits/UltiTools-Reborn#607). The file is never re-emitted: an edit replaces the value text of the one
physical line that defines the key and nothing else - the header and operator comments, key order, the key text
and separator as written, line terminators and every other value keep their bytes. The file is decoded the way
the running server reads it (measured from the Paper 1.21.11 jar, Settings#loadFromFile: strict UTF-8,
falling back to ISO-8859-1 when the file is not valid UTF-8), the new value is escaped with the
Properties value grammar in a form that reader returns exactly, and the result is re-encoded in the
charset it was decoded with. Before publishing, every other line is checked byte for byte and a fresh parse must
return the new value and every other key unchanged; a key defined on more than one line, or over continuation
lines, is refused with a reason. Publication is atomic through AtomicConfigWriter, and only while the file
still holds the bytes the edit was made against. A refusal writes nothing and logs one WARNING naming the file,
the key and the reason, never a value.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne physical line that defines a key, located with thePropertiesgrammar.static final classOne typed read ofserver.propertiesfor the protocol 2.1.0 panel, as returned byreadVersioned(int).private static final classThe file's bytes and its text as the server decodes them: strict UTF-8, or ISO-8859-1 when the bytes are not valid UTF-8 (Paper 1.21.11Settings#loadFromFile, measured from the server jar).static final classThe per-key result ofupdateWithBaseVersion(String, Map).static final classWhat happened to one key of a protocol 2.1.0 properties update.static final classOutcome of oneset_allbatch.private static enumWhat actually happened to one key.private static final classAn outcome, its refusal reason (no value), and whether its diagnostic was already logged. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Stringprivate static final Loggerprivate final FileThe keys the frozen protocol 2.1.0 contract lets a panel read, in contract order.The keys a remote update may change: the contract's twelve.private static final ObjectSerializes panel edits of the file within this server run. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionapplySetAll(com.google.gson.JsonObject values) Apply a batch of properties and hand the outcome back.private static List<byte[]> byteLines(byte[] bytes) Splits bytes into lines, each with its terminator (LF, CR or CRLF).private static booleanprivate static StringProperties#saveConvertfor a value: a leading space,\,= : # !and the four control escapes are escaped; any other control character, a lone surrogate, and - in a file the server reads as ISO-8859-1 - every character above~is written as\uXXXX, which the reader decodes in both charsets.The whitelisted keys the file defines, decoded the way the server reads them (see the class description).private static booleanisEditableContent(byte[] bytes, ServerPropertiesManager.PropertiesText text, int maxContentBytes) The contract's RUNTIME_CHECKSfile-contentrule overserver.properties: within the connection's limit, valid UTF-8 (the manager falls back to ISO-8859-1 for the server's own reading, but the panel only ever writes UTF-8) and no C0 control character other than TAB, LF and CR, and no DEL.private static booleanisWhitespace(char c) private static StringlineNumbers(List<ServerPropertiesManager.Definition> definitions) readVersioned(int maxContentBytes) Readsserver.propertiesfor the protocol 2.1.0 panel: the fourteen readable keys that are present and parse to their contract type, the writable key set, and the version a later update must send back as itsbaseVersion.private static ServerPropertiesManager.WriteResultbooleansetProperty(String key, String value) private static int[]splitKey(CharSequence line) Where the key ends and the value starts in one logical line, exactly asProperties#load0decides: the key ends at the first unescaped=,:or whitespace; then whitespace, at most one=or:, and whitespace are skipped.private static StringProperties#loadConvert:\uXXXX,\t \n \r \f, and\xfor any other x.updateWithBaseVersion(String baseVersion, Map<String, String> changes) Applies a protocol 2.1.0 properties update: for each requested key, replaces the value text of the physical line that defines it and nothing else, and only whileserver.propertiesstill hashes tobaseVersion.private static StringversionOf(byte[] bytes) private voidwarnIfIncomplete(ServerPropertiesManager.SetAllResult result, Set<String> reportedFailures) The response above is what the panel reads; this is what a server operator reads.The keys a remote update may change: the contract's twelve.writeProperty(String key, String value) Why this exists next tosetProperty(String, String): the boolean is lossy.
-
Field Details
-
LOGGER
-
WRITE_LOCK
Serializes panel edits of the file within this server run. -
FILE_NAME
- See Also:
-
serverRoot
-
SAFE_KEYS
-
V2_READABLE_KEYS
The keys the frozen protocol 2.1.0 contract lets a panel read, in contract order.SAFE_KEYSstays its own set: this list decides whatreadVersioned(int)reports, not what the v1 write path accepts. -
V2_WRITABLE_KEYS
The keys a remote update may change: the contract's twelve. It is deliberately independent ofSAFE_KEYS, which still contains the v1-onlyspawn-animalsandspawn-npcs.online-modeandwhite-listmust never join it: turning server authentication or the whitelist off from the panel is an elevation of privilege the operator has not granted (maintainer decision, user decision 15), so those two keys are readable and never writable.
-
-
Constructor Details
-
ServerPropertiesManager
-
-
Method Details
-
getSafeProperties
The whitelisted keys the file defines, decoded the way the server reads them (see the class description).- Returns:
- key to value; empty when the file is absent, unreadable or unparseable
-
setProperty
-
writableKeys
The keys a remote update may change: the contract's twelve.- Returns:
- an unmodifiable list of the writable keys, in contract order
-
readVersioned
Readsserver.propertiesfor the protocol 2.1.0 panel: the fourteen readable keys that are present and parse to their contract type, the writable key set, and the version a later update must send back as itsbaseVersion.The version is the SHA-256 of exactly the bytes this call read, so the values and the version always describe one file state; a key whose value does not parse to its type is left out rather than reported with a wrong type. The contract's RUNTIME_CHECKS
file-contentrule applies here too — the file must be valid UTF-8, within the connection's limit and free of C0 control characters other than TAB, LF and CR — because the panel reads this file to write it back.- Parameters:
maxContentBytes- the connection's negotiatedfileMaxBytes- Returns:
- the snapshot, or which refusal prevented one
-
updateWithBaseVersion
public ServerPropertiesManager.PropertiesUpdateResult updateWithBaseVersion(String baseVersion, Map<String, String> changes) Applies a protocol 2.1.0 properties update: for each requested key, replaces the value text of the physical line that defines it and nothing else, and only whileserver.propertiesstill hashes tobaseVersion.Why this path cannot overwrite operator content (configuration-layer rule): the version is computed from the bytes read inside
WRITE_LOCK, the comparison happens there too, and publication goes throughAtomicConfigWriter, which re-reads the file under its own lock and replaces it only while it still holds those bytes. A panel edit therefore lands only when the operator is writing back the very content the panel read; any other state answersServerPropertiesManager.PropertiesUpdateResult.Outcome.FILE_CHANGEDand leaves every byte in place. Keys outside the contract's twelve are refused per key even when a caller reaches this method directly — that refusal is the defence in depth behind the strict payload schema, soonline-modeandwhite-listcannot be changed remotely by any route.- Parameters:
baseVersion- the SHA-256 the panel read, or null to have the update refused as changedchanges- the requested keys and their new values, already in their text form- Returns:
- the per-key outcome, the version now on disk and, for a refusal, its reason
-
isEditableContent
private static boolean isEditableContent(byte[] bytes, ServerPropertiesManager.PropertiesText text, int maxContentBytes) The contract's RUNTIME_CHECKSfile-contentrule overserver.properties: within the connection's limit, valid UTF-8 (the manager falls back to ISO-8859-1 for the server's own reading, but the panel only ever writes UTF-8) and no C0 control character other than TAB, LF and CR, and no DEL. -
versionOf
-
writeProperty
Why this exists next tosetProperty(String, String): the boolean is lossy. Afalsecould mean "the key is not on the whitelist", "the running server server's server.properties does not have the key", "there is no server.properties to write to", or "the write itself failed" — four situations with four different fixes, collapsed into one value. The batch path has to tell the caller which one happened, so the real outcome is produced here andsetPropertykeeps its original contract by narrowing it.The write changes only the line defining
key(see the class description for why nothing else can change); a refusal carries its reason. -
refuse
-
lineNumbers
-
isWhitespace
private static boolean isWhitespace(char c) -
endsWithOddBackslashes
-
splitKey
Where the key ends and the value starts in one logical line, exactly asProperties#load0decides: the key ends at the first unescaped=,:or whitespace; then whitespace, at most one=or:, and whitespace are skipped.- Returns:
[keyLength, valueStart]
-
unescape
Properties#loadConvert:\uXXXX,\t \n \r \f, and\xfor any other x. -
escape
Properties#saveConvertfor a value: a leading space,\,= : # !and the four control escapes are escaped; any other control character, a lone surrogate, and - in a file the server reads as ISO-8859-1 - every character above~is written as\uXXXX, which the reader decodes in both charsets. In a UTF-8 file other characters are written as they are, as the server itself writes them. -
byteLines
Splits bytes into lines, each with its terminator (LF, CR or CRLF). -
applySetAll
Apply a batch of properties and hand the outcome back.The return value is the point:
update_configwithfileName: server_propertiesroutes through here, and itsconfig_update_responseis supposed to distinguish "delivered" from "took effect". Before this returned anything, that path had no way to learn the answer and reported success unconditionally.- Parameters:
values- key → value, an explicit JSON null meaning "leave this key alone"- Returns:
- what happened to each key
-
warnIfIncomplete
private void warnIfIncomplete(ServerPropertiesManager.SetAllResult result, Set<String> reportedFailures) The response above is what the panel reads; this is what a server operator reads. Both are needed: someone changing a setting that silently does not apply otherwise finds nothing on either side.
-