Class ConfigManager
- Version:
- 1.0.0
- Author:
- wisdomme
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final ThreadLocal<ConfigManager.InitializationBatch> private final Map<UltiToolsPlugin, Map<String, AbstractConfigEntity>> -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate voidaddConfigEntity(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) private voidbeginBatch(UltiToolsPlugin plugin) private StringbuildJsonFromConfigs(Function<AbstractConfigEntity, com.google.gson.JsonObject> extractor) Builds a JSON string from all config entities using the provided extractor function.private voidendBatch(boolean accepted) getAllConfigEntities(UltiToolsPlugin plugin) Get all config entities for a plugin.final StringGet all comments.<T extends AbstractConfigEntity>
List<T> getConfigEntities(UltiToolsPlugin plugin, Class<T> type) Get all config entities by type.<T extends AbstractConfigEntity>
TgetConfigEntity(UltiToolsPlugin plugin, Class<T> type) Get config entity.<T extends AbstractConfigEntity>
TgetConfigEntity(UltiToolsPlugin plugin, String path, Class<T> type) Get config entity by path.final voidloadFromJson(String json) Load config from JSON string.final voidloadFromJson(String configFilePath, String json) Load a single config file from a JSON string.static booleanpermitsConfigThread(UltiToolsPlugin plugin, String operation) Shared refusal guard, checked before any configuration monitor or registry access.private voidpersistPanelBatch(List<AbstractConfigEntity> touchedEntities, List<com.google.gson.JsonObject> touchedPayloads) private ConverterRegistryprepareConverters(UltiToolsPlugin plugin) voidRewrites the framework comment lines of every configuration ofpluginin the module's current language (#594), throughAbstractConfigEntity.refreshFrameworkComments(): a comment-only write through the config write gate, owning only the comment lines the framework identifies as its own, never a value, a key or another comment line.voidregister(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) Register config entity.voidregisterAll(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader) Registers every@ConfigEntityclass found across MULTIPLE scan packages for one plugin, as ONE atomic batch (CR-01, #358 Part 1 gate-1 finding).voidregisterAll(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader) Register all config entities in the specified package.private voidregisterAllImmediate(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader) private voidregisterAllPackages(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader) (package private) Set<UltiToolsPlugin> registeredOwners(Class<?> moduleClass) private voidregisterImmediate(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) voidreloadConfigs(UltiToolsPlugin plugin) Reload all configs.private voidreportUnsaved(Map<String, AbstractConfigEntity> configMap) voidReports, at server stop, every registered configuration that holds module changes never saved: one WARNING per configuration names its file and the changed entry keys, never a value, and says they are not written.private static voidrequireConfigThread(UltiToolsPlugin plugin, String operation) private voidrollBackRegisteredEntities(UltiToolsPlugin ultiToolsPlugin, Map<String, AbstractConfigEntity> entitiesToRestore) RestoresultiToolsPlugin's entry inpluginConfigMapto exactlyentitiesToRestore- not just the same key set, but the same path-to-entity VALUES, so a path this call replaced with a different entity before failing is restored to the entity that was actually there before the call started (seesnapshotRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin)'s javadoc for why key-only retention was insufficient).voidsaveAll()Deprecated.(package private) voidsaveForShutdown(UltiToolsPlugin plugin) private Map<String, AbstractConfigEntity> snapshotRegisteredEntities(UltiToolsPlugin ultiToolsPlugin) Snapshots the complete config-file-path-to-entity mappingultiToolsPluginalready holds inpluginConfigMap, before a caller is about to register a batch of entities in one call.final StringtoJson()Cast config to JSON format.voidunregisterAll(UltiToolsPlugin plugin) Releases one module's configuration entities after unload.unsavedPaths(UltiToolsPlugin plugin) private voidLogs, at server stop, that a configuration file was left alone because the framework could not parse it the last time it read it (#510), and that any in-memory change to that configuration was therefore not written.private voidwithPanelMonitors(List<AbstractConfigEntity> monitors, int index, List<AbstractConfigEntity> entities, List<com.google.gson.JsonObject> payloads)
-
Field Details
-
pluginConfigMap
-
initializationBatch
-
-
Constructor Details
-
ConfigManager
public ConfigManager()
-
-
Method Details
-
permitsConfigThread
Shared refusal guard, checked before any configuration monitor or registry access.- Parameters:
plugin- module, possibly absent for an uninitialized entityoperation- operation name or entity path- Returns:
- whether the current caller may perform the operation
-
requireConfigThread
-
beginBatch
-
endBatch
private void endBatch(boolean accepted) -
register
public void register(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) throws IOException Register config entity.- Parameters:
ultiToolsPlugin- UltiTools moduleconfigEntity- Config entity- Throws:
IOException
-
registerImmediate
private void registerImmediate(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) throws IOException - Throws:
IOException
-
prepareConverters
-
addConfigEntity
-
snapshotRegisteredEntities
private Map<String,AbstractConfigEntity> snapshotRegisteredEntities(UltiToolsPlugin ultiToolsPlugin) Snapshots the complete config-file-path-to-entity mappingultiToolsPluginalready holds inpluginConfigMap, before a caller is about to register a batch of entities in one call. Used byregister(UltiToolsPlugin, AbstractConfigEntity)'s directory branch,registerAll(UltiToolsPlugin, String, ClassLoader), andregisterAll(UltiToolsPlugin, String[], ClassLoader)to restore precisely what existed before this call, on a refusal, without touching anything a prior call already committed (#358 Part 1).Gate-1 review finding: an earlier version of this snapshot captured only the KEY set, and
rollBackRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.util.Map<java.lang.String, com.ultikits.ultitools.abstracts.AbstractConfigEntity>)retained those keys viaretainAllrather than restoring their values. That is correct only when a batch never replaces an already- registered path with a different entity instance before failing - but a supported flow (e.g.registerAllscanning a path a module also registered directly viagetAllConfigs()) can do exactly that, perConfigManagerDualPathRegistrationTest. Capturing entity VALUES, not just keys, is what lets a rollback actually restore the original entity rather than silently keeping whatever replaced it.- Parameters:
ultiToolsPlugin- the plugin whose current registrations to snapshot- Returns:
- a defensive copy of the currently-registered path-to-entity map, or an empty map if none
-
rollBackRegisteredEntities
private void rollBackRegisteredEntities(UltiToolsPlugin ultiToolsPlugin, Map<String, AbstractConfigEntity> entitiesToRestore) RestoresultiToolsPlugin's entry inpluginConfigMapto exactlyentitiesToRestore- not just the same key set, but the same path-to-entity VALUES, so a path this call replaced with a different entity before failing is restored to the entity that was actually there before the call started (seesnapshotRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin)'s javadoc for why key-only retention was insufficient). The cleanup half of #358 Part 1's all-or-nothing registration.- Parameters:
ultiToolsPlugin- the plugin to roll backentitiesToRestore- the path-to-entity map to restore, as captured before this call started
-
registerAll
Register all config entities in the specified package.- Parameters:
plugin- UltiTools modulepackageName- Package nameclassLoader- Class loader
-
registerAllImmediate
private void registerAllImmediate(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader) -
registerAll
Registers every@ConfigEntityclass found across MULTIPLE scan packages for one plugin, as ONE atomic batch (CR-01, #358 Part 1 gate-1 finding).UltiToolsPlugin.initConfig()callsregisterAll(UltiToolsPlugin, String, ClassLoader)once per surviving entry ofDependencyUtils.getPluginPackages(plugin)- and #362's de-duplication only collapses NESTED scan packages, so two unrelated sibling packages (declared via@ComponentScan(basePackages = {...})or@UltiToolsModule(scanBasePackages = {...})) both survive and are scanned in two SEPARATE calls. Each single-package call's own snapshot/rollback is correctly scoped to not disturb a PRIOR call's successful work - which means a refusal on the SECOND package left the FIRST package's already-committed entries stranded, because neither call's snapshot ever captured "before this plugin's whole scan", only "before this one call". This overload snapshots once, before any package inpackageNamesis scanned, and rolls back to that ONE snapshot if any package's scan refuses - so a plugin whose scan packages span more than one package registers all of them, or none.- Parameters:
plugin- UltiTools modulepackageNames- every package name to scan, in the orderDependencyUtils.getPluginPackagesreturns themclassLoader- Class loader
-
registerAllPackages
private void registerAllPackages(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader) -
getConfigEntity
Get config entity.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools moduletype- Config entity type- Returns:
- Config entity
-
getConfigEntity
public <T extends AbstractConfigEntity> T getConfigEntity(UltiToolsPlugin plugin, String path, Class<T> type) Get config entity by path.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools modulepath- Config entity pathtype- Config entity type- Returns:
- Config entity
-
getConfigEntities
public <T extends AbstractConfigEntity> List<T> getConfigEntities(UltiToolsPlugin plugin, Class<T> type) Get all config entities by type.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools moduletype- Config entity type- Returns:
- Config entity list
-
getAllConfigEntities
Get all config entities for a plugin.- Parameters:
plugin- UltiTools module- Returns:
- All config entities
-
unregisterAll
Releases one module's configuration entities after unload.- Parameters:
plugin- unloaded module instance
-
registeredOwners
-
unsavedPaths
-
reloadConfigs
Reload all configs.Since 6.3.0 (#589) a configuration that fails to reload is not logged and skipped: the failure propagates, so the module's reload reports it (
/ul reload <name>replies that the module did not reload, naming the file and the cause). A configuration whose file cannot be read or parsed throwsConfigurationExceptionfrom its ownreload(); anIOExceptionis rethrown as aConfigurationExceptionnaming the file. The first failure stops the remaining configurations of this module, as a validation failure always has; the failed one keeps its running values.- Parameters:
plugin- UltiTools module- Throws:
ConfigurationException- if a configuration fails to reload
-
refreshFrameworkComments
Rewrites the framework comment lines of every configuration ofpluginin the module's current language (#594), throughAbstractConfigEntity.refreshFrameworkComments(): a comment-only write through the config write gate, owning only the comment lines the framework identifies as its own, never a value, a key or another comment line. The module's reload calls this right after it rebuilds the module's language, which happens afterreloadConfigs(com.ultikits.ultitools.abstracts.UltiToolsPlugin)read the files, and before the module's own reload hook. One entity's failure is one warning naming its file and the failure's class, and the others still run.- Parameters:
plugin- UltiTools module- Since:
- 6.3.0
-
saveAll
Deprecated.as of 6.3.0 this writes nothing; persist changes withAbstractConfigEntity.save()orAbstractConfigEntity.saveOperatorChange(String...)when they are madeWrites nothing; reports, asreportUnsavedAtStop()does, every registered configuration that holds module changes never saved.Until 6.3.0 this saved every changed configuration at server stop. As of 6.3.0 nothing writes configuration at server stop, module unload or module replacement (maintainer decision of 2026-10-04, "what code may write, by file type": no shutdown save of whole entities), because a lifecycle event is not an operator's request to write and a whole-entity save at stop could write over what the operator changed on disk. A module persists a change when it makes it:
AbstractConfigEntity.save()for a change the operator asked for through the module (it writes only what the module changed, where the file still holds what was read), orAbstractConfigEntity.saveOperatorChange(String...)to write exactly the settings an operator's command names. -
reportUnsavedAtStop
@Internal public void reportUnsavedAtStop()Reports, at server stop, every registered configuration that holds module changes never saved: one WARNING per configuration names its file and the changed entry keys, never a value, and says they are not written. Nothing is written - no configuration file is touched at server stop (maintainer decision of 2026-10-04). A configuration whose file could not be read or parsed the last time it was loaded is named once instead, as before (#510).Framework-internal:
UltiTools#onDisable()calls it for configurations still registered after every module was unloaded; each module's own configurations are reported once, just before their release, during its unload at stop. Registry access runs on the server thread; one entity's failure is logged and the others are still reported.- Since:
- 6.3.0
-
saveForShutdown
-
reportUnsaved
-
warnUnparseableFileLeftAlone
Logs, at server stop, that a configuration file was left alone because the framework could not parse it the last time it read it (#510), and that any in-memory change to that configuration was therefore not written.- Parameters:
config- the entity that was skipped
-
buildJsonFromConfigs
private String buildJsonFromConfigs(Function<AbstractConfigEntity, com.google.gson.JsonObject> extractor) Builds a JSON string from all config entities using the provided extractor function.- Parameters:
extractor- function to extract JsonObject from config entity- Returns:
- JSON string
-
getComments
Get all comments.- Returns:
- all comments
-
toJson
Cast config to JSON format.- Returns:
- config in JSON format
-
loadFromJson
Load config from JSON string.Since 6.3.0, a value violating its
@Range/@NotEmpty/@Size/@Patternconstraint refuses withConfigurationExceptioninstead of being written - the operator's file is not modified for that config entity (SILENT-14).Every touched entity is validated before persistence. All changed files are staged before any replacement; effective and raw acknowledgments advance only after every commit succeeds. An I/O or preparation refusal restores attempted files and every entity checkpoint, discards temporaries, and rethrows the original failure. Recovery failures are attached as suppressed exceptions: persistently unavailable storage can prevent restoration and is not reported as success. This is in-process recovery, not crash-safe multi-file storage; a JVM crash between moves remains outside this guarantee (UltiKits/UltiTools-Reborn#545).
- Parameters:
json- JSON string- Throws:
IOException- if staging or replacement fails; recovery failures are suppressedConfigurationException- if a value violates its validation constraint - nothing in this call's batch is persisted when this is thrown, since validation runs to completion across the whole batch before persistence starts
-
withPanelMonitors
private void withPanelMonitors(List<AbstractConfigEntity> monitors, int index, List<AbstractConfigEntity> entities, List<com.google.gson.JsonObject> payloads) throws IOException - Throws:
IOException
-
persistPanelBatch
private void persistPanelBatch(List<AbstractConfigEntity> touchedEntities, List<com.google.gson.JsonObject> touchedPayloads) throws IOException - Throws:
IOException
-
loadFromJson
Load a single config file from a JSON string.loadFromJson(String)accepts the full nested structure{pluginName: {configPath: {key: value}}}-- the same shapetoJson()produces. This method accepts just the innermost layer -- one config file's own{key: value}map -- and writes it to whichever fileconfigFilePathnames. The panel uses this narrower shape when pushing a single config by filename; see issue #236.A config path is unique within a single plugin (it is the inner key of
pluginConfigMap), but may collide across plugins. Both "not found" and "matched more than one" throw rather than silently doing nothing -- "the caller thinks it wrote something and nothing actually happened" was exactly the original defect on this path.Since 6.3.0, a value violating its
@Range/@NotEmpty/@Size/@Patternconstraint refuses withConfigurationExceptioninstead of being written - the operator's file is not modified (SILENT-14).- Parameters:
configFilePath- config file path as registered, e.g.config/lang.ymljson- JSON object of that file's entries- Throws:
IOException- if the path is blank, unknown, ambiguous, or the JSON is not an object, or if saving failsConfigurationException- if a value violates its validation constraint- Since:
- 6.2.5
-
AbstractConfigEntity.save()orAbstractConfigEntity.saveOperatorChange(String...)when they are made