Class OperatorFileWriter

java.lang.Object
com.ultikits.ultitools.config.document.OperatorFileWriter

@Internal public final class OperatorFileWriter extends Object
The write gate for operator-editable config files: the one way the framework changes such a file automatically (maintainer decisions of 2026-10-04: "operator-written configuration is never overwritten automatically" and "what code may write, by file type").

Why it cannot overwrite operator content. Every call declares the paths it owns (OwnedPaths). The gate reads the file at write time through ConfigDocument.load(Path), applies the caller's edit to that fresh document, renders the whole document, and then verifies the result instead of trusting the renderer:

  1. the rendered text parses back to exactly the edited values, and outside the owned value paths the edited values equal the file's;
  2. owned spans are computed on both sides from SnakeYAML node marks: a value path's span runs from its key's line to the last line of its value, plus the comment lines directly above the key when the key is inserted or removed or its comment is owned; an owned comment's span is those comment lines - or, for a framework comment run (OwnedPaths.Builder.frameworkComment(List, int)), only the run's last lines, so the comment lines above the run are unowned and must come out unchanged (#604); an inserted key whose nearest key in the file is a section the operator left with no value (messages:, nothing after the colon but a comment) also owns that section's one line, and the section's value may change only from nothing to a mapping holding exactly the inserted keys - the line keeps the key as written, the comment text and its line terminator, and only its whitespace may be normalized: a space before the colon, the spaces before the comment, and trailing spaces after the comment or at the end of a line without one (#620; owned-span rule revision 3, maintainer decision 2026-10-06). A section written as an empty value (~, null, {}) is the operator's value and is never owned this way;
  3. the lines outside the owned spans - each compared as text including its line terminator - are the same sequence on both sides (the line diff restricted to unowned lines; stricter than a plain longest-common-subsequence diff, which could match an unowned line against an owned one);
  4. byte-order mark and final line break are unchanged;
  5. no owned span shares a line with a node of a path it does not own (a flow collection holding an owned and an unowned key on one line is refused, not partially rewritten).
A file using YAML anchors, aliases or merge keys is refused before the edit is applied: the renderer cannot write one back without expanding or moving shared content. A runtime failure of the YAML library while the edit is applied, while the document is rendered or while the rendering is parsed back (an EmitterException for a layout it cannot emit, for example) is refused the same way (#624). If any check fails nothing is written, one WARNING names the absolute file, the keys the write would have changed (a key below a secret-shaped key is redacted) and the reason - never a value - and the caller keeps its in-memory value. Layout is never normalized and no operator value, key or comment is repaired to make a write pass.

When the caller passes the fingerprint of the bytes it last read and the file no longer holds them, the outcome is OperatorFileWriter.Outcome.FILE_CHANGED. Immediately before publishing, after rendering and verifying, the gate reads the target again - after the new content is staged, under the lock every load and write holds - and abandons the write when the bytes moved; the residual window between that read and the atomic rename cannot be closed between an editor and the JVM. Publication goes through AtomicConfigWriter; a file the gate read absent is created exclusively (a hard link of the staged file, or a re-check and a move that does not replace where links are refused), so a file that appeared meanwhile is never replaced. Files that are unreadable or unparseable are never written.

Since:
6.3.0