Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package io.kform.internal.actions
import io.kform.*
import io.kform.internal.*
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.launch
/** Action writing values and their state. */
internal abstract class WriteValueStateAction(formManager: FormManager) :
ValueStateAction(formManager) {
abstract val parentPath: AbsolutePath?
abstract val fragment: AbsolutePathFragment?
override val accesses =
listOf(
AccessValueStateTree(ActionAccessType.Write),
AccessValidationState(ActionAccessType.Write),
AccessStatefulValidationDeferredState(ActionAccessType.Read),
AccessIsTouched(ActionAccessType.Read),
AccessDescendantsDisplayingIssues(ActionAccessType.Read)
)
override val accessedPaths
get() = listOf(accessedPath)
/** Path representing the resources that this action will access. */
abstract val accessedPath: AbsolutePath
val path
get() = if (parentPath != null) parentPath!! + fragment!! else AbsolutePath.ROOT
override fun overridesConflictingAction(action: Action<*>): Boolean =
(action is SetAction &&
fragment !is AbsolutePathFragment.CollectionEnd &&
(path + AbsolutePathFragment.RecursiveWildcard).contains(action.path)) ||
(action is RemoveAction &&
(path + AbsolutePathFragment.RecursiveWildcard).contains(action.parentPath))
/**
* Runs [fn] with an events bus to be passed to schemas, where each event emitted to it is
* appropriately handled. This is used by [SetAction] and [RemoveAction] to handle events
* emitted by schemas. Returns the result of [fn] after all events have been handled.
*/
protected suspend fun withSchemaEventsBus(
fn: suspend (eventsChannel: SchemaEventsBus) -> TResult
): TResult = fn(SchemaEventsChannelBus { event -> handleSchemaEvent(event) })
/** Handles an event emitted by a schema. */
private suspend fun handleSchemaEvent(event: ValueEvent<*>) {
FormManager.logger.trace { "Handling schema event: $event" }
@Suppress("UNCHECKED_CAST")
when (event) {
is ValueEvent.Init<*> -> handleSchemaInitEvent(event as ValueEvent.Init)
is ValueEvent.Change<*> -> handleSchemaChangeEvent(event as ValueEvent.Change)
is ValueEvent.Destroy<*> -> handleSchemaDestroyAction(event as ValueEvent.Destroy)
is ValueEvent.Add<*, *> -> handleSchemaAddAction(event as ValueEvent.Add)
is ValueEvent.Remove<*, *> ->
handleSchemaRemoveAction(event as ValueEvent.Remove)
}
}
/**
* Returns the state of the value with the provided [path] and [schema], creating it and its
* parent states when necessary.
*/
private fun initState(path: AbsolutePath, schema: Schema<*>): StateImpl {
var parentState: ParentState? = null
var fragment: AbsolutePathFragment.Id? = null
var state =
if (path == AbsolutePath.ROOT) formManager.formState
else {
parentState =
path.parent().let {
initState(it, schemaInfo(it).single().schema) as ParentState
}
fragment = path.lastFragment as AbsolutePathFragment.Id
parentState.childrenStates(path, fragment).singleOrNull()?.state
}
as StateImpl?
if (state == null) {
val nValidations = schema.validations.size
val nStatefulValidations =
schema.validations.count { validation -> validation is StatefulValidation<*, *> }
state =
when (schema) {
is CollectionSchema<*, *> ->
CollectionStateImpl(
schema.childrenStatesContainer(),
nValidations,
nStatefulValidations
)
is ParentSchema<*> ->
ParentStateImpl(
schema.childrenStatesContainer(),
nValidations,
nStatefulValidations
)
else -> StateImpl(nValidations, nStatefulValidations)
}
if (path == AbsolutePath.ROOT) {
formManager.formState = state
} else {
parentState!!.setState(fragment!!, state)
}
}
return state
}
/**
* Destroys the state of the value with the provided [path] and all children states. This
* function **can** be called with the path of a value whose state has already been destroyed.
*/
private suspend fun destroyState(path: AbsolutePath) {
// Keep track of how many values were destroyed that were displaying local errors/warnings
var destroyedDisplayingLocalError = 0
var destroyedDisplayingLocalWarning = 0
for (info in stateInfo(path + AbsolutePathFragment.RecursiveWildcard)) {
val state = info.state as StateImpl? ?: continue
when (state.localDisplayStatus()) {
DisplayStatus.Error -> ++destroyedDisplayingLocalError
DisplayStatus.Warning -> ++destroyedDisplayingLocalWarning
else -> {}
}
// Destroy state (cancels deferred validation states)
state.destroy()
// Destroy stateful validation states (if the validation state was in the process of
// being updated, then the above `state.destroy()` will cause the validation state to be
// destroyed by the update state action, otherwise we destroy it here)
var statefulValidationIndex = 0
for (validation in info.schema.validations) {
if (validation is StatefulValidation<*, *>) {
val deferredState =
state.getStatefulValidationDeferredState(statefulValidationIndex++)
?: continue
@Suppress("UNCHECKED_CAST") (validation as StatefulValidation)
formManager.scope.launch(CoroutineName("Destroy stateful validation state")) {
try {
// This can throw due to the above `cancel` or due to an error while
// updating the state, in which case we don't need to do anything as
// explained above; otherwise, we destroy the validation state
val validationState = deferredState.await()
FormManager.logger.trace {
"At '$path': Destroying validation '$validation' state: " +
"$validationState"
}
validation.destroyState(validationState)
} catch (_: Throwable) {}
}
}
}
}
// Update "descendants displaying issues" of ancestors
if (
(destroyedDisplayingLocalError != 0 || destroyedDisplayingLocalWarning != 0) &&
path != AbsolutePath.ROOT
) {
formManager.scheduleAction(
UpdateDescendantsDisplayingIssuesAction(
formManager,
path.parent(),
-destroyedDisplayingLocalError,
-destroyedDisplayingLocalWarning
)
)
}
}
/** Removes all cached/external issues at [path]. */
private suspend fun invalidateLocalValidations(path: AbsolutePath) {
val (state, schema) = stateInfo(path).single()
state as StateImpl
val wasValidated =
state.validationStatus == ValidationStatus.Validated ||
state.validationStatus == ValidationStatus.ValidatedExceptionally
val oldLocalDisplayStatus = state.localDisplayStatus()
val oldDisplayStatus = state.displayStatus()
state.removeCachedIssues()
val removedExternalIssues = state.removeExternalIssues()
if (removedExternalIssues.isNotEmpty()) {
formManager.externalIssuesDependencies.removeDependenciesOfExternalIssues(
removedExternalIssues
)
}
// Update validation state as needed
if (wasValidated) {
state.validationStatus = ValidationStatus.Unvalidated
}
ValidateAction.updateValidationState(
formManager,
schema,
path,
state,
oldLocalDisplayStatus,
oldDisplayStatus,
emitValidationChange = wasValidated || removedExternalIssues.isNotEmpty()
)
}
/** Invalidates all validations that depend on the value at [path]. */
private suspend fun invalidateDependingValidations(path: AbsolutePath) {
for ((dependingPath, validation, validationIndex) in formManager.pathDependencies[path]) {
val resolvedDependingPath = path.resolve(dependingPath)
formManager.scheduleAction(
InvalidateValidationAction(
formManager,
resolvedDependingPath,
validation,
validationIndex
)
)
}
}
/** Removes all external issues depending on (but not set on) [path]. */
private suspend fun removeDependingExternalIssues(path: AbsolutePath) {
val dependingExternalIssues =
formManager.externalIssuesDependencies.getAndRemoveExternalIssuesDependentOnPath(path)
if (dependingExternalIssues.isNotEmpty()) {
formManager.scheduleAction(
RemoveDependingExternalIssuesAction(formManager, dependingExternalIssues)
)
}
}
/** Updates the state of all stateful validations observing the given [path]. */
private suspend fun updateObservingStatefulValidationsStates(event: ValueEvent<*>) {
// If the event's path matches against multiple paths in the `toObserve` list of a
// validation, we mustn't update the state of said validation multiple times; as such, we
// keep track of the validations we have already updated
val updatedValidations = mutableSetOf>()
for ((
observingPath, validation, validationIndex, statefulValidationIndex, toObserveIndex) in
formManager.observedPathDependencies[event.path]) {
val resolvedObservingPath = event.path.resolve(observingPath)
val validationInfo = Pair(resolvedObservingPath, validationIndex)
// Mustn't update state with `init`/`destroy` events of the value being validated; also
// mustn't update already updated state
if (
(event.path != resolvedObservingPath ||
(event !is ValueEvent.Init && event !is ValueEvent.Destroy)) &&
!updatedValidations.contains(validationInfo)
) {
@Suppress("UNCHECKED_CAST")
formManager.scheduleAction(
UpdateStatefulValidationsStateAction(
formManager,
resolvedObservingPath,
validation as StatefulValidation,
validationIndex,
statefulValidationIndex,
toObserveIndex,
event as ValueEvent
)
)
updatedValidations += validationInfo
}
}
}
/** Handles an "init [event]" emitted by a schema. */
private suspend fun handleSchemaInitEvent(event: ValueEvent.Init) {
// Initialise state and possibly parent states
initState(event.path, event.schema)
invalidateDependingValidations(event.path)
updateObservingStatefulValidationsStates(event)
removeDependingExternalIssues(event.path)
formManager.eventsBus.emit(event)
}
/** Handles a "change [event]" emitted by a schema. */
private suspend fun handleSchemaChangeEvent(event: ValueEvent.Change) {
invalidateLocalValidations(event.path)
invalidateDependingValidations(event.path)
updateObservingStatefulValidationsStates(event)
removeDependingExternalIssues(event.path)
formManager.eventsBus.emit(event)
}
/** Handles a "destroy [event]" emitted by a schema. */
private suspend fun handleSchemaDestroyAction(event: ValueEvent.Destroy) {
// Destroy state of destroyed value (state may already have been destroyed by a remove)
destroyState(event.path)
// Set state to `null` in parent state when the child state exists
val parentState = stateInfo(event.path.parent()).singleOrNull()?.state as ParentState?
val id = event.path.lastFragment as AbsolutePathFragment.Id
if (parentState != null && (parentState !is CollectionState || parentState.hasState(id))) {
parentState.setState(id, null)
}
invalidateDependingValidations(event.path)
updateObservingStatefulValidationsStates(event)
removeDependingExternalIssues(event.path)
formManager.eventsBus.emit(event)
}
/** Handles an "add [event]" emitted by a schema. */
private suspend fun handleSchemaAddAction(event: ValueEvent.Add) {
invalidateLocalValidations(event.path)
invalidateDependingValidations(event.path)
updateObservingStatefulValidationsStates(event)
removeDependingExternalIssues(event.path)
formManager.eventsBus.emit(event)
}
/** Handles a "remove [event]" emitted by a schema. */
private suspend fun handleSchemaRemoveAction(event: ValueEvent.Remove) {
// Destroy state of removed value and remove its state from parent state
val parentState = stateInfo(event.path).single().state as CollectionState
destroyState(event.path + event.id)
parentState.removeState(event.id)
invalidateLocalValidations(event.path)
invalidateDependingValidations(event.path)
updateObservingStatefulValidationsStates(event)
removeDependingExternalIssues(event.path)
formManager.eventsBus.emit(event)
}
}