Compare commits

...

8 Commits

13 changed files with 167 additions and 36 deletions

View File

@@ -1,5 +1,19 @@
# Changelog # Changelog
## 0.24.4
* `Repos`:
* `Exposed`:
* Improve `CommonExposedRepo.selectByIds`
* `FSM`:
* Fixes and improvements
## 0.24.3
* `Ksp`:
* `Sealed`:
* Fixes in processing of `GenerateSealedTypesWorkaround` annotations
## 0.24.2 ## 0.24.2
* `Versions`: * `Versions`:

View File

@@ -12,6 +12,7 @@ kotlin {
dependencies { dependencies {
api project(":micro_utils.common") api project(":micro_utils.common")
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
api libs.kslog
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import dev.inmo.kslog.common.TagLogger
import dev.inmo.kslog.common.e
import dev.inmo.micro_utils.common.Optional import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
@@ -68,6 +70,7 @@ open class DefaultStatesMachine <T: State>(
protected val handlers: List<CheckableHandlerHolder<in T, T>>, protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler() protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : StatesMachine<T> { ) : StatesMachine<T> {
protected val logger = TagLogger(this::class.simpleName!!)
/** /**
* Will call [launchStateHandling] for state handling * Will call [launchStateHandling] for state handling
*/ */
@@ -96,7 +99,13 @@ open class DefaultStatesMachine <T: State>(
statesJobsMutex.withLock { statesJobsMutex.withLock {
statesJobs[actualState] ?.cancel() statesJobs[actualState] ?.cancel()
statesJobs[actualState] = scope.launch { statesJobs[actualState] = scope.launch {
performUpdate(actualState) runCatching {
performUpdate(actualState)
}.onFailure {
logger.e(it) {
"Unable to perform update of state from $actualState"
}
}.getOrThrow()
}.also { job -> }.also { job ->
job.invokeOnCompletion { _ -> job.invokeOnCompletion { _ ->
scope.launch { scope.launch {

View File

@@ -9,7 +9,12 @@ interface StatesManager<T : State> {
/** /**
* Must set current set using [State.context] * It is expected, that [new] state will be saved in manager.
*
* If [new] context will not be equal to [old] one, it must do some check of availability for replacement
* of potentially exists state on [new] context. If this state can't be replaced, it will throw [IllegalStateException]
*
* @throws IllegalStateException - in case when [new] [State] can't be set
*/ */
suspend fun update(old: T, new: T) suspend fun update(old: T, new: T)

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import dev.inmo.kslog.common.e
import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
@@ -44,7 +45,13 @@ open class DefaultUpdatableStatesMachine<T : State>(
val job = previousState.mapOnPresented { val job = previousState.mapOnPresented {
statesJobs.remove(it) statesJobs.remove(it)
} ?.takeIf { it.isActive } ?: scope.launch { } ?.takeIf { it.isActive } ?: scope.launch {
performUpdate(actualState) runCatching {
performUpdate(actualState)
}.onFailure {
logger.e(it) {
"Unable to perform update of state up to $actualState"
}
}.getOrThrow()
}.also { job -> }.also { job ->
job.invokeOnCompletion { _ -> job.invokeOnCompletion { _ ->
scope.launch { scope.launch {

View File

@@ -1,10 +1,11 @@
package dev.inmo.micro_utils.fsm.common.managers package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager import dev.inmo.micro_utils.fsm.common.StatesManager
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/** /**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager] * Implement this repo if you want to use some custom repo for [DefaultStatesManager]
@@ -19,6 +20,14 @@ interface DefaultStatesManagerRepo<T : State> {
* NOT be removed * NOT be removed
*/ */
suspend fun removeState(state: T) suspend fun removeState(state: T)
/**
* Semantically, calls [removeState] and then [set]
*/
suspend fun removeAndSet(toRemove: T, toSet: T) {
removeState(toRemove)
set(toSet)
}
/** /**
* @return Current list of available and saved states * @return Current list of available and saved states
*/ */
@@ -58,7 +67,7 @@ open class DefaultStatesManager<T : State>(
protected val _onEndChain = MutableSharedFlow<T>(0) protected val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow() override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
protected val mapMutex = Mutex() protected val internalLocker = SmartRWLocker()
constructor( constructor(
repo: DefaultStatesManagerRepo<T>, repo: DefaultStatesManagerRepo<T>,
@@ -68,28 +77,30 @@ open class DefaultStatesManager<T : State>(
onUpdateContextsConflictResolver = onContextsConflictResolver onUpdateContextsConflictResolver = onContextsConflictResolver
) )
override suspend fun update(old: T, new: T) = mapMutex.withLock { override suspend fun update(old: T, new: T) = internalLocker.withWriteLock {
val stateByOldContext: T? = repo.getContextState(old.context) val stateByOldContext: T? = repo.getContextState(old.context)
when { when {
stateByOldContext != old -> return@withLock stateByOldContext != old -> return@withWriteLock
stateByOldContext == null || old.context == new.context -> { old.context == new.context -> {
repo.removeState(old) repo.removeAndSet(old, new)
repo.set(new)
_onChainStateUpdated.emit(old to new) _onChainStateUpdated.emit(old to new)
} }
else -> { old.context != new.context -> {
val stateOnNewOneContext = repo.getContextState(new.context) val stateOnNewOneContext = repo.getContextState(new.context)
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) { if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
stateOnNewOneContext ?.let { endChainWithoutLock(it) } stateOnNewOneContext ?.let { endChainWithoutLock(it) }
repo.removeState(old) repo.removeAndSet(old, new)
repo.set(new)
_onChainStateUpdated.emit(old to new) _onChainStateUpdated.emit(old to new)
} else {
error(
"Unable to update state from $old to $new due to false answer from $onUpdateContextsConflictResolver and state on old context $stateOnNewOneContext"
)
} }
} }
} }
} }
override suspend fun startChain(state: T) = mapMutex.withLock { override suspend fun startChain(state: T) = internalLocker.withWriteLock {
val stateOnContext = repo.getContextState(state.context) val stateOnContext = repo.getContextState(state.context)
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) { if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
stateOnContext ?.let { stateOnContext ?.let {
@@ -108,11 +119,13 @@ open class DefaultStatesManager<T : State>(
} }
override suspend fun endChain(state: T) { override suspend fun endChain(state: T) {
mapMutex.withLock { internalLocker.withWriteLock {
endChainWithoutLock(state) endChainWithoutLock(state)
} }
} }
override suspend fun getActiveStates(): List<T> = repo.getStates() override suspend fun getActiveStates(): List<T> = internalLocker.withReadAcquire {
repo.getStates()
}
} }

View File

@@ -1,25 +1,59 @@
package dev.inmo.micro_utils.fsm.repos.common package dev.inmo.micro_utils.fsm.repos.common
import dev.inmo.kslog.common.TagLogger
import dev.inmo.kslog.common.i
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.pagination.getAll
import dev.inmo.micro_utils.repos.unset
class KeyValueBasedDefaultStatesManagerRepo<T : State>( class KeyValueBasedDefaultStatesManagerRepo<T : State>(
private val keyValueRepo: KeyValueRepo<Any, T> private val keyValueRepo: KeyValueRepo<Any, T>
) : DefaultStatesManagerRepo<T> { ) : DefaultStatesManagerRepo<T> {
private val locker = SmartRWLocker()
private val logger = TagLogger("KeyValueBasedDefaultStatesManagerRepo")
override suspend fun set(state: T) { override suspend fun set(state: T) {
keyValueRepo.set(state.context, state) locker.withWriteLock {
} keyValueRepo.set(state.context, state)
logger.i { "Set ${state.context} value to $state" }
override suspend fun removeState(state: T) {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
} }
} }
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second } override suspend fun removeState(state: T) {
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context) locker.withWriteLock {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
logger.i { "Unset $state" }
}
}
}
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) override suspend fun removeAndSet(toRemove: T, toSet: T) {
locker.withWriteLock {
when {
toRemove.context == toSet.context -> {
keyValueRepo.set(toSet.context, toSet)
}
else -> {
keyValueRepo.set(toSet.context, toSet)
keyValueRepo.unset(toRemove)
}
}
}
}
override suspend fun getStates(): List<T> = locker.withReadAcquire {
keyValueRepo.getAll { keys(it) }.map { it.second }
}
override suspend fun getContextState(context: Any): T? = locker.withReadAcquire {
keyValueRepo.get(context)
}
override suspend fun contains(context: Any): Boolean = locker.withReadAcquire {
keyValueRepo.contains(context)
}
} }

View File

@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.24.2 version=0.24.4
android_code_version=281 android_code_version=283

View File

@@ -116,9 +116,9 @@ class Processor(
allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false
).distinct() ).distinct()
val subClassesNames = subClasses.filter { val subClassesNames = subClasses.filter {
it.getAnnotationsByType(GenerateSealedWorkaround.Exclude::class).count() == 0 it.getAnnotationsByType(GenerateSealedTypesWorkaround.Exclude::class).count() == 0
}.sortedBy { }.sortedBy {
(it.getAnnotationsByType(GenerateSealedWorkaround.Order::class).firstOrNull()) ?.order ?: 0 (it.getAnnotationsByType(GenerateSealedTypesWorkaround.Order::class).firstOrNull()) ?.order ?: 0
}.map { }.map {
it.toClassName() it.toClassName()
}.toList() }.toList()

View File

@@ -7,10 +7,10 @@ import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
@GenerateSealedTypesWorkaround @GenerateSealedTypesWorkaround
sealed interface Test { sealed interface Test {
@GenerateSealedWorkaround.Order(2) @GenerateSealedWorkaround.Order(2)
@GenerateSealedTypesWorkaround.Order(2) @GenerateSealedTypesWorkaround.Exclude
object A : Test object A : Test
@GenerateSealedWorkaround.Exclude @GenerateSealedWorkaround.Exclude
@GenerateSealedTypesWorkaround.Exclude @GenerateSealedTypesWorkaround.Order(2)
object B : Test object B : Test
@GenerateSealedWorkaround.Order(0) @GenerateSealedWorkaround.Order(0)
@GenerateSealedTypesWorkaround.Order(0) @GenerateSealedTypesWorkaround.Order(0)

View File

@@ -7,6 +7,6 @@ import kotlin.collections.Set
import kotlin.reflect.KClass import kotlin.reflect.KClass
private val subtypes: Set<KClass<out Test>> = setOf(Test.C::class, private val subtypes: Set<KClass<out Test>> = setOf(Test.C::class,
Test.A::class) Test.B::class)
public fun Test.Companion.subtypes(): Set<KClass<out Test>> = subtypes public fun Test.Companion.subtypes(): Set<KClass<out Test>> = subtypes

View File

@@ -0,0 +1,40 @@
import dev.inmo.micro_utils.ksp.sealed.generator.test.subtypes
import dev.inmo.micro_utils.ksp.sealed.generator.test.values
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class TestTests {
@Test
fun testThatAfterCompilationTestWorkaroundsHaveCorrectValues() {
val correctValues = arrayOf(
dev.inmo.micro_utils.ksp.sealed.generator.test.Test.C,
dev.inmo.micro_utils.ksp.sealed.generator.test.Test.A,
)
val correctSubtypes = arrayOf(
dev.inmo.micro_utils.ksp.sealed.generator.test.Test.C::class,
dev.inmo.micro_utils.ksp.sealed.generator.test.Test.B::class,
)
assertEquals(
correctValues.size, dev.inmo.micro_utils.ksp.sealed.generator.test.Test.values().size
)
correctValues.forEachIndexed { index, value ->
assertTrue(
value === dev.inmo.micro_utils.ksp.sealed.generator.test.Test.values().elementAt(index)
)
}
assertEquals(
correctSubtypes.size, dev.inmo.micro_utils.ksp.sealed.generator.test.Test.subtypes().size
)
correctSubtypes.forEachIndexed { index, value ->
assertTrue(
value.qualifiedName != null
)
assertTrue(
value.qualifiedName === dev.inmo.micro_utils.ksp.sealed.generator.test.Test.subtypes().elementAt(index).qualifiedName
)
}
}
}

View File

@@ -8,8 +8,16 @@ interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean> val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean> val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
get() = { get() = {
it.foldRight<IdType, Op<Boolean>?>(null) { id, acc -> if (it.isEmpty()) {
acc ?.or(selectById(id)) ?: selectById(id) Op.FALSE
} ?: Op.FALSE } else {
var op = it.firstOrNull() ?.let { selectById(it) } ?: Op.FALSE
var i = 1
while (i < it.size) {
op = op.or(selectById(it[i]))
i++
}
op
}
} }
} }