This effort is nearly complete, and a little more testing is needed before I can commit my work. Below are some details on this update:
Added an execution context (ViewContext) for the purpose of separating structural metadata related to component definition from contextual metadata used during the lifecycle. To start with, the view context provides access to the following details:
- Original view, unmodified from the cached state
- Mutable view, in the process of being prepared for rendering
- View helper service instance
- View lifecycle event listener
The view context has two methods for encapsulating processing within the context:
- encapsulateInitialization: Used to encapsulate processes that build initial component state. Initialization routines may be encapsulated within other encapsulated operations, and will replace the context during encapsulation. Once the initialization routing has completed, the former context will be returned to the thread.
- encapsulateLifecycle: Used to encapsulate processes that prepare a view for rendering. Only one lifecycle routine may be encapsulated at a time.
Added immutability to ComponentBase, LayoutManagerBase, View, and several related classes. Collections and maps have been converted to unmodifiable with internal modifiers, and setters, copy methods, and complex operations that modify component state have all been safeguarded. Attempts to modify component state outside of an encapsulated view context result in IllegalStateException. The purpose of immutability is to reduce the need for copying components during and after the lifecycle - existing code that runs into an immutability check can be refactored to either remove behavior that modifies the component, or to move that behavior to part of the lifecycle.
During an encapsulateInitialization call, component setters may be called, and collections related to components will be externally modifiable. However, performIntitialization, performApplyModel, and performFinalize methods will result in IllegalStateException. Copy calls within encapsulateInitialization will result in an immutable component. Once the initialization routine has completed, all components created and/or modified during the routine will revert to an immutable state. The routines in DataDictionary and ComponentFactory that interact with Spring have been encapsulated.
During an encapsulateLifecycle call, components may be modified as described above for encapsualteInitialization, but only if the component was copied within the context of the encapsulated call. The performInitialization, performApplyModel, and performFinalize are available within an encapsulateLifecycle call.
The purpose of this safeguarding and encapsulation is to move control over component behavior from the application's default execution context to an execution context specifically defined for rendering the view.
To better isolate the view lifecycle from other processing, I have made the following adjustments:
- Split ViewHelperService into three separate interfaces:
- ViewHelperService, including methods only related to lifecycle processing.
- CollectionViewHelperService, extends ViewHelperService to define methods related to collection handling unrelated to the lifecycle
- InquiryViewHelperService, extends ViewHelperService to define methods related to inquriy handler unrelated to the lifecycle
- Split ViewHelperServiceImpl similarly:
- ViewHelperServiceBase, implementing only the methods related to lifecycle processing
- ViewHelperServiceImpl, implmeneting the CollectionViewHelperService and InquiryViewHelperService interfaces
- Removed the view reference from all argument lists related to lifecycle processing, in ViewHelperService, Component, and LayoutManager. Implementations have been updated to refer to the view from the context rather than from the argument list.
The steps above serve to isolate view lifecycle processing into a clean execution context, and to separate code related to the lifecycle from unrelated code. The safeguards primary serve to facilitate refactoring, and to prevent new code from modifying components outside of the lifecycle. The last remaining TODO for this request is to reorder final lifecycle processing as bottom-up, marking components as immutable as they finalized. performInitialization will remain top-down, but will be modified to create a bottom-up traversal stack for performApplyModel and performFinalize. The final two phases will be converted to per-component rather than performed on the full view. The basis for this final work is in the POC on
Roughly 2h remains to clean up the modifications noted above, perform final TODOs, and commit. I should be able to finish this request and perform regression testing this evening.