mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
f7e98dfd2d | |||
61277e92bd | |||
32ef9f399f | |||
54e6ca5dc3 | |||
a8e226786d | |||
ce717a4c9f | |||
fd41bf0ae7 | |||
b2b68bf29f | |||
b87c29c354 | |||
24977822c9 | |||
647daa8627 | |||
a372efacb1 | |||
f40d33db2a | |||
a5eb0fbd24 | |||
307c8030af | |||
a9016465fa | |||
358b70eb5f | |||
6fcbb80a71 | |||
|
643f6c420b | ||
ac68b0b941 |
38
CHANGELOG.md
38
CHANGELOG.md
@@ -1,5 +1,43 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.24.5
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `2.1.0` -> `2.1.10`
|
||||||
|
* `SQLite`: `3.47.2.0` -> `3.48.0.0`
|
||||||
|
* `Koin`: `4.0.1` -> `4.0.2`
|
||||||
|
* `Android RecyclerView`: `1.3.2` -> `1.4.0`
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.57.0` -> `0.58.0`
|
||||||
|
* `Ksp`:
|
||||||
|
* `Sealed`:
|
||||||
|
* Add annotation `GenerateSealedTypesWorkaround` which allow to generate `subtypes` lists
|
||||||
|
|
||||||
|
## 0.24.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.7.3` -> `1.8.0`
|
||||||
|
* `SQLite`: `3.47.1.0` -> `3.47.2.0`
|
||||||
|
* `Koin`: `4.0.0` -> `3.10.2`
|
||||||
|
* `OKio`: `3.9.1` -> `3.10.2`
|
||||||
|
|
||||||
## 0.24.0
|
## 0.24.0
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.24.0
|
version=0.24.5
|
||||||
android_code_version=279
|
android_code_version=285
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "2.1.0"
|
kt = "2.1.10"
|
||||||
kt-serialization = "1.7.3"
|
kt-serialization = "1.8.0"
|
||||||
kt-coroutines = "1.10.1"
|
kt-coroutines = "1.10.1"
|
||||||
|
|
||||||
kslog = "1.4.0"
|
kslog = "1.4.0"
|
||||||
|
|
||||||
jb-compose = "1.7.3"
|
jb-compose = "1.7.3"
|
||||||
jb-exposed = "0.57.0"
|
jb-exposed = "0.58.0"
|
||||||
jb-dokka = "2.0.0"
|
jb-dokka = "2.0.0"
|
||||||
|
|
||||||
sqlite = "3.47.1.0"
|
sqlite = "3.48.0.0"
|
||||||
|
|
||||||
korlibs = "5.4.0"
|
korlibs = "5.4.0"
|
||||||
uuid = "0.8.4"
|
uuid = "0.8.4"
|
||||||
@@ -19,11 +19,11 @@ ktor = "3.0.3"
|
|||||||
|
|
||||||
gh-release = "2.5.2"
|
gh-release = "2.5.2"
|
||||||
|
|
||||||
koin = "4.0.0"
|
koin = "4.0.2"
|
||||||
|
|
||||||
okio = "3.9.1"
|
okio = "3.10.2"
|
||||||
|
|
||||||
ksp = "2.1.0-1.0.29"
|
ksp = "2.1.10-1.0.29"
|
||||||
kotlin-poet = "1.18.1"
|
kotlin-poet = "1.18.1"
|
||||||
|
|
||||||
versions = "0.51.0"
|
versions = "0.51.0"
|
||||||
@@ -32,7 +32,7 @@ android-gradle = "8.2.2"
|
|||||||
dexcount = "4.0.0"
|
dexcount = "4.0.0"
|
||||||
|
|
||||||
android-coreKtx = "1.15.0"
|
android-coreKtx = "1.15.0"
|
||||||
android-recyclerView = "1.3.2"
|
android-recyclerView = "1.4.0"
|
||||||
android-appCompat = "1.7.0"
|
android-appCompat = "1.7.0"
|
||||||
android-fragment = "1.8.5"
|
android-fragment = "1.8.5"
|
||||||
android-espresso = "3.6.1"
|
android-espresso = "3.6.1"
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -3,9 +3,15 @@ package dev.inmo.micro_utils.ksp.sealed.generator
|
|||||||
import com.google.devtools.ksp.KspExperimental
|
import com.google.devtools.ksp.KspExperimental
|
||||||
import com.google.devtools.ksp.getAnnotationsByType
|
import com.google.devtools.ksp.getAnnotationsByType
|
||||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||||
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround
|
||||||
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
||||||
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround as OldGenerateSealedWorkaround
|
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround as OldGenerateSealedWorkaround
|
||||||
|
|
||||||
@OptIn(KspExperimental::class)
|
@OptIn(KspExperimental::class)
|
||||||
val KSClassDeclaration.getGenerateSealedWorkaroundAnnotation
|
val KSClassDeclaration.getGenerateSealedWorkaroundAnnotation
|
||||||
get() = (getAnnotationsByType(GenerateSealedWorkaround::class).firstOrNull() ?: getAnnotationsByType(OldGenerateSealedWorkaround::class).firstOrNull())
|
get() = (getAnnotationsByType(GenerateSealedWorkaround::class).firstOrNull() ?: getAnnotationsByType(OldGenerateSealedWorkaround::class).firstOrNull())
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
val KSClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation
|
||||||
|
get() = getAnnotationsByType(GenerateSealedTypesWorkaround::class).firstOrNull()
|
||||||
|
@@ -6,21 +6,17 @@ import com.google.devtools.ksp.processing.CodeGenerator
|
|||||||
import com.google.devtools.ksp.processing.Resolver
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
import com.google.devtools.ksp.symbol.*
|
import com.google.devtools.ksp.symbol.*
|
||||||
import com.squareup.kotlinpoet.ClassName
|
import com.squareup.kotlinpoet.*
|
||||||
import com.squareup.kotlinpoet.CodeBlock
|
|
||||||
import com.squareup.kotlinpoet.FileSpec
|
|
||||||
import com.squareup.kotlinpoet.FunSpec
|
|
||||||
import com.squareup.kotlinpoet.KModifier
|
|
||||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
import com.squareup.kotlinpoet.PropertySpec
|
|
||||||
import com.squareup.kotlinpoet.asTypeName
|
|
||||||
import com.squareup.kotlinpoet.ksp.toClassName
|
import com.squareup.kotlinpoet.ksp.toClassName
|
||||||
import dev.inmo.micro_ksp.generator.buildSubFileName
|
import dev.inmo.micro_ksp.generator.buildSubFileName
|
||||||
import dev.inmo.micro_ksp.generator.companion
|
import dev.inmo.micro_ksp.generator.companion
|
||||||
import dev.inmo.micro_ksp.generator.findSubClasses
|
import dev.inmo.micro_ksp.generator.findSubClasses
|
||||||
import dev.inmo.micro_ksp.generator.writeFile
|
import dev.inmo.micro_ksp.generator.writeFile
|
||||||
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround
|
||||||
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class Processor(
|
class Processor(
|
||||||
private val codeGenerator: CodeGenerator
|
private val codeGenerator: CodeGenerator
|
||||||
@@ -109,6 +105,62 @@ class Processor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
private fun FileSpec.Builder.generateSealedTypesWorkaround(
|
||||||
|
ksClassDeclaration: KSClassDeclaration,
|
||||||
|
resolver: Resolver
|
||||||
|
) {
|
||||||
|
val annotation = ksClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation
|
||||||
|
val subClasses = ksClassDeclaration.resolveSubclasses(
|
||||||
|
searchIn = resolver.getAllFiles(),
|
||||||
|
allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false
|
||||||
|
).distinct()
|
||||||
|
val subClassesNames = subClasses.filter {
|
||||||
|
it.getAnnotationsByType(GenerateSealedTypesWorkaround.Exclude::class).count() == 0
|
||||||
|
}.sortedBy {
|
||||||
|
(it.getAnnotationsByType(GenerateSealedTypesWorkaround.Order::class).firstOrNull()) ?.order ?: 0
|
||||||
|
}.map {
|
||||||
|
it.toClassName()
|
||||||
|
}.toList()
|
||||||
|
val className = ksClassDeclaration.toClassName()
|
||||||
|
val setType = Set::class.asTypeName().parameterizedBy(
|
||||||
|
KClass::class.asTypeName().parameterizedBy(
|
||||||
|
TypeVariableName(
|
||||||
|
"out ${ksClassDeclaration.asStarProjectedType().toClassName().simpleNames.joinToString(".")}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addProperty(
|
||||||
|
PropertySpec.builder(
|
||||||
|
"subtypes",
|
||||||
|
setType
|
||||||
|
).apply {
|
||||||
|
modifiers.add(
|
||||||
|
KModifier.PRIVATE
|
||||||
|
)
|
||||||
|
initializer(
|
||||||
|
CodeBlock.of(
|
||||||
|
"""setOf(${subClassesNames.joinToString(",\n") { it.simpleNames.joinToString(".") + "::class" }})"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("subtypes").apply {
|
||||||
|
val companion = ksClassDeclaration.takeIf { it.isCompanionObject } ?.toClassName()
|
||||||
|
?: ksClassDeclaration.companion ?.toClassName()
|
||||||
|
?: ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion")
|
||||||
|
receiver(companion)
|
||||||
|
returns(setType)
|
||||||
|
addCode(
|
||||||
|
CodeBlock.of(
|
||||||
|
"""return subtypes"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(KspExperimental::class)
|
@OptIn(KspExperimental::class)
|
||||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
||||||
@@ -131,6 +183,26 @@ class Processor(
|
|||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(resolver.getSymbolsWithAnnotation(GenerateSealedTypesWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
|
||||||
|
val prefix = (it.getGenerateSealedTypesWorkaroundAnnotation) ?.prefix ?.takeIf {
|
||||||
|
it.isNotEmpty()
|
||||||
|
} ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "")
|
||||||
|
it.writeFile(prefix = prefix, suffix = "SealedTypesWorkaround") {
|
||||||
|
FileSpec.builder(
|
||||||
|
it.packageName.asString(),
|
||||||
|
"${it.simpleName.getShortName()}SealedTypesWorkaround"
|
||||||
|
).apply {
|
||||||
|
addFileComment(
|
||||||
|
"""
|
||||||
|
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
TO REGENERATE IT JUST DELETE FILE
|
||||||
|
ORIGINAL FILE: ${it.containingFile ?.fileName}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
generateSealedTypesWorkaround(it, resolver)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
package dev.inmo.micro_utils.ksp.sealed.generator.test
|
package dev.inmo.micro_utils.ksp.sealed.generator.test
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround
|
||||||
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
|
||||||
|
|
||||||
@GenerateSealedWorkaround
|
@GenerateSealedWorkaround
|
||||||
|
@GenerateSealedTypesWorkaround
|
||||||
sealed interface Test {
|
sealed interface Test {
|
||||||
@GenerateSealedWorkaround.Order(2)
|
@GenerateSealedWorkaround.Order(2)
|
||||||
|
@GenerateSealedTypesWorkaround.Exclude
|
||||||
object A : Test
|
object A : Test
|
||||||
@GenerateSealedWorkaround.Exclude
|
@GenerateSealedWorkaround.Exclude
|
||||||
|
@GenerateSealedTypesWorkaround.Order(2)
|
||||||
object B : Test
|
object B : Test
|
||||||
@GenerateSealedWorkaround.Order(0)
|
@GenerateSealedWorkaround.Order(0)
|
||||||
|
@GenerateSealedTypesWorkaround.Order(0)
|
||||||
object C : Test
|
object C : Test
|
||||||
|
|
||||||
// Required for successful sealed workaround generation
|
// Required for successful sealed workaround generation
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
// TO REGENERATE IT JUST DELETE FILE
|
||||||
|
// ORIGINAL FILE: Test.kt
|
||||||
|
package dev.inmo.micro_utils.ksp.`sealed`.generator.test
|
||||||
|
|
||||||
|
import kotlin.collections.Set
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
private val subtypes: Set<KClass<out Test>> = setOf(Test.C::class,
|
||||||
|
Test.B::class)
|
||||||
|
|
||||||
|
public fun Test.Companion.subtypes(): Set<KClass<out Test>> = subtypes
|
40
ksp/sealed/generator/test/src/jvmTest/kotlin/TestTests.kt
Normal file
40
ksp/sealed/generator/test/src/jvmTest/kotlin/TestTests.kt
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.ksp.sealed
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class GenerateSealedTypesWorkaround(
|
||||||
|
val prefix: String = "",
|
||||||
|
val includeNonSealedSubTypes: Boolean = false,
|
||||||
|
) {
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Order(val order: Int)
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Exclude
|
||||||
|
}
|
@@ -4,7 +4,7 @@ package dev.inmo.micro_utils.ksp.sealed
|
|||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
annotation class GenerateSealedWorkaround(
|
annotation class GenerateSealedWorkaround(
|
||||||
val prefix: String = "",
|
val prefix: String = "",
|
||||||
val includeNonSealedSubTypes: Boolean = false
|
val includeNonSealedSubTypes: Boolean = false,
|
||||||
) {
|
) {
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:base"
|
"config:recommended"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user