Class ConditionalRegistrationEvaluator
@ConditionalOnConfig registration decision (D-17).
ComponentScanner.shouldRegister(Class) is the only caller of this evaluator in
src/main -- recording the decision here therefore catches every path the annotation
is honoured on, without needing a second copy of the decision anywhere else.
Reload drift reporting (issue #392, D-01). @ConditionalOnConfig is evaluated
exactly once, during component scanning at plugin startup. ul reload re-reads the
config file but this framework deliberately does not rebuild the container or
re-register/unregister the component on reload -- see D-01 in the issue for the rejected
"unregister and re-register the module" alternative and why it was rejected (it would discard
the whole module's in-memory state, turning a config reload into a de-facto restart while
players are online). Instead, shouldRegister(Class, SimpleContainer) records every
decision it makes, and reportDrift(UltiToolsPlugin) -- called from
UltiToolsPlugin.reloadSelf() -- re-evaluates each recorded class and logs a
Level.WARNING for any class whose answer has changed, naming the class, the config
file, the key, the new direction, and that a restart is required. This turns a previously
silent no-op into a visible one without changing what reload actually does.
Where the record lives, and why. The record is a private static map keyed by the
owning UltiToolsPlugin instance (identity, not equality -- a plugin has no
value-based equals()), because: (a) the decision is inherently per-plugin, since the
same class annotated on two different plugins would resolve against two different resource
folders; (b) it is released by an explicit clear(UltiToolsPlugin) call from
PluginManager.unregister, the same place this codebase already releases the other
module-scoped registries that would otherwise pin the module's ClassLoader after unload
(TabCompletionManager, EventBus, PanelResponderRegistry) -- the record
holds Class<?> objects, so leaving it behind would pin the loader exactly the same
way; (c) it is safe for the framework's own core context, which has no UltiToolsPlugin
bean at all, because evaluation exits down the existing fail-open branch before anything is
recorded, and a class carrying no @ConditionalOnConfig returns true before
that. Rejected alternative: storing the record as a field on UltiToolsPlugin would
make garbage collection automatic (no clear() call needed), but it would widen a
public abstract class's API surface for an ApiStatus.Internal bookkeeping concern that
has nothing to do with what a module author subclasses UltiToolsPlugin for.
- Since:
- 6.3.0
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Loggerprivate static final ObjectGuards every read, write, and iteration ofSTARTUP_DECISIONS.private static final Map<UltiToolsPlugin, Map<Class<?>, Boolean>> Every scan-time@ConditionalOnConfigdecision, keyed by owning plugin instance (identity) then by the evaluated class. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static StringadviceFor(boolean actuallyPresent, boolean currentDecision) D-04: the action advice follows the observed(actuallyPresent, currentDecision)pair instead of a single fixed template -- the old unconditional "a restart is required" sentence sent an operator to restart the server in order to remove a component that was never actually there.static voidclear(UltiToolsPlugin plugin) Release every decision recorded forplugin.private static StringdriftMessage(Class<?> clazz, ConditionalOnConfig condition, boolean currentDecision, boolean actuallyPresent) private static booleanevaluate(UltiToolsPlugin plugin, ConditionalOnConfig condition) The decision logic extracted verbatim from the pre-6.3.0shouldRegisterbody -- no existing decision semantics changed by this extraction.private static booleanisActuallyRegistered(SimpleContainer context, Class<?> clazz) Whetherclazzis actually present incontextright now -- a real, constructed singleton, not merely a registeredBeanDefinition(D-03).private static voidrecord(UltiToolsPlugin plugin, Class<?> clazz, boolean decision) reportDrift(UltiToolsPlugin plugin) Re-evaluate every@ConditionalOnConfigdecision recorded forpluginand log (and return) oneLevel.WARNINGmessage for each class whose answer has changed since it was recorded (issue #392, D-01).static booleanshouldRegister(Class<?> clazz, SimpleContainer container) Check if a class should be registered based on@ConditionalOnConfig.
-
Field Details
-
LOGGER
-
RECORD_LOCK
Guards every read, write, and iteration ofSTARTUP_DECISIONS. -
STARTUP_DECISIONS
Every scan-time@ConditionalOnConfigdecision, keyed by owning plugin instance (identity) then by the evaluated class. The inner map is aLinkedHashMapsoreportDrift(UltiToolsPlugin)reports drift in a deterministic order.
-
-
Constructor Details
-
ConditionalRegistrationEvaluator
private ConditionalRegistrationEvaluator()
-
-
Method Details
-
shouldRegister
Check if a class should be registered based on@ConditionalOnConfig.The two "the decision cannot be determined" branches stay fail-open -- a component that should have been enabled but was not is harder to diagnose than the reverse -- but each now emits a
Level.WARNINGrecord naming the evaluated class and the reason, rather than silently registering by default (D-20). The config-file-missing branch is unaffected: it returnscondition.negate(), i.e. missing means disabled, which already matches the annotation's own javadoc.The decision made here -- including a
false(skipped) decision, which never appears in any container -- is recorded against the resolved plugin so a laterreportDrift(UltiToolsPlugin)call can detect drift (issue #392, D-01).- Parameters:
clazz- the class to checkcontainer- the container used to resolve the owningUltiToolsPlugin- Returns:
- true if the class should be registered
-
evaluate
The decision logic extracted verbatim from the pre-6.3.0shouldRegisterbody -- no existing decision semantics changed by this extraction. Kept separate fromshouldRegister(Class, SimpleContainer)soreportDrift(UltiToolsPlugin)can re-run exactly the same logic against the current file contents.- Parameters:
plugin- the resolved, non-null owning plugincondition- the class's@ConditionalOnConfigannotation- Returns:
- true if the condition currently evaluates to "register"
-
record
-
reportDrift
Re-evaluate every@ConditionalOnConfigdecision recorded forpluginand log (and return) oneLevel.WARNINGmessage for each class whose answer has changed since it was recorded (issue #392, D-01).Per D-01, this method only reports -- it never registers, unregisters, or rebuilds anything. Re-evaluation runs outside
RECORD_LOCK(it does disk I/O viaYamlConfiguration.loadConfiguration(File), which must never run while holding a lock other reload/scan threads might contend on); the plugin's recorded map is snapshotted inside the lock first. Each class is re-evaluated in its owntry/catchso one unreadable config file cannot abort the reload or the remaining classes.- Parameters:
plugin- the plugin being reloaded- Returns:
- an unmodifiable list of the drift messages emitted, in recording order; empty if
there was no drift, if
pluginisnull, or if nothing is recorded forplugin
-
isActuallyRegistered
Whetherclazzis actually present incontextright now -- a real, constructed singleton, not merely a registeredBeanDefinition(D-03).Deliberately does not use the container's membership-predicate method: that method ORs a bean definition in with an actual singleton, and using it here would repeat the exact untrue claim this method exists to avoid. The composition used instead --
SimpleContainer.getBeanNamesForType(Class)(does not instantiate) followed bySimpleContainer.getSingleton(String, boolean)(Level 1 cache read only, no early reference) -- distinguishes a registered definition from a constructed instance.- Parameters:
context- the plugin's container, ornullif none is wired yetclazz- the class to check- Returns:
trueif a constructed singleton assignable toclazzexists
-
driftMessage
private static String driftMessage(Class<?> clazz, ConditionalOnConfig condition, boolean currentDecision, boolean actuallyPresent) -
adviceFor
D-04: the action advice follows the observed(actuallyPresent, currentDecision)pair instead of a single fixed template -- the old unconditional "a restart is required" sentence sent an operator to restart the server in order to remove a component that was never actually there.Four branches, three from D-04's own decision and a fourth extension it does not enumerate:
- present, now disabled -> a restart is required to remove it
- absent, now disabled -> no restart needed; if the operator expected it to be present, the cause is at startup, so the startup log is where to look
- absent, now enabled -> a restart is required to create it
- present, now enabled (D-04 does not name this pair; reachable when a bean is
registered through a non-scanner path such as
registerSingleton/@Beanafter the scan-time decision was recorded) -> applying D-04's own principle, the observed state already matches this decision, so no restart is needed
- Parameters:
actuallyPresent- whether a constructed singleton is observed in the container nowcurrentDecision- whether the condition currently evaluates to "register"- Returns:
- the advice sentence, ending the overall drift message
-
clear
Release every decision recorded forplugin.Called from
PluginManager.unregisterwhen a module unloads -- the record holdsClass<?>references, and would otherwise pin the module's ClassLoader after unload, exactly like theTabCompletionManager/EventBus/PanelResponderRegistryreleases it sits alongside there.- Parameters:
plugin- the plugin whose recorded decisions should be released;nullis a no-op, as is a plugin with nothing recorded
-