Class AbstractConfigEntity
Precondition for subclasses (#363, counts re-measured for #510): the constructor must be cheap
and free of side effects. For any class declaring at least one @ConfigEntry field, the
framework constructs and discards throwaway instances of this class on the paths below, and a
constructor that opens a file, registers a listener, or otherwise does real work pays that cost
again on every one of those events, purely to be thrown away:
validateFields()- reached frominit(UltiToolsPlugin),reload(),updateProperties(com.google.gson.JsonObject)andvalidateProposedProperties(com.google.gson.JsonObject)- constructs ONE instance viaensureConstructable()to prove the class still supports one of the framework's two documented construction idioms. The construction happens before the accept/refuse decision, not only when a panel write is ultimately accepted.canonicalize(String)constructs ONE instance, which it uses to read the text being canonicalized (see that method). It runs once intakeSnapshot()- that is, on everyinit(UltiToolsPlugin),reload(), successfulsave()and successfulupdateProperties(JsonObject)- and once inisModifiedSinceSnapshot(), the shutdown check.
init() 2, reload() 2, a panel write 2, save() 1, and the shutdown check 1 per configuration (2 when it goes on to save). save() and the shutdown check construct nothing before 6.3.0 and are the two paths a module
author is most likely to consider exempt. A class with zero @ConfigEntry fields
constructs nothing on any of them. The documented super(configFilePath)-only idiom is
unaffected - each construction is a single, trivial reflective call.
Saved-state snapshot (#510, since 6.3.0): every entity remembers what its file on disk holds as
of the last time the framework read or wrote it - after init(UltiToolsPlugin) (including
a first-boot defaults write), after reload(), after every successful save(),
and after every successful updateProperties(JsonObject) - together with a SHA-256
fingerprint of the file at that same point. The snapshot is derived from the file's text, plus
this class's declared defaults for keys the text does not contain, and never from this instance's
fields, so no unwritten in-memory change can ever be recorded as saved. The
shutdown save (ConfigManager#saveAll()) writes only the entities for which isModifiedSinceSnapshot() is true, so an operator's edit to a file whose configuration
no module code changed survives a restart. An explicit save() call still writes
unconditionally.
Thread safety (#510): the framework's own read, write, snapshot and comparison paths - init(UltiToolsPlugin)'s and reload()'s load, save(), updateProperties(JsonObject), validateProposedProperties(JsonObject) and the two
snapshot checks - run under this entity's own monitor, which ConfigManager#saveAll() also
holds across its check-then-save of this entity. A panel write arriving on the WebSocket thread
and the shutdown save therefore each see the other's whole effect or none of it. Field setters in
module code are not synchronized by the framework.
That monitor is held across file I/O and across the constructions listed above, so it is also a
new direction of blocking: a panel write on the WebSocket thread holds it across validation, the
write and the snapshot, and a module calling save() on the server thread waits for that
span. Both are bounded by small configuration files and by the cheap-constructor precondition.
-
Field Summary
FieldsModifier and TypeFieldDescriptionThe value ranges a config binding (#531) imposes on this entity's keys, by@ConfigEntrypath, then by rule text.private final List<ConfigChangeListener> private org.bukkit.configuration.file.YamlConfigurationprivate final Stringprivate booleanWhether the lastinit(com.ultikits.ultitools.abstracts.UltiToolsPlugin)did not run to the end of its validation -- set when it starts, cleared only aftervalidateFields()succeeds.private booleanWhether the last attempt to read this configuration's file failed to parse (#510).private static final Loggerprivate StringFingerprint of the file on disk as of the last snapshot point (#510), seefingerprintOf(File);nullbefore the first snapshot.private StringThe canonical form (seecanonicalize(String)) of the text on disk as of the last snapshot point (#510), ornullbefore the first successful snapshot - whichisModifiedSinceSnapshot()treats as modified, so shutdown keeps the pre-#510 "save it" behaviour for an entity that was never in sync with its file.private UltiToolsPluginConverts aNumberto the wrapper at the same index ofWIDENING_ORDER.The numeric wrappers in JLS 5.1.2 widening order: every conversion from an earlier entry to a later one is a widening primitive conversion, and no other conversion between them is. -
Constructor Summary
ConstructorsConstructorDescriptionAbstractConfigEntity(String configFilePath) Constructor for AbstractConfigEntity. -
Method Summary
Modifier and TypeMethodDescriptionfinal 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 voidapplyAndValidate(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 voidapplyFieldsTo(org.bukkit.configuration.file.YamlConfiguration target) Copies every non-null@ConfigEntryfield, serialized through its declared parser, ontotarget.private Stringcanonicalize(String text) Brings a configuration text to the form this entity would give it after reading it and saving it again (#510): the text is parsed, a throwaway instance of this class (one per call, see the construction counts in this class's own javadoc) loads every present@ConfigEntrykey through its parser exactly asinit(UltiToolsPlugin)does (absent keys keep that instance's declared defaults), and the instance's fields are applied back onto the parsed text throughapplyFieldsTo(YamlConfiguration).private StringcanonicalizeOnce(String text, AbstractConfigEntity probe, List<Field> configFields) One pass ofcanonicalize(String).voidRemoves all configuration change listeners.Every@ConfigEntryfield this class declares or inherits, inReflectionUtil.getFields(Class)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 - forensureConstructable()and for the throwaway readercanonicalizeOnce(String, AbstractConfigEntity, java.util.List)uses (#510).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 StringfingerprintOf(File file) Fingerprints a configuration file by the SHA-256 digest of its bytes (#510).intGets the number of registered change listeners.com.google.gson.JsonObjectGets the comments of the configuration entity.private intgetValueLength(Object value) final voidinit(UltiToolsPlugin ultiToolsPlugin) Initializes the configuration entity.final booleanfinal 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 this entity's current state differs from what its file held when the framework last read or wrote it (#510): the canonical form of the textsave()would write now is compared with the snapshot.private booleanisNotEmptyViolation(Field field, Object value) private booleanisPatternViolation(Field field, Object value) private booleanisRangeViolation(Field field, Object value) private booleanisSecretShapedFieldName(String fieldName) Whether a field name looks like it stores a secret.private booleanisSizeViolation(Field field, Object value) protected voidNotifies all registered listeners about the configuration change.private static ObjectreadConfigValue(Field field, ConfigEntry annotation, Object raw) The one conversion from a raw YAML value to the value stored in a@ConfigEntryfield: the entry's parser, thenwidenToFieldType(java.lang.Class<?>, java.lang.Object).voidreload()Reloads the configuration from file and notifies all listeners.voidremoveChangeListener(ConfigChangeListener listener) Removes a configuration change listener.private StringrenderParsed(String text) Parsestextand renders it back, with no field applied - the whole ofcanonicalize(String)for a class that declares no@ConfigEntryfield.private StringRenders the exact textsave()would write right now, without writing it and without touching the liveconfig(whichtoJsonObject()still reports to the panel).voidsave()Saves the configuration to the file.splitComment(String comment) Splits a@ConfigEntry.comment()value into oneListelement per line, in declaration order, ready forConfigurationSection's comment-writing API.private voidRecords the snapshot and the on-disk file fingerprint (#510).com.google.gson.JsonObjectConverts the configuration entity to a JSON object.voidupdateProperties(com.google.gson.JsonObject jsonObject) Updates the properties of the configuration entity.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).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.(package private) static ObjectwidenToFieldType(Class<?> fieldType, Object value) Gives a boxed numeric field exactly the widening conversions its primitive already gets.
-
Field Details
-
LOGGER
-
WIDENING_ORDER
The numeric wrappers in JLS 5.1.2 widening order: every conversion from an earlier entry to a later one is a widening primitive conversion, and no other conversion between them is. -
WIDENERS
Converts aNumberto the wrapper at the same index ofWIDENING_ORDER. -
configFilePath
-
changeListeners
-
ultiToolsPlugin
-
config
private org.bukkit.configuration.file.YamlConfiguration config -
savedSnapshot
The canonical form (seecanonicalize(String)) of the text on disk as of the last snapshot point (#510), ornullbefore the first successful snapshot - whichisModifiedSinceSnapshot()treats as modified, so shutdown keeps the pre-#510 "save it" behaviour for an entity that was never in sync with its file. A serialized string, never a reference to or shallow copy of the field values: a module that mutates a collection field in place (UltiChat's auto-replyrulesmap) must still be detected as changed. -
savedFileFingerprint
Fingerprint of the file on disk as of the last snapshot point (#510), seefingerprintOf(File);nullbefore the first snapshot. -
lastLoadUnparseable
private volatile boolean lastLoadUnparseableWhether the last attempt to read this configuration's file failed to parse (#510). While set, the framework does not know what the file holds, soisModifiedSinceSnapshot()reportsfalseand the shutdown save leaves the file alone rather than overwriting an operator's broken file with the in-memory state. Cleared by the next successful load or save. -
lastInitIncomplete
private volatile boolean lastInitIncompleteWhether the lastinit(com.ultikits.ultitools.abstracts.UltiToolsPlugin)did not run to the end of its validation -- set when it starts, cleared only aftervalidateFields()succeeds. A caller that catches theIOExceptionof a failed write-back (asConfigManagerdoes) is otherwise left with an entity whose fields hold the file's new values unvalidated (#533). Kept on the entity so that it lives and dies with it; nothing else has to remember to clear it (Codex round 3 on #536). -
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).
-
-
Constructor Details
-
AbstractConfigEntity
Constructor for AbstractConfigEntity.- Parameters:
configFilePath- the path to the configuration file, for example: config/config.yml
-
-
Method Details
-
save
Saves the configuration to the file.An explicit call always writes, whether or not anything changed since the last snapshot. Only the shutdown save (
ConfigManager#saveAll()) is conditional onisModifiedSinceSnapshot()(#510). A successful write refreshes the snapshot; a failed write leaves the previous snapshot in place, so the entity stays modified and the shutdown save retries it.- Throws:
IOException- if an I/O error occurs
-
applyFieldsTo
private void applyFieldsTo(org.bukkit.configuration.file.YamlConfiguration target) Copies every non-null@ConfigEntryfield, serialized through its declared parser, ontotarget. The one serialization path shared bysave(),renderSaveText()andcanonicalizeOnce(String, AbstractConfigEntity, java.util.List), so the shutdown comparison renders exactly whatsave()would write (#510).- Parameters:
target- the configuration to write the serialized field values into
-
renderSaveText
Renders the exact textsave()would write right now, without writing it and without touching the liveconfig(whichtoJsonObject()still reports to the panel). The live configuration is copied through its own YAML text - comments included - and the fields are applied to the copy throughapplyFieldsTo(YamlConfiguration), the same pathsave()uses.- Returns:
- the rendered text, or
nullif the live configuration's own text cannot be parsed back
-
canonicalize
Brings a configuration text to the form this entity would give it after reading it and saving it again (#510): the text is parsed, a throwaway instance of this class (one per call, see the construction counts in this class's own javadoc) loads every present@ConfigEntrykey through its parser exactly asinit(UltiToolsPlugin)does (absent keys keep that instance's declared defaults), and the instance's fields are applied back onto the parsed text throughapplyFieldsTo(YamlConfiguration). Two passes make the result stable, because a default filled in for an absent key by the first pass is re-read through the parser by the second.Both sides of the shutdown comparison go through this: the snapshot canonicalizes the text on disk,
isModifiedSinceSnapshot()canonicalizes whatsave()would write now. Parser quirks therefore cancel out (for exampleDefaultConfigParserreading the integers of a YAML list back as strings), while any in-memory value the file does not hold - a field a partial panel write did not touch, a key missing from a reloaded file - still differs.- Parameters:
text- a YAML text, possiblynull- Returns:
- the canonical text, or
nulliftextisnullor cannot be parsed
-
configEntryFields
Every@ConfigEntryfield this class declares or inherits, inReflectionUtil.getFields(Class)order.- Returns:
- the annotated fields, possibly empty
-
renderParsed
Parsestextand renders it back, with no field applied - the whole ofcanonicalize(String)for a class that declares no@ConfigEntryfield.- Parameters:
text- a YAML text- Returns:
- the re-rendered text, or
nullif it cannot be parsed
-
canonicalizeOnce
One pass ofcanonicalize(String).- Parameters:
text- a YAML text, possiblynullprobe- the throwaway instance this pass readstextintoconfigFields- this class's@ConfigEntryfields, never empty- Returns:
- the text after one read-and-render pass, or
nullif it cannot be parsed
-
takeSnapshot
private void takeSnapshot()Records the snapshot and the on-disk file fingerprint (#510). Called, under this entity's monitor, right afterinit(UltiToolsPlugin)orreload()has read the file and right aftersave()orupdateProperties(JsonObject)has written it - at each of those points the liveconfigholds exactly the file's content, and the snapshot is derived from that text alone.Never throws. If the snapshot cannot be computed, it is cleared rather than left stale, so the entity counts as modified and shutdown saves it exactly as it did before #510, and a WARNING says so; a fingerprint that cannot be computed is recorded as
"unreadable". -
isModifiedSinceSnapshot
@Internal public final boolean isModifiedSinceSnapshot()Whether this entity's current state differs from what its file held when the framework last read or wrote it (#510): the canonical form of the textsave()would write now is compared with the snapshot. This is what the shutdown save uses to decide whether to write this configuration at all.Framework-internal: this method is called only by
ConfigManager#saveAll()and ispublicsolely becauseConfigManagerlives in another package. Module code should not call it.An entity that was never initialized has nothing to save and reports
false. An entity with no snapshot yet (its first-boot defaults write failed, or its snapshot could not be computed) reportstrue, preserving the pre-#510 behaviour of saving it at shutdown. A map field whose entries were only reordered also reportstrue, because serialization follows the map's iteration order; saving it is harmless and matches the pre-#510 behaviour.Two cases deliberately report
false. An entity whose file failed to parse the last time it was read (isLastLoadUnparseable()) is never written by the shutdown save: the framework does not know what that file holds, so overwriting it with the in-memory state would destroy an operator's broken-but-recoverable file. And a key the file does not contain whose in-memory value equals this class's declared default is indistinguishable from the file's own implied state; it is not written at shutdown, and the next load produces the same value anyway.- Returns:
trueif the shutdown save should write this configuration- 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. The shutdown save skips such a configuration and says so, instead of overwriting a file the framework could not read.Framework-internal: this method is called only by
ConfigManager#saveAll()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 or save 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() -
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, save and shutdown.- 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
-
readConfigValue
The one conversion from a raw YAML value to the value stored in a@ConfigEntryfield: the entry's parser, thenwidenToFieldType(java.lang.Class<?>, java.lang.Object). Every place that reads the file into a field goes through here --init(com.ultikits.ultitools.abstracts.UltiToolsPlugin),reload()and the #510 snapshot probe incanonicalizeOnce-- so the three cannot drift apart again. Round 2 of #531 gate-1 CR-01 found the snapshot probe still unwidened: aLongfield made every snapshot fail, and the shutdown save then overwrote operator edits (the #510 defect, reinstated).- Parameters:
field- the target@ConfigEntryfieldannotation- its@ConfigEntryraw- the value SnakeYAML returned for the entry's path, nevernull- Returns:
- the value to store in
field
-
widenToFieldType
Gives a boxed numeric field exactly the widening conversions its primitive already gets.SnakeYAML hands back an
Integerfor a whole number such as1800.Field.setwidens that into alongordoublefield, but it refuses the same value for aLong,DoubleorFloatfield (measured:IllegalArgumentException: Can not set java.lang.Long field ... to java.lang.Integer). A boxed field therefore loaded on the first boot, when the key was missing and the field default was written, and threw on every later boot and on every reload (#531, gate-1 CR-01).Only the JLS 5.1.2 widening primitive conversions are applied, so a boxed field accepts exactly what its primitive accepts:
ShortfromByte;IntegerfromByte/Short;LongfromByte/Short/Integer;Floatfrom any integral value;Doublefrom any integral value or aFloat. Anything else -- a narrowing conversion, a non-numeric value, a field that is not a numeric wrapper -- is returned unchanged, soField.setrefuses it exactly as before.- Parameters:
fieldType- the declared type of the target fieldvalue- the parsed value- Returns:
valuewidened tofieldType, orvalueitself
-
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 whileconfig.savestill ran and the caller still received success.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 eitherconfig.set(...)orconfig.save(...)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). Unlikereload(), this entity keeps running after a refusal, so its in-memory state must not be left holding a rejected value.- Parameters:
jsonObject- the JSON object containing the new properties- Throws:
IOException- if an I/O error occursConfigurationException- 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
-
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 void 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
-
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
-
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; the module author must fix the value(s) on disk and restart.- 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 - forensureConstructable()and for the throwaway readercanonicalizeOnce(String, AbstractConfigEntity, java.util.List)uses (#510).- 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. -
isRangeViolation
-
isNotEmptyViolation
-
isSizeViolation
-
isPatternViolation
-
getValueLength
-
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.- Throws:
IOException- if an I/O error occurs
-