Class ConfigManager

java.lang.Object
com.ultikits.ultitools.manager.ConfigManager

public class ConfigManager extends Object
Version:
1.0.0
Author:
wisdomme
  • Field Details

  • Constructor Details

    • ConfigManager

      public ConfigManager()
  • Method Details

    • permitsConfigThread

      @Internal public static boolean permitsConfigThread(UltiToolsPlugin plugin, String operation)
      Shared refusal guard, checked before any configuration monitor or registry access.
      Parameters:
      plugin - module, possibly absent for an uninitialized entity
      operation - operation name or entity path
      Returns:
      whether the current caller may perform the operation
    • requireConfigThread

      private static void requireConfigThread(UltiToolsPlugin plugin, String operation)
    • beginBatch

      private void beginBatch(UltiToolsPlugin plugin)
    • endBatch

      private void endBatch(boolean accepted)
    • register

      public void register(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) throws IOException
      Register config entity.
      Parameters:
      ultiToolsPlugin - UltiTools module
      configEntity - Config entity
      Throws:
      IOException
    • registerImmediate

      private void registerImmediate(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) throws IOException
      Throws:
      IOException
    • prepareConverters

      private ConverterRegistry prepareConverters(UltiToolsPlugin plugin)
    • addConfigEntity

      private void addConfigEntity(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity)
    • snapshotRegisteredEntities

      private Map<String,AbstractConfigEntity> snapshotRegisteredEntities(UltiToolsPlugin ultiToolsPlugin)
      Snapshots the complete config-file-path-to-entity mapping ultiToolsPlugin already holds in pluginConfigMap, before a caller is about to register a batch of entities in one call. Used by register(UltiToolsPlugin, AbstractConfigEntity)'s directory branch, registerAll(UltiToolsPlugin, String, ClassLoader), and registerAll(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 via retainAll rather 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. registerAll scanning a path a module also registered directly via getAllConfigs()) can do exactly that, per ConfigManagerDualPathRegistrationTest. 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)
      Restores ultiToolsPlugin's entry in pluginConfigMap to exactly entitiesToRestore - 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 (see snapshotRegisteredEntities(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 back
      entitiesToRestore - the path-to-entity map to restore, as captured before this call started
    • registerAll

      public void registerAll(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader)
      Register all config entities in the specified package.
      Parameters:
      plugin - UltiTools module
      packageName - Package name
      classLoader - Class loader
    • registerAllImmediate

      private void registerAllImmediate(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader)
    • registerAll

      public void registerAll(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader)
      Registers every @ConfigEntity class found across MULTIPLE scan packages for one plugin, as ONE atomic batch (CR-01, #358 Part 1 gate-1 finding).

      UltiToolsPlugin.initConfig() calls registerAll(UltiToolsPlugin, String, ClassLoader) once per surviving entry of DependencyUtils.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 in packageNames is 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 module
      packageNames - every package name to scan, in the order DependencyUtils.getPluginPackages returns them
      classLoader - Class loader
    • registerAllPackages

      private void registerAllPackages(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader)
    • getConfigEntity

      public <T extends AbstractConfigEntity> T getConfigEntity(UltiToolsPlugin plugin, Class<T> type)
      Get config entity.
      Type Parameters:
      T - Config entity type
      Parameters:
      plugin - UltiTools module
      type - 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 module
      path - Config entity path
      type - 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 module
      type - Config entity type
      Returns:
      Config entity list
    • getAllConfigEntities

      public Map<String,AbstractConfigEntity> getAllConfigEntities(UltiToolsPlugin plugin)
      Get all config entities for a plugin.
      Parameters:
      plugin - UltiTools module
      Returns:
      All config entities
    • unregisterAll

      @Internal public void unregisterAll(UltiToolsPlugin plugin)
      Releases one module's configuration entities after unload.
      Parameters:
      plugin - unloaded module instance
    • registeredOwners

      Set<UltiToolsPlugin> registeredOwners(Class<?> moduleClass)
    • unsavedPaths

      List<String> unsavedPaths(UltiToolsPlugin plugin)
    • reloadConfigs

      public void reloadConfigs(UltiToolsPlugin plugin)
      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 throws ConfigurationException from its own reload(); an IOException is rethrown as a ConfigurationException naming 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

      @Internal public void refreshFrameworkComments(UltiToolsPlugin plugin)
      Rewrites the framework comment lines of every configuration of plugin in the module's current language (#594), through AbstractConfigEntity.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 after reloadConfigs(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(since="6.3.0") public void saveAll()
      Deprecated.
      as of 6.3.0 this writes nothing; persist changes with AbstractConfigEntity.save() or AbstractConfigEntity.saveOperatorChange(String...) when they are made
      Writes nothing; reports, as reportUnsavedAtStop() 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), or AbstractConfigEntity.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

      void saveForShutdown(UltiToolsPlugin plugin)
    • reportUnsaved

      private void reportUnsaved(Map<String,AbstractConfigEntity> configMap)
    • warnUnparseableFileLeftAlone

      private void warnUnparseableFileLeftAlone(AbstractConfigEntity config)
      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

      public final String getComments()
      Get all comments.
      Returns:
      all comments
    • toJson

      public final String toJson()
      Cast config to JSON format.
      Returns:
      config in JSON format
    • loadFromJson

      public final void loadFromJson(String json) throws IOException
      Load config from JSON string.

      Since 6.3.0, a value violating its @Range/@NotEmpty/@Size/ @Pattern constraint refuses with ConfigurationException instead 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 suppressed
      ConfigurationException - 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

      public final void loadFromJson(String configFilePath, String json) throws IOException
      Load a single config file from a JSON string.

      loadFromJson(String) accepts the full nested structure {pluginName: {configPath: {key: value}}} -- the same shape toJson() produces. This method accepts just the innermost layer -- one config file's own {key: value} map -- and writes it to whichever file configFilePath names. 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/ @Pattern constraint refuses with ConfigurationException instead of being written - the operator's file is not modified (SILENT-14).

      Parameters:
      configFilePath - config file path as registered, e.g. config/lang.yml
      json - 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 fails
      ConfigurationException - if a value violates its validation constraint
      Since:
      6.2.5