Class AbstractConfigEntity
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOne setting, or one entry of a map setting, the module changed since the last load or save: where it is in the file, what the module holds there now, and what the framework last read or wrote there.private final classComplete entity state before a panel candidate or a reload attempt, including raw and effective acknowledgments.final classNarrow manager coordination for one staged entity; not a module transaction API.private static final classA batch initialization write: what to insert, and the bytes it was read from (#602).private static final classWhereinsertKeysIn(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)puts a setting, and the dotted section it belongs to (named in a refusal).private static final classA detached raw entry acknowledgment; absent and explicit null are different states. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Objectprivate final Map<Field, AbstractConfigEntity.RawEntry> The value ranges a config binding (#531) imposes on this entity's keys, by@ConfigEntrypath, then by rule text.private final List<ConfigChangeListener> private booleanWhether the last load's comment-only write was refused or failed (it has logged its one warning).The composite values a panel edit is about to rewrite whole, as last read, per setting and unit; set byapplyAndValidateand consumed bypanelChangesunder the entity monitor (route change, R3-01).The whole composite values a panel edit writes, per setting and unit: the value as last read with only the edited fields changed, so every untouched field keeps the bytes the operator wrote (#609, 17-65 round 4 R4-I2); set byapplyAndValidateand consumed bypanelChangesunder the entity monitor, likecompositeReads.private final StringThe declared default's own size per field, measured on the Java value when the defaults are captured (0 for an empty or null container, -1 for a value@Sizedoes not count): a converter or a legacy parser may give the default another plain shape, such as a set written as onejoined:entry (PR #632 local Codex run 1).private booleanprivate booleanprivate ConfigDocumentprivate booleanWhether a write to this file failed part-way and the file could not be put back from its framework backup (#622): it may hold part of that write, so the entity treats it as changed since it was read and writes nothing to it until a load reads it again.private booleanprivate booleanprivate static final Loggerprivate static final StringThe refusal reason whilefileNotRestoredholds (#622): a phrase without any value.Representative classes of each kind a constraint checks, for "can a value of the declared type be one of them".private Stringprivate UltiToolsPluginprivate booleanSet only while a reload validates the values it read, for the refusal's wording (#595). -
Constructor Summary
ConstructorsConstructorDescriptionAbstractConfigEntity(String configFilePath) Constructor for AbstractConfigEntity. -
Method Summary
Modifier and TypeMethodDescriptionprivate voidacknowledgeRaw(ConfigDocument source, List<Field> fields) private voidacknowledgeWritten(String readFingerprint, String fingerprint, ConfigDocument onDisk, List<AbstractConfigEntity.ModuleChange> applied) Advances the effective baseline and the last-read entry of exactly the changes the file now holds.final voidaddBindingRange(String key, String rule, Predicate<Long> isValid) Records thatkeyis bound by a config-bound@Scheduledor@CmdCD(#531), so that a panel write setting it to a valueisValidrejects is refused, naming the key, the value andrule.voidaddChangeListener(ConfigChangeListener listener) Adds a configuration change listener.private booleanaddEntryComment(ConfigDocument target, Field field) Writes the framework's comment abovefield's key intarget: for a token comment, only the run of comment linesframeworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)identifies as the framework's is replaced, and a comment that is the operator's is left as it is; a literal comment is written only for a key just inserted, which has no comment yet.private static voidaddLeaves(com.google.gson.JsonObject result, String prefix, Map<?, ?> values, com.google.gson.Gson gson) private static voidapply(ConfigDocument target, List<AbstractConfigEntity.ModuleChange> changes) Sets the module's value, or removes the map entry the module removed, at each change's path.applyAndValidate(com.google.gson.JsonObject jsonObject, List<Field> touchedFieldsOut, List<Object> previousValuesOut) Applies everyjsonObjectfield this class declares via@ConfigEntryto this instance, then validates the resulting state viavalidateFields().private voidbindDeclaredDefault(Field field, boolean deleted) Bindsfield's initially declared default because a reload found its key missing from the file and the module had not changed the setting since the last load or save (#596 item 2): deleting a key resets that setting, as an unusable value already does (#523).private static booleanWhether a field declared asrawcan hold a value of one ofkinds:rawis one of them, a subtype, or a supertype such asObject,Serializable,Comparable,CharSequenceorNumber(final review F1 of plan 17-76).private voidprivate static voidchangedLeaves(Object base, Object current, Type declared, List<String> prefix, List<List<String>> out) Collects the paths, relative to a setting, at whichcurrentdiffers frombase.private voidRefuses, at load and before the file is read, a constraint declaration the framework cannot hold (maintainer decision of 2026-10-06, option A; #630, #631).voidRemoves all configuration change listeners.Every@ConfigEntryfield this class declares or inherits, inReflectionUtil.getFields(Class)order.private static List<Annotation> constraintsOn(AnnotatedElement element) The four constraint annotationselementcarries, in a fixed order.private AbstractConfigEntityConstructs a fresh instance of this config class through the(String)constructor, or failing that the no-arg constructor - the two idiomsensureConstructable()proves - for the existing validation constructability precondition only.private StringcontainerKind(Field field) The value kind@NotEmptytreats as a container onfield:"list"(a list or an array),"set","map"or"collection"by the field's declared type, ornullfor any other type (text keeps its refusal).private booleancontainsSecret(Object value) private static StringconversionReason(ConversionException failure) currentPlain(List<Field> fields) currentPlain(List<Field> fields, boolean writing) private FielddeclaredEntry(String entryPath) The field declared atentryPath(ConfigEntry.path(), or the field name for an empty path).private TypedeclaredType(Field field) private StringdefaultCannotStandIn(Field field, String kind) Why the declared default of a@NotEmptylist, set, map or array cannot stand in for an empty value, ornullwhen it can (#630, maintainer decision of 2026-10-06): it is empty itself, or it violates the field's other constraint -@Size, the only other one such a field can carry (the declaration check refuses@Rangeand@Patternon it).private StringdescribeFailure(Field field, List<String> path) How a refusal names the path of a conversion failure offield: throughdescribeKey(java.lang.reflect.Field, java.util.List<java.lang.String>)with the keys below the setting's declared keys; a path that does not start with them is displayed whole by the same rule.private StringdescribeKey(Field field, List<String> leaf) The key a warning names for a change: the setting's path, then the map keys, any key below a secret-shaped one redacted.describeReadings(Field owner, List<List<String>> readings) Every reading of an ambiguous panel path, as the real keys ofowner's map displayed bydisplayKeys(boolean, java.util.List<java.lang.String>).private StringdescribeUndeclared(String path) How a refusal names a panel path no declared setting owns: the rule ofdisplayKeys(boolean, java.util.List<java.lang.String>)over its dotted segments, so everything below the first secret-shaped segment is redacted (#673).final voidClears an uncommitted initialization belonging to a refused manager batch.displayKeys(boolean secretAbove, List<String> keys) The one display rule for map keys in every configuration diagnostic bound for the server log or the panel (#673, case 54): once the setting or a key above is secret-shaped, every deeper key shows as<redacted>; the secret-shaped key itself is shown, so the operator still sees which setting to fix.dottedKeys(String path) The dotted segments of a panel path whose real keys are not known, for display only: a whole key holding a dot is split, which can only redact more, never less (no secret-shaped word contains a dot).private voidConstructs and discards an instance of this config class through the same two-step fallbackConfigManager.registerAlluses at registration time - a(String)constructor first, then an accessible no-arg constructor.private static StringentityFingerprint(String sha256Hex) The fingerprint formfingerprintOf(java.io.File)uses, for a storage-layer SHA-256 in lower-case hex (orOperatorFileWriter.ABSENT): what the entity records as the bytes it last bound or wrote (#602).private static StringexpectedBase(ConfigLoadResult loaded) private static StringfailedLoadCause(ConfigLoadResult loaded) The safe cause of a load that could not read or parse the file, ornullwhen it did.private Stringprivate static StringfingerprintOf(File file) Fingerprints a configuration file by the SHA-256 digest of its bytes (#510).final voidFlushes only a validated manager batch's initialization write, through the config write gate and only while the file still holds the bytesinitForBatchread (#602); seewriteInitialization(java.util.Map<java.lang.reflect.Field, java.lang.Object>, java.lang.String, com.ultikits.ultitools.config.document.ConfigDocument).private intframeworkCommentRun(ConfigDocument target, Field field) How many of the last comment lines abovefield's key intargetthe framework wrote (#604, maintainer decision 2026-10-04: "only the framework's own comments are rewritten").frameworkRenderings(Field field) Every rendering of a token comment the framework may have written abovefield's key: the token's text in each catalogue the module's jar ships (read without any language-file side effect), the texts earlier module versions shipped for the entry (ConfigEntry.previousComments()), the token's text in each of the operator's custom language files in the module'slang/folder (UltiToolsPlugin.customCatalogueTexts(java.lang.String), #615 item 1), the text the module resolves now, and the bare token - each in the byte formConfigDocument.setFrameworkComment(java.util.List<java.lang.String>, java.util.List<java.lang.String>)writes it, asConfigDocument.blockCommentAsWritten(List)reports it.intGets the number of registered change listeners.com.google.gson.JsonObjectGets the comments of the configuration entity.private intgetValueLength(Object value) What@Sizecounts: a text's length (acharis one character), a collection's size, a map's entries and an array's length (maps and arrays since 6.3.0, #631);-1for anything else.private booleanheldTwice(ConfigDocument doc, Field field) Whetherdocholdsfield's setting in more than one form (#612).private StringheldTwiceReason(Field field) The refusal text for a setting held in more than one form: the setting path, never a value (#612).final voidinit(UltiToolsPlugin ultiToolsPlugin) Initializes the configuration entity.final voidinitForBatch(UltiToolsPlugin plugin) Framework manager bridge; binds without flushing initialization writes.private voidinitialize(UltiToolsPlugin ultiToolsPlugin, boolean deferred) insertKeysIn(ConfigDocument doc, Field field) Where an insert putsfield's setting, whichdocdoes not hold (#614, maintainer decision 2026-10-06): below the longest prefix of its path the file holds as a section (ConfigDocument.heldSections(java.lang.String)), the rest of the path as nested keys - so a section the operator wrote in flat dotted form (a.b:) gets the setting in place, never a second, nested copy of the section.private static booleanisContainerType(Class<?> raw) private static booleanisEmptyContainer(Object value) Whether a container value holds nothing:null, an empty collection, map or array.final booleanWhether the file on disk differs from the file as it was at the last snapshot point (#510) - in practice, whether someone edited, replaced or removed it while the server was running.final booleanWhether the lastinit(com.ultikits.ultitools.abstracts.UltiToolsPlugin)failed before its field validation completed -- for example because writing back a missing key threw anIOException, whichConfigManagerlogs and continues past.final booleanWhether the last attempt to read this configuration's file failed to parse (#510) - in practice, whether the file on disk holds invalid YAML.final booleanWhether serialized fields differ from their last bound/persisted effective values - module changes not yet saved.private booleanisNotEmptyViolation(Field field, Object value) private static booleanisNumberType(Class<?> raw) private booleanisPatternViolation(Field field, Object value) final booleanisPresentInFile(String path) Reports presence in the last successfully loaded document, including explicit nulls and undeclared keys.private booleanisRangeViolation(Field field, Object value) private booleanisSecretShapedFieldName(String fieldName) Whether a field name looks like it stores a secret.private static booleanisSerializedMap(Object value) Whethervalueis a serialized Bukkit value: a map carrying the==type key.private booleanisSizeViolation(Field field, Object value) private static booleanisTextType(Class<?> raw) private booleanisTokenComment(Field field) private static ObjectjsonToPlain(com.google.gson.JsonElement element) keysIn(ConfigDocument doc, Field field) Wheredocholdsfield's setting: its one reading (ConfigDocument.readings(String)) - the nested keys, a flat dotted key such asfeatures.chat, or a mix such asa.b: {c: 1}- or, when it holds none, where an insert puts it (insertKeysIn(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field); the nested keys when that cannot be decided, for a caller that only asks whether the setting is held).The keys oflevel, when it is a map, that read back throughkeyTypetotyped, in map order.private voidload(boolean initialize) private static booleanmapContains(Object tree, List<String> path) private static Objectprivate static TypemapValueType(Type declared) The declared value type of a map type (Objectwhen it is raw).private static voidprivate ObjectmergeReload(Object base, Object mine, Object theirs, Type declared, Field field, List<String> leaf, List<String> conflicts) The #511 three-way reload merge of one setting.mirroredPath(List<String> reading, int common, List<String> parts) The missing setting's path in a sibling's form: the sibling's reading cut at thecommondotted segments both paths share, when that cut falls inside one of its keys ([a, b.c]cut aftera.bgives[a, b.d]fora.b.d);nullwhen the cut falls between keys (that prefix is a held section).private voidnoteFailedWrite(IOException failure) Records that a write to this file could not be put back after it failed part-way (#622), so the file is treated as changed since it was read until a load reads it again; any other failure left the file holding its previous bytes.protected voidNotifies all registered listeners about the configuration change.private static StringA value for a warning on one log line, or<redacted>.private static booleanorderedEquals(Object left, Object right) private voidownFrameworkComments(OwnedPaths.Builder owned, ConfigDocument read) The token comments ofreadthe framework may rewrite, each owning only the run of linesframeworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)identified as the framework's in the file as read (#604): the config write gate then refuses any write that changes a comment line above that run or of a comment that is the operator's.private List<AbstractConfigEntity.ModuleChange> A panel edit's changes: each touched setting as a whole, or - for a map setting edited leaf by leaf - each touched leaf, at the value the edit applied.private StringpanelComment(Field field) private static booleanparentInMemory(Object tree, List<String> path) Whether the map holding the last key ofpathexists intree(the module's value in memory).private static ObjectpatchedMap(Object tree, List<String> path, Object value) private AbstractConfigEntity.Placementplacement(ConfigDocument doc, Field field) private ObjectplainValue(Field field) private ObjectplainValue(Field field, boolean writing) The plain value offieldas the module holds it, spelled like the file this entity last read or wrote: the raw acknowledgement is passed as the file value, so a typed map key the file spells'01',1.0or in upper case keeps that text (#673, case 52).private ObjectplainValue(Field field, boolean writing, Object fileValue) preparePanelWrite(com.google.gson.JsonObject properties) Prepares one panel entity without acknowledging or replacing its file: the touched settings or leaves are verified and staged through the configuration write gate (seeupdateProperties(com.google.gson.JsonObject)); the commit re-checks the file's bytes immediately before its move.private ConfigEntryPresenceExceptionThe refusal of a map-entry write whose presence condition did not hold: logged at FINE only, the caller reports it (#623).private booleanprotectFailedLoad(ConfigLoadResult loaded) private Objecttextread as a map key ofkeyTypeon the loader's read path, ornullwhen it does not convert.final voidRewrites this configuration's framework comment lines in the owning module's language as it is now (#594).private ConfigWriteRefusedExceptionprivate voidrefuseSeveralSpellings(ConfigDocument read, Field field) An operator write of a setting whose typed map keysreadholds in several spellings is refused.private ConverterRegistryregistry()private static booleanregistryConverts(Field field) Whether the converter registry convertsfield, astoPlainResultdecides: an explicit non-default legacyConfigEntry.parser()keeps conversion ownership, so no default typed-key spelling rule applies to it.voidreload()Reloads the configuration from file and notifies all listeners.private ConfigurationExceptionreloadRefusal(ConfigLoadResult loaded, String cause) #589: a reload that cannot read or parse the file refuses with the file and the safe cause.voidremoveChangeListener(ConfigChangeListener listener) Removes a configuration change listener.renderedComment(String text) The comment linestextbecomes when the framework writes it, in their written byte form.private static voidreplaceMapLeaf(Object tree, List<String> path, Object value) private booleanreportedSeveralSpellings(ConfigDocument read, Field field, List<List<String>> leaves) The save rule for a setting whose typed map keysreadholds in several spellings: none of its changedleavesis written, and one warning names them with the reason, never a value.private voidrequireOperatorWriteThread(String operation) private StringresolvedComment(Field field) private voidrestoreFields(List<Field> fields, List<Object> values) private OperatorFileWriter.ResultrewriteTokenComments(ConfigLoadResult loaded) Rewrites the framework's token comments in the current language through the config write gate (OperatorFileWriter), at start-up and on reload.private static StringsafeParserLocation(String message) voidsave()Writes the settings this module changed in memory since the last load or save - and nothing else - through the config write gate.private voidThe save rule ofsave(): collects the module's changes against the effective baseline; keeps those whose last-read file value is the value the module started from; checks each on one read of the file, which must still hold there what the framework last read (a file already holding the module's value needs nothing); writes the rest through the config write gate against exactly that read; advances the baseline and the last-read entry only for what the file now holds; and names every change that was not written in one warning.final voidsaveOperatorChange(String... entryPaths) Writes exactly the named settings, as the module holds them now, because the operator explicitly asked for that change - a command such as/setspawn, which names the sixspawn.location.*entries.final voidsaveOperatorMapEntry(EntryPresence required, String entryPath, String... mapKeys) Writes exactly one entry of a map setting, assaveOperatorMapEntry(String, String...)does, but only when the entry's presence in the file is what the operator's command assumes:EntryPresence.MUST_BE_ABSENTfor a command that creates the entry (/autoreply add <name>),EntryPresence.MUST_BE_PRESENTfor one that changes an existing entry (/autoreply setkeyword <name>).final voidsaveOperatorMapEntry(String entryPath, String... mapKeys) Writes exactly one entry of a map setting, as the module holds it now, because the operator explicitly asked for that change - a command such as/autoreply add, which names one rule ofautoreply.rules.private static OwnedPathssaveOwnership(List<AbstractConfigEntity.ModuleChange> changes, ConfigDocument read) What a save owns at the config write gate: each change's whole key, except that a map which is empty in the file as read ({}), or which the changes empty, is owned as a whole - its key line must change with its first or last entry, and an empty map holds nothing of the operator's that could be lost.private booleansecretPath(Field field, List<String> leaf) Whether a value offieldatleafis secret: the setting or a real key down to it is secret-shaped.private booleansecretSetting(Field field) Whether a setting is secret-shaped: its field name or a segment of its declared path.sectionHoldingASibling(ConfigDocument doc, Field field, List<List<String>> sections) Of several forms of one section, the one form holding another declared setting of it;nullfor none or several.private static booleanseveralSpellings(ConfigDocument read, Field field) Every typed map key thatreadholds forfieldin several spellings, relative to the setting and ending in its first spelling in file order (#673, case 52).private StringseveralSpellingsReason(Field field, List<List<String>> leaves) The refusal text for typed map keys the file writes in several spellings, such as1:and'01':(#673, case 52): the setting and each key (its first spelling) throughdescribeKey(java.lang.reflect.Field, java.util.List<java.lang.String>), never a value or the other spellings.private StringspelledKey(Field field, List<String> above, String key, Type keyType, Object moduleLevel, Object fileLevel) One key ofspelledLeaf(java.lang.reflect.Field, java.util.List<java.lang.String>, java.lang.Object):aboveholds the keys already resolved, outermost first.spelledLeaf(Field field, List<String> leaf, Object mine) The keys of an operator's map-entry write, spelled as the module's map and the file spell them (#673, case 52).splitComment(String comment) Splits a@ConfigEntry.comment()value into oneListelement per line, in declaration order, ready forConfigurationSection's comment-writing API.private static intsplitDepth(Type declared, List<String> keys) How many ofkeys, from the start, address entries of declared maps: the keys after them would reach inside a value that is one value (17-65 review round 2 R2-01).private static booleansplitsByEntry(Type declared, Object base, Object current) Whether a value is compared entry by entry: it is declared as aMapand both plain forms are maps that are not a serialized object (Bukkit's==type key).private OperatorFileWriter.StagedPrepares an operator's explicit changes for the configuration write gate and stages the verified file (seewriteOperatorChanges(java.util.List<com.ultikits.ultitools.abstracts.AbstractConfigEntity.ModuleChange>)); a write the gate settles as refused, or a file that cannot be read or parsed, throwsConfigWriteRefusedExceptionhere, before anything is staged.private booleanstartedFromFile(Field field, List<String> leaf, Object base) Whether the value the framework last read forfieldatleafis the value the module started from there: it is present, and the setting's last loaded or saved value with that entry taken from the file converts, without a conversion failure, back to exactly that value.com.google.gson.JsonObjectConverts the configuration entity to a JSON object.private booleantokenCommentsDiffer(ConfigDocument target) Whether a framework token comment intargetdiffers from what the current language renders, without changingtarget: the framework's own run (frameworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)) compared, in its written byte form, with the current rendering; a comment that is the operator's never differs.private static Stringprivate StringunplacedReason(ConfigDocument doc, Field field) Whyfield's setting cannot be inserted (insertKeysIn(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)returnednull): names, never values.private StringunreadableKind(Field field, Object value) A violation for a bound value of a kind the field's annotation cannot read - possible only when the setting is declared as a supertype such asObject(final review of plan 17-76: a declared constraint works or is refused, never passes silently).Names unsaved declared entries without exposing their values, for superseded-copy reporting.private static StringunsupportedBy(Annotation constraint, Class<?> raw) Whyconstraintcannot be checked on a setting declared asraw, ornullwhen it can: the value kinds the validation step reads (isRangeViolation(java.lang.reflect.Field, java.lang.Object),isPatternViolation(java.lang.reflect.Field, java.lang.Object),getValueLength(java.lang.Object),isNotEmptyViolation(java.lang.reflect.Field, java.lang.Object)).voidupdateProperties(com.google.gson.JsonObject jsonObject) Updates the properties of the configuration entity.private booleanupdateTokenComments(ConfigDocument target) private voiduseDeclaredDefaultIfEmpty(Field field, Object raw) A@NotEmptylist, set or map that a load or reload bound empty - the file holds it empty ornull, or every entry failed to bind - runs on the field's declared default in memory (#630, maintainer decision of 2026-10-06).private voidvalidateBindingRanges(List<Field> touchedFields) Refuses a panel write that sets a key bound by a config binding (#531) outside that binding's range, in the same shape as a@Rangeviolation.protected voidValidates all fields annotated with validation annotations (@Range, @NotEmpty, @Size, @Pattern).private voidvalidateLoaded(boolean initialize) RunsvalidateFields()on the values a load just bound.voidvalidateProposedProperties(com.google.gson.JsonObject jsonObject) Validates that applyingjsonObject's touched fields would NOT violate any@Range/@NotEmpty/@Size/@Patternconstraint, without persisting anything to disk or leaving any field changed (#358 Part 2).private StringvalidateSingleField(Field field) Describes the single validation constraintfieldviolates, if any.private voidwarnConversion(Field field, ConversionException failure, Object raw, Object fieldRaw) private voidwarnGateFailure(String action, RuntimeException failure) An unchecked failure inside the configuration write gate during start-up never refuses the module: one warning names the file and the failure's class (never a message, which may quote file content), the declared defaults run in memory, and the file is not changed (17-65 review round 2 R2-02).private voidwarnNotWritten(List<String> keys, String reason) private static Objectvaluewith each whole number inside a serialized Bukkit value (isSerializedMap(java.lang.Object)) widened to aDoublewhereguide- the module's own value, as plain data - holds a floating-point number at the same place; everything else as it is.private static Objectprivate static ObjectwithoutLeaf(Object tree, List<String> path) A copy oftreewithout the entry atpath(a copy oftreewhen it has none).private OperatorFileWriter.ResultwriteInitialization(Map<Field, Object> inserted, String expected, ConfigDocument read) Writes an initialization - the declared keysinitfound missing, each with its comment, and the framework's token comments - through the config write gate (OperatorFileWriter), at once or from the batch flush.private voidWrites an operator's explicit changes through the configuration write gate - the module's value at each change's path, whatever the file holds there now (the operator's consent), each owned as a whole key - against one read of the file, and advances the baseline and the last-read entry of exactly those paths.private voidwriteOperatorMapEntry(EntryPresence required, String entryPath, String... mapKeys) BothsaveOperatorMapEntryoverloads:requiredisnullfor the unconditional one.
-
Field Details
-
ABSENT_RELOAD_VALUE
-
LOGGER
-
NUMBER_KINDS
Representative classes of each kind a constraint checks, for "can a value of the declared type be one of them". -
TEXT_KINDS
-
CONTAINER_KINDS
-
configFilePath
-
changeListeners
-
ultiToolsPlugin
-
document
-
lastLoadedPresence
-
acknowledgedRaw
-
declaredDefaults
-
declaredDefaultLengths
The declared default's own size per field, measured on the Java value when the defaults are captured (0 for an empty or null container, -1 for a value@Sizedoes not count): a converter or a legacy parser may give the default another plain shape, such as a set written as onejoined:entry (PR #632 local Codex run 1). -
savedSnapshot
-
savedFileFingerprint
-
lastLoadUnparseable
private boolean lastLoadUnparseable -
lastInitIncomplete
private volatile boolean lastInitIncomplete -
defaultsCaptured
private boolean defaultsCaptured -
validatingReload
private boolean validatingReloadSet only while a reload validates the values it read, for the refusal's wording (#595). -
deferInitialization
private boolean deferInitialization -
pendingInitialization
-
commentWriteNotAppliedAtLoad
private boolean commentWriteNotAppliedAtLoadWhether the last load's comment-only write was refused or failed (it has logged its one warning). -
fileNotRestored
private boolean fileNotRestoredWhether a write to this file failed part-way and the file could not be put back from its framework backup (#622): it may hold part of that write, so the entity treats it as changed since it was read and writes nothing to it until a load reads it again. Deliberately not part ofAbstractConfigEntity.PanelCheckpoint: rolling an entity's state back never forgets that its file is in an unknown state. -
warnedCommentKeys
-
compositeReads
The composite values a panel edit is about to rewrite whole, as last read, per setting and unit; set byapplyAndValidateand consumed bypanelChangesunder the entity monitor (route change, R3-01). -
compositeWrites
The whole composite values a panel edit writes, per setting and unit: the value as last read with only the edited fields changed, so every untouched field keeps the bytes the operator wrote (#609, 17-65 round 4 R4-I2); set byapplyAndValidateand consumed bypanelChangesunder the entity monitor, likecompositeReads. -
bindingRanges
The value ranges a config binding (#531) imposes on this entity's keys, by@ConfigEntrypath, then by rule text. A panel write (updateProperties(com.google.gson.JsonObject),validateProposedProperties(com.google.gson.JsonObject)) is refused when it sets a bound key outside its range, the same way as a@Rangeviolation.init(com.ultikits.ultitools.abstracts.UltiToolsPlugin)andreload()do not consult this: the binding step handles an invalid value there (it refuses the module at load and keeps the running value on reload). -
NOT_RESTORED
The refusal reason whilefileNotRestoredholds (#622): a phrase without any value.- See Also:
-
-
Constructor Details
-
AbstractConfigEntity
Constructor for AbstractConfigEntity.- Parameters:
configFilePath- the path to the configuration file, for example: config/config.yml
-
-
Method Details
-
save
Writes the settings this module changed in memory since the last load or save - and nothing else - through the config write gate.A setting is written only when all three hold: the module changed it since the last load or save; the file still holds at that path exactly the value the framework last read or wrote there; and that value is the one the module started from (it converts to the setting's value as last loaded or saved). A setting declared as a
Mapis compared and written entry by entry, following the declared map types into nested maps: only the entries the module added, changed or removed are set or removed. Any other value - a list, aConfigurationSerializablesuch as a BukkitLocation, the value of a typed map entry that is not itself a map - is one value, written whole or not at all, so the file never holds a value mixed from the module's and the operator's. Everything else in the file stays as it is: a save never inserts a key the file lacks, never rewrites a comment and never removes a map entry the module did not remove. When nothing needs writing, the file is not touched (bytes and modification time stay).Why it cannot overwrite operator content. A value the operator edited on disk since it was read, a key the operator deleted, a value the framework could not use (
interval: 3O0, a list elementabc) and a map entry left in the 6.2 split form (o: {O: x}) are never the value the module started from at that path, so they are never written - whether or not the module changed that setting. The config write gate (OperatorFileWriter) then verifies, after rendering, that every line outside the written settings is byte-identical to the file as it is at write time, or writes nothing. A change that is not written stays in memory, and one WARNING names the file and those keys, never a value (maintainer decision 2026-10-04, "what code may write, by file type", which supersedes the earlier "a save warns and overwrites").Use it for a change the operator asked for through the module, or for shipped text the module re-renders after a language switch. A failed write leaves the effective baseline as it was, so the change is still unsaved.
- Throws:
IOException- if publishing the verified file fails; the file then holds the bytes it held before, put back from its framework backup when an in-place write failed part-way, or - when even that fails - one SEVERE names the file and the kept backup and later saves write nothing until a reload
-
apply
Sets the module's value, or removes the map entry the module removed, at each change's path. -
saveOwnership
private static OwnedPaths saveOwnership(List<AbstractConfigEntity.ModuleChange> changes, ConfigDocument read) What a save owns at the config write gate: each change's whole key, except that a map which is empty in the file as read ({}), or which the changes empty, is owned as a whole - its key line must change with its first or last entry, and an empty map holds nothing of the operator's that could be lost. -
saveModuleChanges
The save rule ofsave(): collects the module's changes against the effective baseline; keeps those whose last-read file value is the value the module started from; checks each on one read of the file, which must still hold there what the framework last read (a file already holding the module's value needs nothing); writes the rest through the config write gate against exactly that read; advances the baseline and the last-read entry only for what the file now holds; and names every change that was not written in one warning.- Throws:
IOException
-
acknowledgeWritten
private void acknowledgeWritten(String readFingerprint, String fingerprint, ConfigDocument onDisk, List<AbstractConfigEntity.ModuleChange> applied) Advances the effective baseline and the last-read entry of exactly the changes the file now holds. The entity records the bytes the gate wrote as last read only when the gate's read held exactly the bytes this entity last bound; otherwise the operator's newer edit stays a change on disk (never a fresh read: 17-63 review WR-01).- Parameters:
readFingerprint- the bytes the write was checked against (storage-layer SHA-256 hex)fingerprint- the bytes the file holds now (the gate's written bytes, or the bytes read when nothing was written)onDisk- the document the file holds nowapplied- the changes the file now holds
-
startedFromFile
Whether the value the framework last read forfieldatleafis the value the module started from there: it is present, and the setting's last loaded or saved value with that entry taken from the file converts, without a conversion failure, back to exactly that value. An unconvertible value or element, a key the operator deleted and a 6.2 split map entry are therefore never "started from". -
changedLeaves
private static void changedLeaves(Object base, Object current, Type declared, List<String> prefix, List<List<String>> out) Collects the paths, relative to a setting, at whichcurrentdiffers frombase. Only a value declared as aMapis compared key by key (an added or removed key is one path), following the declared value type into nested maps; anything else - a list, aConfigurationSerializablesuch as a BukkitLocationorVector, a typed map's value of a non-map type - is one value, written whole or not at all, so a save never leaves on disk a value mixed from the module's and the operator's (save rule revision 1, 17-65 review round 1 R65-01). -
mapValueType
The declared value type of a map type (Objectwhen it is raw). -
splitDepth
How many ofkeys, from the start, address entries of declared maps: the keys after them would reach inside a value that is one value (17-65 review round 2 R2-01). -
splitsByEntry
Whether a value is compared entry by entry: it is declared as aMapand both plain forms are maps that are not a serialized object (Bukkit's==type key). -
describeKey
The key a warning names for a change: the setting's path, then the map keys, any key below a secret-shaped one redacted. -
displayKeys
The one display rule for map keys in every configuration diagnostic bound for the server log or the panel (#673, case 54): once the setting or a key above is secret-shaped, every deeper key shows as<redacted>; the secret-shaped key itself is shown, so the operator still sees which setting to fix. Only the displayed text is redacted - whether a value is secret is decided from the real keys (secretPath(java.lang.reflect.Field, java.util.List<java.lang.String>)), never from this text.- Parameters:
secretAbove- whether a key abovekeys- the field name or a declared path segment - is secret-shapedkeys- the real keys, outermost first- Returns:
- the keys to display, in the same order
-
secretSetting
Whether a setting is secret-shaped: its field name or a segment of its declared path. -
secretPath
Whether a value offieldatleafis secret: the setting or a real key down to it is secret-shaped. -
saveOperatorChange
Writes exactly the named settings, as the module holds them now, because the operator explicitly asked for that change - a command such as/setspawn, which names the sixspawn.location.*entries.The operator's request is their consent to replace what the file holds at those settings: the module's value is written there even when the operator also edited that key by hand since it was read (the command wins at the key it names), and a named setting the file lacks is inserted with its comment. Nothing else is written - not another setting, not even one the module changed and did not name - and no other key, value, comment or byte of the file moves (maintainer decision of 2026-10-04, "what code may write, by file type": write exactly the item the operator explicitly asked to change).
Naming a setting declared as a
Mapwrites the whole map as the module holds it: every entry the operator added or edited by hand in that map since it was read is replaced or dropped. For a command that changes one entry - one rule, one warp - usesaveOperatorMapEntry(String, String...), which writes only that entry.Why it cannot overwrite other operator content. The write goes through the framework's configuration write gate owning only the named settings' keys: after rendering, every line outside them must be byte-identical to the file as it is at write time, or nothing is written. When the gate refuses - the file cannot be read or parsed, uses anchors, aliases or merge keys, has a layout the write could not keep byte for byte outside the named keys, or changed while the write was being prepared - this throws
ConfigWriteRefusedExceptionnaming the reason; the file keeps its bytes and the in-memory values stay as the module set them, still unsaved.- Parameters:
entryPaths- theConfigEntry.path()of each setting to write (the field name for an entry declared without a path), at least one- Throws:
IllegalArgumentException- if no path is given, or a path is not a declared entry of this configuration; nothing is writtenIllegalStateException- if called beforeinit, or off the server thread while a server runsConfigWriteRefusedException- if the configuration write gate refused the write; nothing is writtenIOException- if publishing the verified file fails; the file then holds the bytes it held before, put back from its framework backup when an in-place write failed part-way, or - when even that fails - one SEVERE names the file and the kept backup, and this configuration treats the file as changed since it was read: every later write is refused until a reload reads it again- Since:
- 6.3.0
-
saveOperatorMapEntry
Writes exactly one entry of a map setting, as the module holds it now, because the operator explicitly asked for that change - a command such as/autoreply add, which names one rule ofautoreply.rules.The entry is set to the module's value, inserted when the file lacks it; when the module's map no longer holds the entry, the entry is removed from the file. The operator's request is their consent to replace what the file holds at that entry. Every other entry - one the operator added or edited by hand since the file was read, a 6.2 split entry - and every other key, value, comment and byte of the file stay (maintainer decision of 2026-10-04: write exactly the item the operator explicitly asked to change). Each map key is one whole key: a rule named
play.exampleis the single keyplay.example.Why it cannot overwrite other operator content. As
saveOperatorChange(String...): the write owns only that entry's key at the configuration write gate, which refuses any change to another byte of the file and then throwsConfigWriteRefusedExceptionnaming the reason, leaving the file and the in-memory value as they are. A map that is empty in the file ({}), or that the change empties, is owned as a whole, since its key line changes with its first or last entry.- Parameters:
entryPath- theConfigEntry.path()of a setting declared as aMapmapKeys- the keys from that map down to the entry, one whole key each; usually just the entry's key- Throws:
IllegalArgumentException- ifentryPathis not a declared entry of this configuration, the setting is not declared as a map, no key is given, or the keys reach inside an entry that is not itself a declared map (a serializable such as aLocation, or a list, is one value: name the entry itself); nothing is writtenIllegalStateException- if called beforeinit, or off the server thread while a server runsConfigWriteRefusedException- if the configuration write gate refused the write; nothing is writtenIOException- if publishing the verified file fails; the file then holds the bytes it held before, put back from its framework backup when an in-place write failed part-way, or - when even that fails - one SEVERE names the file and the kept backup, and this configuration treats the file as changed since it was read: every later write is refused until a reload reads it again- Since:
- 6.3.0
-
saveOperatorMapEntry
public final void saveOperatorMapEntry(EntryPresence required, String entryPath, String... mapKeys) throws IOException Writes exactly one entry of a map setting, assaveOperatorMapEntry(String, String...)does, but only when the entry's presence in the file is what the operator's command assumes:EntryPresence.MUST_BE_ABSENTfor a command that creates the entry (/autoreply add <name>),EntryPresence.MUST_BE_PRESENTfor one that changes an existing entry (/autoreply setkeyword <name>). When the condition does not hold, nothing is written andConfigEntryPresenceExceptiontells the caller which condition failed. A module then needs no second read of the file to decide whether the command may write.What "present" means. The entry is present when its whole key path exists in the file. The path is where the framework reads the setting (its nested keys, or the flat dotted key the operator wrote), followed by
mapKeys, each one whole key. An entry holdingnullis present. An entry is absent when its key, or a key above it, is missing,null, an empty map or not a map. A file that is missing, empty or holds only comments holds no entry (seeEntryPresence).Why it cannot overwrite other operator content. As
saveOperatorMapEntry(String, String...): the write owns only that entry's key at the configuration write gate, which refuses any change to another byte of the file. The condition adds no second read. It is decided on the same read of the file that the gate verifies the edit against and that the gate checks again immediately before it replaces the file. An entry the operator added or deleted by hand before that read is seen. An edit saved after it makes the gate refuse the write, because the file changed. Soaddnever replaces an entry the operator wrote, andsetkeywordnever writes back an entry the operator deleted.A failed condition logs at FINE only, because the caller reports it to the operator. Every other refusal is as for the two-argument overload: one WARNING names the file and the keys, never a value.
- Parameters:
required- whether the entry must already be in the file, or must not beentryPath- theConfigEntry.path()of a setting declared as aMapmapKeys- the keys from that map down to the entry, one whole key each; usually just the entry's key- Throws:
IllegalArgumentException- ifrequiredisnull, or for any reason the two-argument overload names; nothing is writtenIllegalStateException- if called beforeinit, or off the server thread while a server runsConfigEntryPresenceException- if the entry's presence in the file is notrequired; nothing is writtenConfigWriteRefusedException- if the configuration write gate refused the write; nothing is writtenIOException- if publishing the verified file fails; the file then holds the bytes it held before, put back from its framework backup when an in-place write failed part-way, or - when even that fails - one SEVERE names the file and the kept backup, and this configuration treats the file as changed since it was read: every later write is refused until a reload reads it again- Since:
- 6.3.0
-
writeOperatorMapEntry
private void writeOperatorMapEntry(EntryPresence required, String entryPath, String... mapKeys) throws IOException BothsaveOperatorMapEntryoverloads:requiredisnullfor the unconditional one.- Throws:
IOException
-
spelledLeaf
private List<String> spelledLeaf(Field field, List<String> leaf, Object mine) throws ConfigWriteRefusedException The keys of an operator's map-entry write, spelled as the module's map and the file spell them (#673, case 52). At a map level declared with a non-text key type (Integer,Long,Boolean, ...), a key the module's plain map there does not hold as given is read through that key type and replaced by the one key of the module's map that reads back to the same key, else by the one such key of the value last read from the file, so"1"reaches the entry the file spells'01'. Text that does not convert, or that no key reads back to, is kept as given; levels keyed byStringorObjectare not changed. A setting with its own legacyConfigEntry.parser()owns its keys: they are kept as given (PR #687 local Codex run 2).- Throws:
ConfigWriteRefusedException- if several keys at one level read back to the same key; nothing is written
-
spelledKey
private String spelledKey(Field field, List<String> above, String key, Type keyType, Object moduleLevel, Object fileLevel) throws ConfigWriteRefusedException One key ofspelledLeaf(java.lang.reflect.Field, java.util.List<java.lang.String>, java.lang.Object):aboveholds the keys already resolved, outermost first.- Throws:
ConfigWriteRefusedException
-
keysReadingBack
private List<String> keysReadingBack(Object level, Object typed, Type keyType, List<String> location) The keys oflevel, when it is a map, that read back throughkeyTypetotyped, in map order. -
readKey
textread as a map key ofkeyTypeon the loader's read path, ornullwhen it does not convert. A probe: nothing is logged or recorded. -
several
-
severalSpellingsReason
The refusal text for typed map keys the file writes in several spellings, such as1:and'01':(#673, case 52): the setting and each key (its first spelling) throughdescribeKey(java.lang.reflect.Field, java.util.List<java.lang.String>), never a value or the other spellings. -
severalSpellings
Every typed map key thatreadholds forfieldin several spellings, relative to the setting and ending in its first spelling in file order (#673, case 52). Decided from the file's keys alone through the converter's file-key ambiguity scan - the same key index the writer binds keys with - so a key the module removed or never held still counts. A setting with its own legacyConfigEntry.parser()has none: its parser, not the default key converter, decides which file keys are one key (PR #687 local Codex run 2). -
registryConverts
Whether the converter registry convertsfield, astoPlainResultdecides: an explicit non-default legacyConfigEntry.parser()keeps conversion ownership, so no default typed-key spelling rule applies to it. -
refuseSeveralSpellings
private void refuseSeveralSpellings(ConfigDocument read, Field field) throws ConfigWriteRefusedException An operator write of a setting whose typed map keysreadholds in several spellings is refused.- Throws:
ConfigWriteRefusedException
-
reportedSeveralSpellings
private boolean reportedSeveralSpellings(ConfigDocument read, Field field, List<List<String>> leaves) The save rule for a setting whose typed map keysreadholds in several spellings: none of its changedleavesis written, and one warning names them with the reason, never a value.- Returns:
- whether the setting was reported and must not be written
-
requireOperatorWriteThread
-
declaredEntry
The field declared atentryPath(ConfigEntry.path(), or the field name for an empty path). -
writeOperatorChanges
private void writeOperatorChanges(List<AbstractConfigEntity.ModuleChange> changes) throws IOException Writes an operator's explicit changes through the configuration write gate - the module's value at each change's path, whatever the file holds there now (the operator's consent), each owned as a whole key - against one read of the file, and advances the baseline and the last-read entry of exactly those paths. A refusal throwsConfigWriteRefusedException; nothing is acknowledged then.- Throws:
IOException
-
stageOperatorChanges
private OperatorFileWriter.Staged stageOperatorChanges(List<AbstractConfigEntity.ModuleChange> changes) throws IOException Prepares an operator's explicit changes for the configuration write gate and stages the verified file (seewriteOperatorChanges(java.util.List<com.ultikits.ultitools.abstracts.AbstractConfigEntity.ModuleChange>)); a write the gate settles as refused, or a file that cannot be read or parsed, throwsConfigWriteRefusedExceptionhere, before anything is staged.- Throws:
IOException
-
refused
-
presenceRefused
The refusal of a map-entry write whose presence condition did not hold: logged at FINE only, the caller reports it (#623). -
panelChanges
private List<AbstractConfigEntity.ModuleChange> panelChanges(List<Field> touched, Map<Field, List<List<String>>> leaves) A panel edit's changes: each touched setting as a whole, or - for a map setting edited leaf by leaf - each touched leaf, at the value the edit applied. The operator named exactly these in the panel (maintainer decision of 2026-10-04: write exactly the item the operator explicitly asked to change). -
warnNotWritten
- Parameters:
reason- why nothing was written, ornullfor the save rule's "not the value made from"
-
noteFailedWrite
Records that a write to this file could not be put back after it failed part-way (#622), so the file is treated as changed since it was read until a load reads it again; any other failure left the file holding its previous bytes. -
registry
-
fieldPath
-
keys
-
keysIn
Wheredocholdsfield's setting: its one reading (ConfigDocument.readings(String)) - the nested keys, a flat dotted key such asfeatures.chat, or a mix such asa.b: {c: 1}- or, when it holds none, where an insert puts it (insertKeysIn(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field); the nested keys when that cannot be decided, for a caller that only asks whether the setting is held). One key semantics for every reader and writer of a declared setting (#612): a setting the operator wrote in flat form is read, saved and edited on that line, and never gets a nested copy. A setting held in several forms is refused first (heldTwice(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)). -
insertKeysIn
Where an insert putsfield's setting, whichdocdoes not hold (#614, maintainer decision 2026-10-06): below the longest prefix of its path the file holds as a section (ConfigDocument.heldSections(java.lang.String)), the rest of the path as nested keys - so a section the operator wrote in flat dotted form (a.b:) gets the setting in place, never a second, nested copy of the section. When the file holds that section in several forms, the insert goes below the one form that holds another declared setting of the section (insert-location rule, revision 0, plan 17-75); when none or more than one does, the framework cannot tell, and this returnsnull: the insert is refused, naming the file and the setting (unplacedReason(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)). The nested keys of the whole path when the file holds no prefix as a section.A sibling written as one dotted key below the section (insert-location rule revision 1, gate-1 F4 of plan 17-75, maintainer decision 2026-10-06). When a declared setting of the same section is held with the rest of its path as one dotted key (
a:/b.c: 3), it shares a longer prefix with the missing setting (a.b.d) than any section the file holds; the missing setting is then written in that sibling's form, beside it (b.d: 2), never as a nestedb:/d:- a second form ofa.b, which Bukkit reads as shadowing the first. When siblings sharing that prefix are written in different splits, it cannot be decided.- Returns:
- the key path to insert at, or
nullwhen it cannot be decided
-
placement
-
mirroredPath
The missing setting's path in a sibling's form: the sibling's reading cut at thecommondotted segments both paths share, when that cut falls inside one of its keys ([a, b.c]cut aftera.bgives[a, b.d]fora.b.d);nullwhen the cut falls between keys (that prefix is a held section). -
sectionHoldingASibling
private List<String> sectionHoldingASibling(ConfigDocument doc, Field field, List<List<String>> sections) Of several forms of one section, the one form holding another declared setting of it;nullfor none or several. -
unplacedReason
Whyfield's setting cannot be inserted (insertKeysIn(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)returnednull): names, never values. -
heldTwice
Whetherdocholdsfield's setting in more than one form (#612). -
heldTwiceReason
The refusal text for a setting held in more than one form: the setting path, never a value (#612). -
declaredType
-
plainValue
-
plainValue
The plain value offieldas the module holds it, spelled like the file this entity last read or wrote: the raw acknowledgement is passed as the file value, so a typed map key the file spells'01',1.0or in upper case keeps that text (#673, case 52). Every save, operator write, panel edit, unsaved-entry check and reload's in-memory side therefore names the operator's own key, which is the document's key identity. -
plainValue
- Parameters:
fileValue- the plain value the file holds at the setting, whose typed map keys name the text each key is written under;nullfor canonical key text (a setting the file does not hold)
-
currentPlain
-
currentPlain
-
captureDefaults
private void captureDefaults() -
acknowledgeRaw
-
isTokenComment
-
resolvedComment
-
addEntryComment
Writes the framework's comment abovefield's key intarget: for a token comment, only the run of comment linesframeworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)identifies as the framework's is replaced, and a comment that is the operator's is left as it is; a literal comment is written only for a key just inserted, which has no comment yet.- Returns:
- whether a comment line changed
-
frameworkCommentRun
How many of the last comment lines abovefield's key intargetthe framework wrote (#604, maintainer decision 2026-10-04: "only the framework's own comments are rewritten"). The key's comment - in the byte form it is written in (ConfigDocument.blockCommentAsWritten(List)), without the blank lines above it - is the framework's when it equals, as a whole or as its trailing run of lines, the exact form the framework writes (the key's column,"# "and the text; identification revision 1, 17-64 review round 1 R1-02) of the token's text in a catalogue the module's jar ships, of a text an earlier module version shipped for the entry (ConfigEntry.previousComments()), of the text an operator's custom language file in the module'slang/folder holds for the token now (#615 item 1), of the text the module resolves now, or of the bare{key}token. Equality is the only test: no prefix, similarity, spacing or language tolerance, so a note the operator wrote above the framework's lines, a framework comment the operator edited, or the framework's text written at another column or without the space after#is never taken in. Of several matching texts the longest run counts, so a whole-comment match comes first.- Returns:
- the run's length; 0 when the key has no comment (the framework's comment may be inserted); -1 when the comment is the operator's and must not be touched
-
frameworkRenderings
Every rendering of a token comment the framework may have written abovefield's key: the token's text in each catalogue the module's jar ships (read without any language-file side effect), the texts earlier module versions shipped for the entry (ConfigEntry.previousComments()), the token's text in each of the operator's custom language files in the module'slang/folder (UltiToolsPlugin.customCatalogueTexts(java.lang.String), #615 item 1), the text the module resolves now, and the bare token - each in the byte formConfigDocument.setFrameworkComment(java.util.List<java.lang.String>, java.util.List<java.lang.String>)writes it, asConfigDocument.blockCommentAsWritten(List)reports it. An empty text is not a rendering: the framework writes no comment for it, so it can never identify an operator's bare#line.Why the custom files' texts cannot take in an operator's own comment. While a custom language is selected, the framework renders a token comment from that file's text for the same key, so a comment equal to it, above that key, is a text the framework wrote there; the operator supplied it through their own catalogue, for this very purpose. Only the text for the comment's own key counts, and only as the whole comment or its trailing run of lines, with no tolerance. A comment written from an earlier version of that text - edited in the custom file since - equals no current text and stays the operator's: no record of earlier renderings is kept (maintainer decision of 2026-10-06, option A). The custom files are only read; nothing records, refreshes or backs them up.
-
renderedComment
The comment linestextbecomes when the framework writes it, in their written byte form. -
updateTokenComments
-
panelComment
-
protectFailedLoad
-
failedLoadCause
The safe cause of a load that could not read or parse the file, ornullwhen it did. Names only the exception class or the parser's numeric location, never file content. -
reloadRefusal
#589: a reload that cannot read or parse the file refuses with the file and the safe cause. The caller reports it (the module's reload logs one SEVERE line and/ul reload <name>replies failure), so the entity logs nothing itself. -
safeParserLocation
-
configEntryFields
Every@ConfigEntryfield this class declares or inherits, inReflectionUtil.getFields(Class)order.- Returns:
- the annotated fields, possibly empty
-
validateLoaded
private void validateLoaded(boolean initialize) RunsvalidateFields()on the values a load just bound. A reload's refusal says that fixing the file and reloading again is enough (#595); a refusal at load keeps its wording. -
isModifiedSinceSnapshot
@Internal public final boolean isModifiedSinceSnapshot()Whether serialized fields differ from their last bound/persisted effective values - module changes not yet saved. Map iteration order is significant here; the storage equality used for no-op saves is not. Nothing writes them at server stop: they are reported, by key, and dropped (maintainer decision 2026-10-04). Protected files and uninitialized entities reportfalse.- Returns:
- whether the entity holds module changes that were never saved
- Since:
- 6.3.0
-
unsavedEntryPaths
Names unsaved declared entries without exposing their values, for superseded-copy reporting.- Returns:
- changed entry paths
-
orderedEquals
-
isPresentInFile
Reports presence in the last successfully loaded document, including explicit nulls and undeclared keys. The path is read the way a declared setting path is (#612): split at its dots as nested keys, or held as a flat dotted key such asfeatures.chat, or any mix of the two; the key is present when the file holds it in any of these forms. Failed loads report no presence.- Parameters:
path- dotted configuration path- Returns:
- whether the last load contained the key
- Since:
- 6.3.0
-
isLastLoadUnparseable
@Internal public final boolean isLastLoadUnparseable()Whether the last attempt to read this configuration's file failed to parse (#510) - in practice, whether the file on disk holds invalid YAML. Nothing writes configuration at server stop as of 6.3.0; the stop report names such a configuration once as left alone, instead of listing unsaved keys, and no write path ever replaces a file the framework could not read.Framework-internal: this method is called only by
ConfigManager's stop and unload report and ispublicsolely becauseConfigManagerlives in another package. Module code should not call it.- Returns:
trueif the last load of this configuration's file failed to parse, and no successful load has happened since- Since:
- 6.3.0
-
isLastInitIncomplete
@Internal public final boolean isLastInitIncomplete()Whether the lastinit(com.ultikits.ultitools.abstracts.UltiToolsPlugin)failed before its field validation completed -- for example because writing back a missing key threw anIOException, whichConfigManagerlogs and continues past. The fields may then hold values that their validation annotations never checked. The config-bound@Scheduled/@CmdCDstep (#531) does not apply values from such an entity: it refuses the module at load and keeps the running value on reload.Thread contract: the marker is set by
initand read by the binding step on the main server thread, where the framework and the first-party modules run both. Aninitorreload()run off the main thread is not synchronized with the binding step; confining them to the main thread is tracked in UltiKits/UltiTools-Reborn#538.Framework-internal:
publicsolely because the binding step lives in another package. Module code should not call it.- Returns:
trueif the lastinitdid not complete its validation- Since:
- 6.3.0
-
addBindingRange
Records thatkeyis bound by a config-bound@Scheduledor@CmdCD(#531), so that a panel write setting it to a valueisValidrejects is refused, naming the key, the value andrule. The binding step calls this when it resolves the binding at load. Recording the same rule twice for a key keeps one.Framework-internal:
publicsolely because the binding step lives in another package. Module code should not call it.- Parameters:
key- the@ConfigEntrypath, or the field name when the path is emptyrule- the range, as a sentence, for the refusal messageisValid- whether a value, in seconds (nullfor anullboxed field), is inside the range- Since:
- 6.3.0
-
isFileModifiedSinceSnapshot
@Internal public final boolean isFileModifiedSinceSnapshot()Whether the file on disk differs from the file as it was at the last snapshot point (#510) - in practice, whether someone edited, replaced or removed it while the server was running. The snapshot is the bytes this entity last bound or wrote itself, never a fresh read, so an operator's edit stays visible here until the next load. It is alsotruewhile a write to the file failed part-way and could not be put back from its framework backup (#622): the file may then hold part of that write, so it is treated as changed since it was read until a load reads it again.Framework-internal: no production code calls it as of 6.3.0. Its former caller,
ConfigManager#saveAll(), no longer writes or compares anything (#510, #599); the method is kept for the framework's own regression tests of the snapshot. It ispubliconly because that former caller lives in another package. Module code should not call it.- Returns:
trueif the file's fingerprint changed since the last snapshot, or a write to it could not be put back;falseif neither, or if no snapshot has been taken yet- Since:
- 6.3.0
-
fingerprintOf
Fingerprints a configuration file by the SHA-256 digest of its bytes (#510). A content hash rather than size plus modification time: a typical operator edit changes one value to another of the same length (60to90,truetoTRUE), which size alone cannot see, and modification time is coarse or preserved on common paths (two-second resolution on FAT and many network shares,cp -p,rsync -t, editors that restore it). Configuration files are small and this runs only at load, reload and write time.- Parameters:
file- the configuration file, possiblynullor missing- Returns:
"absent"for a missing file,"unreadable"if it cannot be read, or the Base64 SHA-256 digest of its contents
-
init
Initializes the configuration entity.- Parameters:
ultiToolsPlugin- the plugin instance- Throws:
IOException- if an I/O error occurs
-
initForBatch
Framework manager bridge; binds without flushing initialization writes.- Parameters:
plugin- owning module- Throws:
IOException- on load failure
-
initialize
- Throws:
IOException
-
load
- Throws:
IOException
-
bindDeclaredDefault
Bindsfield's initially declared default because a reload found its key missing from the file and the module had not changed the setting since the last load or save (#596 item 2): deleting a key resets that setting, as an unusable value already does (#523). Memory only - the file is not changed, and a later save does not re-add the key. When the key was in the file at the previous load - the operator deleted it - one warning names the file and the key, never a value; a key the file already lacked (one a refused insert could not add, already named when that write was refused) is not named again at every reload.- Parameters:
field- the setting whose key is missingdeleted- whether the previous load found the key in the file
-
warnGateFailure
An unchecked failure inside the configuration write gate during start-up never refuses the module: one warning names the file and the failure's class (never a message, which may quote file content), the declared defaults run in memory, and the file is not changed (17-65 review round 2 R2-02). -
expectedBase
-
tokenCommentsDiffer
Whether a framework token comment intargetdiffers from what the current language renders, without changingtarget: the framework's own run (frameworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)) compared, in its written byte form, with the current rendering; a comment that is the operator's never differs.- Parameters:
target- the document as read- Returns:
- whether a comment-only write would change a comment
-
ownFrameworkComments
The token comments ofreadthe framework may rewrite, each owning only the run of linesframeworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field)identified as the framework's in the file as read (#604): the config write gate then refuses any write that changes a comment line above that run or of a comment that is the operator's.- Parameters:
owned- the ownership being builtread- the document as read, holding the bytes the write is checked against
-
rewriteTokenComments
Rewrites the framework's token comments in the current language through the config write gate (OperatorFileWriter), at start-up and on reload. This cannot overwrite operator content: the write owns only the comment lines the framework identified as its own above token-commented keys (#604; an operator's comment, or the lines above the framework's run, are not owned), the gate verifies that every other byte of the file is unchanged after rendering and writes nothing when the file no longer holds the bytesloadedread, and it refuses a file using anchors. A refusal or an I/O failure logs one warning and changes no save state, so no later save follows from it (#603); nothing is written at stop.- Parameters:
loaded- the load being bound (LOADED)- Returns:
- the gate's result, or
nullafter an I/O failure
-
refreshFrameworkComments
@Internal public final void refreshFrameworkComments()Rewrites this configuration's framework comment lines in the owning module's language as it is now (#594).A reload reads the module's configurations before it rebuilds the module's language, so the comment pass of that read resolved the tokens with the catalogue of the language the module ran with until then.
UltiToolsPlugin's reload calls this, throughConfigManager, right after the rebuild and before the module's own reload hook, so alanguageswitch applied by/ul reloadreaches the comments too (maintainer decision 2026-10-04, "the framework only refreshes comments on reload").Why it cannot overwrite operator content. It reads the file afresh and hands the config write gate (
OperatorFileWriter) a comment-only write that owns just the comment lines the framework identifies as its own above token-commented keys (frameworkCommentRun(com.ultikits.ultitools.config.document.ConfigDocument, java.lang.reflect.Field), #604), checked against the bytes of that fresh read: no value, no key and no other comment line can change, so an operator's invalid value, a key deleted to reset it, a hand-written note and an edit saved after the reload read the file all stay as typed, and a file that changed again before publishing, uses anchors or has a layout the renderer would normalize is refused with the gate's one warning. Nothing is attempted when the last load refused the file as unreadable or unparseable (it is never written), while a first-start write is still pending, or when no framework comment differs from the current language, nor when this reload's own load already attempted a comment write that was refused or failed: that attempt logged the file's one warning for this reload, and the next reload tries again (17-64 review round 1 R1-03). A refusal or a failure logs one warning and changes no save state, so no later save follows from it (#603, #597 review F2); nothing is written at stop. Only when the fresh read still held exactly the bytes this entity bound does the entity record what the gate wrote as its last read; otherwise the operator's newer file stays a change on disk.Framework-internal:
publicsolely becauseConfigManagerlives in another package. Module code should not call it.- Since:
- 6.3.0
-
writeInitialization
private OperatorFileWriter.Result writeInitialization(Map<Field, Object> inserted, String expected, ConfigDocument read) throws IOExceptionWrites an initialization - the declared keysinitfound missing, each with its comment, and the framework's token comments - through the config write gate (OperatorFileWriter), at once or from the batch flush. This cannot overwrite operator content: the write owns only the inserted keys and the token comment lines the framework identified as its own (#604) (or the whole file when it was absent, created exclusively so a file that appeared meanwhile is never replaced) - and, for a key whose section the operator left with no value (messages:after deleting every key of the section), that section's one line, which carried no value and keeps its key text and comment (#620, maintainer decision 2026-10-06: the key is put back so the section is complete) - the gate verifies that every other byte of the file is unchanged after rendering (layout included), and it writes nothing when the file no longer holds the bytes read atexpected. When it does not write, the gate has logged one warning naming the file and the keys, the fields keep their declared defaults in memory, and the file keeps its bytes.- Parameters:
inserted- the missing fields and their declared default values, as plain dataexpected- the fingerprint of the bytes the init read, orOperatorFileWriter.ABSENTread- the document the init read from those bytes- Returns:
- the gate's result; when it applied the edit, its document and fingerprint are what the file holds
- Throws:
IOException- if publishing the verified text fails
-
entityFingerprint
The fingerprint formfingerprintOf(java.io.File)uses, for a storage-layer SHA-256 in lower-case hex (orOperatorFileWriter.ABSENT): what the entity records as the bytes it last bound or wrote (#602). -
mergeReload
private Object mergeReload(Object base, Object mine, Object theirs, Type declared, Field field, List<String> leaf, List<String> conflicts) The #511 three-way reload merge of one setting. Only a value declared as aMapis merged key by key, following the declared value types into nested maps; any other value - a list, aConfigurationSerializablesuch as a BukkitLocationorVector, a typed map's value that is not a map - is one value: the module's, the file's, or on a conflict the file's whole, never a value mixed from both (17-65 review round 2 R2-01, the same rule as the save'schangedLeaves(java.lang.Object, java.lang.Object, java.lang.reflect.Type, java.util.List<java.lang.String>, java.util.List<java.util.List<java.lang.String>>)). A conflict over a composite value or a list names the key only.The recursion carries the setting and the real keys down to the value. A conflict's key is displayed through
describeKey(java.lang.reflect.Field, java.util.List<java.lang.String>), so a key below a secret-shaped field name, declared path segment or map key is never named, and whether the discarded value is redacted is decided from the real keys (#673, case 54). The merged map keeps the real keys. -
flushInitializationWrite
Flushes only a validated manager batch's initialization write, through the config write gate and only while the file still holds the bytesinitForBatchread (#602); seewriteInitialization(java.util.Map<java.lang.reflect.Field, java.lang.Object>, java.lang.String, com.ultikits.ultitools.config.document.ConfigDocument).- Throws:
IOException- when replacement fails; the entity is then protected until reload
-
discardInitializationWrite
@Internal public final void discardInitializationWrite()Clears an uncommitted initialization belonging to a refused manager batch. -
warnConversion
-
conversionReason
-
containsSecret
-
typeName
-
splitComment
Splits a@ConfigEntry.comment()value into oneListelement per line, in declaration order, ready forConfigurationSection's comment-writing API. No blank leading element is added (Claude's Discretion, D-07) - it would produce a diff on every regenerated file for a purely cosmetic gain, contrary to D-01's touch-as-little-as-possible posture.- Parameters:
comment- the rawcomment()attribute value, possibly empty- Returns:
- one element per line, or an empty list if
commentis blank
-
updateProperties
Updates the properties of the configuration entity.The field traversal and path-derivation must match
init()/save()/reload()exactly, or a write can silently no-op: all four methods walk@ConfigEntryfields, and this method previously diverged from them in two ways -- usinggetDeclaredFields()instead ofReflectionUtil.getFields(Class)(missing inherited fields), and not normalizing an emptypathto the field name. The second divergence was the more hidden one: omittingpathon@ConfigEntryis a supported style,init/saveread and write it by field name, andtoJsonObject()also sends it to the panel by field name -- but this method was looking for an empty-string key in the JSON, which never exists, so the field was silently skipped while persistence still ran and the caller still received success.Since 6.3.0 a panel edit writes exactly the settings it touches - or, inside a map setting, exactly the touched entries - through the framework's configuration write gate: the operator named them, so their values replace what the file holds there, and every other key, value, comment and byte of the file stays as it is (maintainer decision of 2026-10-04, "what code may write, by file type"). It cannot overwrite other operator content: the gate owns only the touched keys and writes nothing unless every other line comes out byte-identical. When the gate refuses (anchors, a layout it cannot keep, a file that cannot be read or parsed, or one that changed while the write was prepared), this throws
ConfigWriteRefusedExceptionnaming the reason and the entity is left exactly as before the call.Since 6.3.0 (SILENT-14, closing CR-01) this method validates the full post-update field state - the same
validateFields()init(UltiToolsPlugin)/reload()already use - before either document mutation or file persistence runs. A violating value refuses withConfigurationExceptioninstead of being written: the operator's file is left byte-identical, and every field this call touched is restored to the value it held before the call, so memory never disagrees with disk (D-01, D-04). The entity keeps running after a refusal, so its in-memory state must not be left holding a rejected value;reload()follows the same all-or-nothing rule. The same holds when the file replacement itself fails: every field and every piece of save tracking is restored to its state before the call, and theIOExceptionis rethrown.- Parameters:
jsonObject- the JSON object containing the new properties- Throws:
IOException- if an I/O error occurs, or the write gate refused the write (ConfigWriteRefusedException); the entity is then left as it wasConfigurationException- withErrorCode.CONFIG_VALIDATION_FAILEDif the post-update field state violates a@Range/@NotEmpty/@Size/@Patternconstraint - the file is not written and touched fields are restored
-
preparePanelWrite
@Internal public final AbstractConfigEntity.PanelWrite preparePanelWrite(com.google.gson.JsonObject properties) throws IOException Prepares one panel entity without acknowledging or replacing its file: the touched settings or leaves are verified and staged through the configuration write gate (seeupdateProperties(com.google.gson.JsonObject)); the commit re-checks the file's bytes immediately before its move.- Parameters:
properties- proposed panel values- Returns:
- manager-owned write
- Throws:
IOException- if reading or staging fails, or the gate refused the write (ConfigWriteRefusedException)
-
restoreFields
-
validateProposedProperties
public void validateProposedProperties(com.google.gson.JsonObject jsonObject) Validates that applyingjsonObject's touched fields would NOT violate any@Range/@NotEmpty/@Size/@Patternconstraint, without persisting anything to disk or leaving any field changed (#358 Part 2).Used by
ConfigManager.loadFromJson(String)to validate every entity touched by a multi-file panel-pushed batch BEFORE persisting any of them - a refusal on entity N must not leave entities 1..N-1 already written to disk. This method always restores every field it touched, whether validation passes or fails; the caller is expected to callupdateProperties(JsonObject)itself afterward (which re-validates - cheap on the documented idiom - and persists) once every entity in its own batch has passed this check.- Parameters:
jsonObject- the JSON object containing the candidate new properties- Throws:
ConfigurationException- withErrorCode.CONFIG_VALIDATION_FAILEDif the candidate post-update field state would violate a validation constraint
-
applyAndValidate
private Map<Field,List<List<String>>> applyAndValidate(com.google.gson.JsonObject jsonObject, List<Field> touchedFieldsOut, List<Object> previousValuesOut) Applies everyjsonObjectfield this class declares via@ConfigEntryto this instance, then validates the resulting state viavalidateFields(). Never itself persists or restores anything - callers decide what happens next.touchedFieldsOut/previousValuesOutare populated even whenvalidateFields()throws, so a caller can still restore exactly what this call touched.- Parameters:
jsonObject- the JSON object containing the candidate new propertiestouchedFieldsOut- populated, in application order, with every field this call appliedpreviousValuesOut- populated in the same order with each field's pre-call value- Throws:
ConfigurationException- withErrorCode.CONFIG_VALIDATION_FAILEDif the post-update field state violates a constraint. A refusal of panel paths names each path through the display rule ofdescribeKey(java.lang.reflect.Field, java.util.List<java.lang.String>)- never a key below a secret-shaped one, never a value - because the message is logged and returned to the panel (#673, case 54)
-
describeUndeclared
How a refusal names a panel path no declared setting owns: the rule ofdisplayKeys(boolean, java.util.List<java.lang.String>)over its dotted segments, so everything below the first secret-shaped segment is redacted (#673). -
dottedKeys
The dotted segments of a panel path whose real keys are not known, for display only: a whole key holding a dot is split, which can only redact more, never less (no secret-shaped word contains a dot). -
describeReadings
Every reading of an ambiguous panel path, as the real keys ofowner's map displayed bydisplayKeys(boolean, java.util.List<java.lang.String>). -
describeFailure
How a refusal names the path of a conversion failure offield: throughdescribeKey(java.lang.reflect.Field, java.util.List<java.lang.String>)with the keys below the setting's declared keys; a path that does not start with them is displayed whole by the same rule. -
parentInMemory
Whether the map holding the last key ofpathexists intree(the module's value in memory). -
isSerializedMap
Whethervalueis a serialized Bukkit value: a map carrying the==type key. -
widenLike
valuewith each whole number inside a serialized Bukkit value (isSerializedMap(java.lang.Object)) widened to aDoublewhereguide- the module's own value, as plain data - holds a floating-point number at the same place; everything else as it is. A whole number given directly (guidea floating-point number) is widened too. Used for a panel edit's conversion and for the edited field it writes (#609, R4-I1). -
widenLike
-
mapLeaf
-
mapContains
-
withoutLeaf
A copy oftreewithout the entry atpath(a copy oftreewhen it has none). -
patchedMap
-
matchMapPaths
-
replaceMapLeaf
-
jsonToPlain
-
validateBindingRanges
Refuses a panel write that sets a key bound by a config binding (#531) outside that binding's range, in the same shape as a@Rangeviolation. Only the fields this write touched are checked: a bound field left as it was may hold a value a refused reload kept out of use, and editing another key must not fail because of it.- Parameters:
touchedFields- the fields this write applied- Throws:
ConfigurationException- withErrorCode.CONFIG_VALIDATION_FAILEDnaming each bound key, its value and its range
-
toJsonObject
public com.google.gson.JsonObject toJsonObject()Converts the configuration entity to a JSON object.- Returns:
- the JSON object representation of the configuration entity
-
addLeaves
-
getComments
public com.google.gson.JsonObject getComments()Gets the comments of the configuration entity.- Returns:
- a JSON object containing the comments
-
validateFields
protected void validateFields()Validates all fields annotated with validation annotations (@Range, @NotEmpty, @Size, @Pattern). A violation refuses this config's module instead of rewriting the value - the operator's file is never modified (D-01). Every violating field is collected and named in a single refusal. At load the operator fixes the value(s) on disk and restarts; on a reload the running values are kept and fixing the file and reloading again is enough (#595).- Throws:
ConfigurationException- withErrorCode.CONFIG_VALIDATION_FAILEDif any field violates its validation constraint, or if this config class cannot be constructed through either of the two framework-supported idioms (D-03)
-
ensureConstructable
private void ensureConstructable()Constructs and discards an instance of this config class through the same two-step fallbackConfigManager.registerAlluses at registration time - a(String)constructor first, then an accessible no-arg constructor. Existence, not the constructed value, is what this proves: the framework needs every registered config class to still be buildable through one of its two documented idioms (D-02). Neither resolving is a genuine config-class error (D-03).- Throws:
ConfigurationException- if neither constructor resolves
-
constructSibling
Constructs a fresh instance of this config class through the(String)constructor, or failing that the no-arg constructor - the two idiomsensureConstructable()proves - for the existing validation constructability precondition only.- Returns:
- a new, uninitialized instance of this entity's class
- Throws:
ConfigurationException- if neither constructor resolves
-
validateSingleField
Describes the single validation constraintfieldviolates, if any.- Parameters:
field- the field to check, already made accessible- Returns:
- a violation description naming the field, its actual value (redacted for
@Patternon a secret-shaped field name, T-04-04) and the broken constraint, ornullif the field's value satisfies its annotations - Throws:
IllegalAccessException
-
isSecretShapedFieldName
Whether a field name looks like it stores a secret. Only@Patternviolations echo an arbitrary string value;@Range/@Sizeviolations always echo a number or a length, and@NotEmptyviolations are empty by definition, so neither can leak a secret verbatim (T-04-04).Widened in 6.3.0 (04-REVIEW.md WR-03) to also cover
key/auth/private/cert, accepting the resulting false positives (a field merely named e.g.publicKeyis redacted too) as the safer default, per Phase 2 D-15's fail-closed preference. The reason for the widening is new, not cosmetic: before the write-path refusal added by this same 6.3.0 change, a@Patternrefusal message went only to the local console; now both remote config-write handlers forward it verbatim to UltiPanel over the WebSocket (T-04-56), so a name-heuristic miss here leaks the server, not just the console. -
unreadableKind
A violation for a bound value of a kind the field's annotation cannot read - possible only when the setting is declared as a supertype such asObject(final review of plan 17-76: a declared constraint works or is refused, never passes silently).@Rangereads numbers,@Patterntext,@Sizetext, collections, maps and arrays,@NotEmptythe same kinds;nullis left to@NotEmptyas before. It is a violation like any other, with that annotation's outcome (refused at load; at reload the reload is refused and the running values are kept). The message names the field and the value's kind, never the value.- Returns:
- the violation, or
nullwhen every annotation on the field can read the value
-
isRangeViolation
-
isNotEmptyViolation
-
isSizeViolation
-
isPatternViolation
-
getValueLength
What@Sizecounts: a text's length (acharis one character), a collection's size, a map's entries and an array's length (maps and arrays since 6.3.0, #631);-1for anything else. -
containerKind
The value kind@NotEmptytreats as a container onfield:"list"(a list or an array),"set","map"or"collection"by the field's declared type, ornullfor any other type (text keeps its refusal). -
isEmptyContainer
Whether a container value holds nothing:null, an empty collection, map or array. -
useDeclaredDefaultIfEmpty
A@NotEmptylist, set or map that a load or reload bound empty - the file holds it empty ornull, or every entry failed to bind - runs on the field's declared default in memory (#630, maintainer decision of 2026-10-06). One WARNING names the file, the key, the value kind, the value as written and the default; both are redacted when the field name, a key segment, or a map key inside the value or the default is secret-shaped, as a conversion warning is. It is not a violation, so the module loads.Why it cannot overwrite operator content: nothing here writes. The setting's baseline becomes the default the field now holds (the load records the bound value after this call) and the file's value stays the one last read, so
save()finds no module change to write and never puts the default over the operator's empty value.- Parameters:
field- the setting just bound, already made accessibleraw- the value as the file holds it
-
oneLine
A value for a warning on one log line, or<redacted>. -
checkConstraintDeclarations
private void checkConstraintDeclarations()Refuses, at load and before the file is read, a constraint declaration the framework cannot hold (maintainer decision of 2026-10-06, option A; #630, #631). One refusal names every such field; no file is read or written:- a constraint on a value type it can never check:
@Rangeoff numbers,@Patternoff text,@Sizeand@NotEmptyoff text, collections, maps and arrays (text is aStringor achar); - a constraint on a field of this class that is not a
@ConfigEntrysetting; - a
@NotEmptylist, set, map or array whose declared default is empty, or outside the field's own@Size, which leaves nothing valid to run in place of an empty value.
ConversionTypes.raw(java.lang.reflect.Type): the check never walks into a setting's value type (maintainer decision of 2026-10-06, the simplest route). A constraint annotation on a field of a value type, a nested class or anything a converter produces is never checked and not reported - the documented limit (COMPATIBILITY.md); a module validates such fields in its converter. - a constraint on a value type it can never check:
-
defaultCannotStandIn
Why the declared default of a@NotEmptylist, set, map or array cannot stand in for an empty value, ornullwhen it can (#630, maintainer decision of 2026-10-06): it is empty itself, or it violates the field's other constraint -@Size, the only other one such a field can carry (the declaration check refuses@Rangeand@Patternon it). -
constraintsOn
The four constraint annotationselementcarries, in a fixed order. -
unsupportedBy
Whyconstraintcannot be checked on a setting declared asraw, ornullwhen it can: the value kinds the validation step reads (isRangeViolation(java.lang.reflect.Field, java.lang.Object),isPatternViolation(java.lang.reflect.Field, java.lang.Object),getValueLength(java.lang.Object),isNotEmptyViolation(java.lang.reflect.Field, java.lang.Object)). -
canHold
Whether a field declared asrawcan hold a value of one ofkinds:rawis one of them, a subtype, or a supertype such asObject,Serializable,Comparable,CharSequenceorNumber(final review F1 of plan 17-76). The runtime validator checks the value bound at load, so only a declared type that can never hold a checkable value is a declaration error. -
isNumberType
-
isTextType
-
isContainerType
-
addChangeListener
Adds a configuration change listener. The listener will be notified when the configuration is reloaded.- Parameters:
listener- the listener to add
-
removeChangeListener
Removes a configuration change listener.- Parameters:
listener- the listener to remove
-
clearChangeListeners
public void clearChangeListeners()Removes all configuration change listeners. -
getChangeListenerCount
public int getChangeListenerCount()Gets the number of registered change listeners.- Returns:
- the number of listeners
-
notifyChangeListeners
protected void notifyChangeListeners()Notifies all registered listeners about the configuration change. Individual listener exceptions do not affect other listeners. -
reload
Reloads the configuration from file and notifies all listeners.Since 6.3.0 a reload is all-or-nothing: if it fails for any reason (a validation violation, a conversion or I/O failure), every field value and every piece of load tracking is restored to what it was before the attempt, and the failure is rethrown without notifying listeners. A rejected file value therefore never becomes an unsaved in-memory edit that the next reload's three-way merge would keep over a corrected file.
Since 6.3.0 (#589) a file that cannot be read or parsed fails the reload the same way: it throws
ConfigurationExceptionnaming the file and the safe cause (the exception class, or the parser's line and column - never file content), the running values and the file stay exactly as they were, and the file stays protected from writes until a later successful load. Nothing is logged by the entity for it: the caller reports the failure, as/ul reloaddoes. Initial loading throughinit(com.ultikits.ultitools.abstracts.UltiToolsPlugin)is different and unchanged: it logs one SEVERE line, keeps the declared defaults, and never overwrites the file.- Throws:
ConfigurationException- if the file cannot be read or parsed, or a value it holds fails validation or conversion; nothing changedIOException- if an I/O error occurs
-