mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
e9c5df4c13 | |||
bc7789ad2c | |||
e3da761249 | |||
4082f65afa | |||
5d1cab075d | |||
bcf67f7e59 | |||
7d3b1f8e75 | |||
119a0588cc | |||
fab789d9c0 | |||
ceba81c08f | |||
a061af0558 | |||
c7a53846ad | |||
a683cccf0c | |||
50d41e35c1 | |||
aa0e831cea | |||
44e26ccb4f | |||
2a783f6e2b | |||
6058d6a724 | |||
2e9c7eb5fa | |||
e75465ad10 | |||
de01ad54e9 | |||
eeea7ddbe3 | |||
e0b18bec05 | |||
410e89bba9 | |||
9ef19dc42b | |||
0337d1b82d | |||
f5bd4c5ccb | |||
630f9bc0d4 | |||
18b4ffece1 | |||
f64e1effa3 | |||
847fcbb488 | |||
88002ec8e7 | |||
7f8db6a29d | |||
b183b82443 | |||
5dad27de72 | |||
6b66084d0e | |||
50b56a7c39 | |||
7ab7d14471 | |||
bdcc179b7b | |||
55ffd4b46f | |||
7fc5ee70e1 | |||
a24a335743 | |||
ef9af71960 | |||
925702d315 | |||
d50dffec8c | |||
cef2081a13 | |||
06c8bde7c9 | |||
c9bbfa3820 | |||
eed7cfdc42 | |||
bd9b0d16ab | |||
ea6c33b497 | |||
dc80ade2fb | |||
f6a06ee8ea | |||
2644f27975 | |||
3dc68a7b8b | |||
97fc1d6239 | |||
662f4d22a3 | |||
b70aa12be9 | |||
71f12f5f19 | |||
e10504eeeb | |||
2dea9f3bc0 | |||
35c9dda5bc | |||
e831f3949a | |||
b0b39cc693 | |||
fc03be3f73 | |||
b61f6b81f1 | |||
f5bc1c1fce | |||
a729f9568c | |||
5749e00377 | |||
ef73c24a0c | |||
94717ee351 | |||
9a18ded65b | |||
b23220f491 | |||
6e6bb03246 | |||
1ae6bae3b8 | |||
1239ca3256 | |||
57b7797ea4 | |||
5ee5bfd1d5 | |||
7229a3e198 | |||
bee083582f | |||
6ef403853c |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: Regular build
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: 1.8
|
|
||||||
- name: Build
|
|
||||||
run: ./gradlew build
|
|
3
.github/workflows/dokka_push.yml
vendored
3
.github/workflows/dokka_push.yml
vendored
@@ -11,6 +11,9 @@ jobs:
|
|||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.8
|
||||||
|
- name: Fix android 31.0.0 dx
|
||||||
|
continue-on-error: true
|
||||||
|
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dokkaHtml
|
run: ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
4
.github/workflows/packages_push.yml
vendored
4
.github/workflows/packages_push.yml
vendored
@@ -9,6 +9,9 @@ jobs:
|
|||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.8
|
||||||
|
- name: Fix android 31.0.0 dx
|
||||||
|
continue-on-error: true
|
||||||
|
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
@@ -18,6 +21,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
continue-on-error: true
|
||||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
||||||
env:
|
env:
|
||||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||||
|
121
CHANGELOG.md
121
CHANGELOG.md
@@ -1,7 +1,128 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.7.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.3` -> `2.4.5`
|
||||||
|
* `Exposed`: `0.35.1` -> `0.35.2`
|
||||||
|
* `Coroutines`:
|
||||||
|
* `Common`:
|
||||||
|
* New `Flow` - `AccumulatorFlow`
|
||||||
|
* `FSM`:
|
||||||
|
* `Common`:
|
||||||
|
* `InMemoryStatesManager` has been replaced
|
||||||
|
* `StatesMachine` became an interface
|
||||||
|
* New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of
|
||||||
|
data info
|
||||||
|
|
||||||
|
## 0.7.0
|
||||||
|
|
||||||
|
**THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL**
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `kotlinx.datetime` -> `Klock`
|
||||||
|
|
||||||
|
## 0.6.0 DO NOT RECOMMENDED
|
||||||
|
|
||||||
|
**THIS VERSION HAS MIGRATED FROM KORLIBS KLOCK TO KOTLINX DATETIME. CAREFUL**
|
||||||
|
**ALL DEPRECATION HAVE BEEN REMOVED**
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Klock` -> `kotlinx.datetime`
|
||||||
|
|
||||||
|
## 0.5.31
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.2` -> `2.4.3`
|
||||||
|
* `Ktor`: `1.6.3` -> `1.6.4`
|
||||||
|
|
||||||
|
## 0.5.30
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.2.2` -> `1.3.0`
|
||||||
|
|
||||||
|
## 0.5.29
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.34.2` -> `0.35.1`
|
||||||
|
|
||||||
|
## 0.5.28
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.5.30` -> `1.5.31`
|
||||||
|
* `Klock`: `2.4.1` -> `2.4.2`
|
||||||
|
|
||||||
|
## 0.5.27
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.34.1` -> `0.34.2`
|
||||||
|
|
||||||
|
## 0.5.26
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `InMemory`:
|
||||||
|
* `MapCRUDRepo`s and `MapKeyValueRepo`s got `protected` methods and properties instead of private
|
||||||
|
|
||||||
|
## 0.5.25
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `UUID`: `0.3.0` -> `0.3.1`
|
||||||
|
* `Common`:
|
||||||
|
* New property `MPPFile#withoutSlashAtTheEnd`
|
||||||
|
* Extension `clamp` has been deprecated
|
||||||
|
* New extension `Iterable#diff`
|
||||||
|
* `Serialization`:
|
||||||
|
* New operators `TypedSerializer#plusAssign` and `TypedSerializer#minusAssign`
|
||||||
|
|
||||||
|
## 0.5.24
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Coroutines`: `1.5.1` -> `1.5.2`
|
||||||
|
* `Klock`: `2.3.4` -> `2.4.1`
|
||||||
|
* `Coroutines`:
|
||||||
|
* New function `CoroutineScope` with safely exceptions handler as second parameter
|
||||||
|
|
||||||
|
## 0.5.23
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.33.1` -> `0.34.1`
|
||||||
|
* `Common`:
|
||||||
|
* New extensions `Iterable#joinTo` and `Array#joinTo`
|
||||||
|
|
||||||
|
## 0.5.22
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Kotlin`: `1.5.21` -> `1.5.30`
|
||||||
|
* `Klock`: `2.3.2` -> `2.3.4`
|
||||||
|
* `AppCompat`: `1.3.0` -> `1.3.1`
|
||||||
|
* `Ktor`: `1.6.2` -> `1.6.3`
|
||||||
|
|
||||||
|
## 0.5.21
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Klock`: `2.3.1` -> `2.3.2`
|
||||||
|
* `Serialization`
|
||||||
|
* `Typed Serializer`:
|
||||||
|
* `TypedSerializer` Descriptor serial name has been fixed
|
||||||
|
|
||||||
|
## 0.5.20
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`
|
||||||
|
* `Android`:
|
||||||
|
* `*OrNull` analogs of `Cursor.get*(String)` extensions have been added
|
||||||
|
* Extensions `Cursor.getFloat` and `Cursor.getFloatOrNull` have been added
|
||||||
|
|
||||||
|
## 0.5.19
|
||||||
|
|
||||||
|
* `LanguageCode`:
|
||||||
|
* `IetfLanguageCode` became as sealed class
|
||||||
|
* `IetfLanguageCode` now override `toString` and returns its code
|
||||||
|
|
||||||
## 0.5.18
|
## 0.5.18
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Kotlin Exposed`: `0.32.1` -> `0.33.1`
|
||||||
* `LanguageCode`:
|
* `LanguageCode`:
|
||||||
* Module has been created
|
* Module has been created
|
||||||
|
|
||||||
|
@@ -12,9 +12,7 @@ package dev.inmo.micro_utils.common
|
|||||||
AnnotationTarget.PROPERTY_GETTER,
|
AnnotationTarget.PROPERTY_GETTER,
|
||||||
AnnotationTarget.PROPERTY_SETTER,
|
AnnotationTarget.PROPERTY_SETTER,
|
||||||
AnnotationTarget.FUNCTION,
|
AnnotationTarget.FUNCTION,
|
||||||
AnnotationTarget.TYPE,
|
AnnotationTarget.TYPEALIAS
|
||||||
AnnotationTarget.TYPEALIAS,
|
|
||||||
AnnotationTarget.TYPE_PARAMETER
|
|
||||||
)
|
)
|
||||||
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
||||||
|
|
||||||
@@ -30,8 +28,6 @@ annotation class PreviewFeature(val message: String = "It is possible, that beha
|
|||||||
AnnotationTarget.PROPERTY_GETTER,
|
AnnotationTarget.PROPERTY_GETTER,
|
||||||
AnnotationTarget.PROPERTY_SETTER,
|
AnnotationTarget.PROPERTY_SETTER,
|
||||||
AnnotationTarget.FUNCTION,
|
AnnotationTarget.FUNCTION,
|
||||||
AnnotationTarget.TYPE,
|
AnnotationTarget.TYPEALIAS
|
||||||
AnnotationTarget.TYPEALIAS,
|
|
||||||
AnnotationTarget.TYPE_PARAMETER
|
|
||||||
)
|
)
|
||||||
annotation class Warning(val message: String)
|
annotation class Warning(val message: String)
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
|
|
||||||
return when {
|
|
||||||
this < min -> min
|
|
||||||
this > max -> max
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
private inline fun <T> getObject(
|
private inline fun <T> getObject(
|
||||||
additional: MutableList<T>,
|
additional: MutableList<T>,
|
||||||
iterator: Iterator<T>
|
iterator: Iterator<T>
|
||||||
@@ -27,8 +29,8 @@ data class Diff<T> internal constructor(
|
|||||||
|
|
||||||
private inline fun <T> performChanges(
|
private inline fun <T> performChanges(
|
||||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
additionalsInOld: MutableList<T>,
|
additionsInOld: MutableList<T>,
|
||||||
additionalsInNew: MutableList<T>,
|
additionsInNew: MutableList<T>,
|
||||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
removedList: MutableList<IndexedValue<T>>,
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
addedList: MutableList<IndexedValue<T>>,
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
@@ -52,20 +54,20 @@ private inline fun <T> performChanges(
|
|||||||
newPotentials.first().second ?.let { addedList.add(it) }
|
newPotentials.first().second ?.let { addedList.add(it) }
|
||||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
addedList.add(newOne!!)
|
addedList.add(newOne!!)
|
||||||
oldOne ?.let { additionalsInOld.add(oldOne.value) }
|
oldOne ?.let { additionsInOld.add(oldOne.value) }
|
||||||
}
|
}
|
||||||
if (newPotentials.size > 1) {
|
if (newPotentials.size > 1) {
|
||||||
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
|
newPotentials.last().first ?.value ?.let { additionsInOld.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOneEqualToOldObject -> {
|
newOneEqualToOldObject -> {
|
||||||
newPotentials.first().first ?.let { removedList.add(it) }
|
newPotentials.first().first ?.let { removedList.add(it) }
|
||||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
removedList.add(oldOne!!)
|
removedList.add(oldOne!!)
|
||||||
newOne ?.let { additionalsInNew.add(newOne.value) }
|
newOne ?.let { additionsInNew.add(newOne.value) }
|
||||||
}
|
}
|
||||||
if (newPotentials.size > 1) {
|
if (newPotentials.size > 1) {
|
||||||
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
|
newPotentials.last().second ?.value ?.let { additionsInNew.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,6 +141,10 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
|
|
||||||
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
}
|
}
|
||||||
|
inline fun <T> Iterable<T>.diff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> = calculateDiff(other, strictComparison)
|
||||||
|
|
||||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
||||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||||
|
@@ -0,0 +1,59 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
inline fun <I, R> Iterable<I>.joinTo(
|
||||||
|
crossinline separatorFun: (I) -> R?,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
crossinline transform: (I) -> R?
|
||||||
|
): List<R> {
|
||||||
|
val result = mutableListOf<R>()
|
||||||
|
val iterator = iterator()
|
||||||
|
|
||||||
|
prefix ?.let(result::add)
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val element = iterator.next()
|
||||||
|
result.add(transform(element) ?: continue)
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
result.add(separatorFun(element) ?: continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postfix ?.let(result::add)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <I, R> Iterable<I>.joinTo(
|
||||||
|
separator: R? = null,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
crossinline transform: (I) -> R?
|
||||||
|
): List<R> = joinTo({ separator }, prefix, postfix, transform)
|
||||||
|
|
||||||
|
inline fun <I> Iterable<I>.joinTo(
|
||||||
|
crossinline separatorFun: (I) -> I?,
|
||||||
|
prefix: I? = null,
|
||||||
|
postfix: I? = null
|
||||||
|
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
|
||||||
|
|
||||||
|
inline fun <I> Iterable<I>.joinTo(
|
||||||
|
separator: I? = null,
|
||||||
|
prefix: I? = null,
|
||||||
|
postfix: I? = null
|
||||||
|
): List<I> = joinTo<I>({ separator }, prefix, postfix)
|
||||||
|
|
||||||
|
inline fun <I, reified R> Array<I>.joinTo(
|
||||||
|
crossinline separatorFun: (I) -> R?,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
crossinline transform: (I) -> R?
|
||||||
|
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
|
||||||
|
|
||||||
|
inline fun <I, reified R> Array<I>.joinTo(
|
||||||
|
separator: R? = null,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
crossinline transform: (I) -> R?
|
||||||
|
): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray()
|
@@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline
|
|||||||
@JvmInline
|
@JvmInline
|
||||||
value class FileName(val string: String) {
|
value class FileName(val string: String) {
|
||||||
val name: String
|
val name: String
|
||||||
get() = string.takeLastWhile { it != '/' }
|
get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
|
||||||
val extension: String
|
val extension: String
|
||||||
get() = name.takeLastWhile { it != '.' }
|
get() = name.takeLastWhile { it != '.' }
|
||||||
val nameWithoutExtension: String
|
val nameWithoutExtension: String
|
||||||
@@ -17,6 +17,8 @@ value class FileName(val string: String) {
|
|||||||
filename.substring(0, it)
|
filename.substring(0, it)
|
||||||
} ?: filename
|
} ?: filename
|
||||||
}
|
}
|
||||||
|
val withoutSlashAtTheEnd: String
|
||||||
|
get() = string.dropLastWhile { it == '/' }
|
||||||
override fun toString(): String = string
|
override fun toString(): String = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ class DiffUtilsTests {
|
|||||||
val withIndex = oldList.withIndex()
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
for ((i, v) in withIndex) {
|
for ((i, _) in withIndex) {
|
||||||
if (i + count > oldList.lastIndex) {
|
if (i + count > oldList.lastIndex) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,94 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
private sealed interface AccumulatorFlowStep
|
||||||
|
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
|
||||||
|
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||||
|
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
|
||||||
|
*
|
||||||
|
* * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new
|
||||||
|
* [FlowCollector]s until anybody will handle it
|
||||||
|
* * Here there are an [activeData] where data [T] will be stored until somebody will handle it
|
||||||
|
*/
|
||||||
|
class AccumulatorFlow<T>(
|
||||||
|
sourceDataFlow: Flow<T>,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) : AbstractFlow<T>() {
|
||||||
|
private val subscope = scope.LinkedSupervisorScope()
|
||||||
|
private val activeData = ArrayDeque<T>()
|
||||||
|
private val dataMutex = Mutex()
|
||||||
|
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
|
||||||
|
private val channelsMutex = Mutex()
|
||||||
|
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
|
||||||
|
when (step) {
|
||||||
|
is DataRetrievedAccumulatorFlowStep -> {
|
||||||
|
if (activeData.first() === step.data) {
|
||||||
|
dataMutex.withLock {
|
||||||
|
activeData.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SubscribeAccumulatorFlowStep -> channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.add(step.channel)
|
||||||
|
dataMutex.withLock {
|
||||||
|
val dataToSend = activeData.toList()
|
||||||
|
safelyWithoutExceptions {
|
||||||
|
dataToSend.forEach { step.channel.send(it as Any) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.remove(step.channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) {
|
||||||
|
dataMutex.withLock {
|
||||||
|
activeData.addLast(it)
|
||||||
|
}
|
||||||
|
channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.forEach { channel ->
|
||||||
|
safelyWithResult {
|
||||||
|
channel.send(it as Any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
||||||
|
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
||||||
|
steps.send(SubscribeAccumulatorFlowStep(channel))
|
||||||
|
for (data in channel) {
|
||||||
|
try {
|
||||||
|
collector.emit(data as T)
|
||||||
|
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
||||||
|
} finally {
|
||||||
|
channel.cancel()
|
||||||
|
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [AccumulatorFlow] using [this] as base [Flow]
|
||||||
|
*/
|
||||||
|
fun <T> Flow<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
|
||||||
|
return AccumulatorFlow(this, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get
|
||||||
|
*/
|
||||||
|
fun <T> Channel<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
|
||||||
|
return receiveAsFlow().accumulatorFlow(scope)
|
||||||
|
}
|
@@ -147,3 +147,10 @@ suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
|||||||
): Result<T?> = runCatching {
|
): Result<T?> = runCatching {
|
||||||
safelyWithoutExceptions(onException, block)
|
safelyWithoutExceptions(onException, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun CoroutineScope(
|
||||||
|
context: CoroutineContext,
|
||||||
|
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
|
||||||
|
) = CoroutineScope(
|
||||||
|
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
||||||
|
)
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
||||||
|
|
||||||
|
@Deprecated("Replaced", ReplaceWith("InMemoryStatesManager", "dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager"))
|
||||||
|
typealias InMemoryStatesManager = InMemoryStatesManager
|
@@ -2,13 +2,25 @@ package dev.inmo.micro_utils.fsm.common
|
|||||||
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
|
||||||
|
* casting in [handleState]
|
||||||
|
*/
|
||||||
class StateHandlerHolder<I : State>(
|
class StateHandlerHolder<I : State>(
|
||||||
private val inputKlass: KClass<I>,
|
private val inputKlass: KClass<I>,
|
||||||
private val strict: Boolean = false,
|
private val strict: Boolean = false,
|
||||||
private val delegateTo: StatesHandler<I>
|
private val delegateTo: StatesHandler<I>
|
||||||
) : StatesHandler<State> {
|
) : StatesHandler<State> {
|
||||||
|
/**
|
||||||
|
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
|
||||||
|
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
|
||||||
|
*/
|
||||||
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
|
||||||
|
* to be sure that this [StateHandlerHolder] will be able to handle [state]
|
||||||
|
*/
|
||||||
override suspend fun StatesMachine.handleState(state: State): State? {
|
override suspend fun StatesMachine.handleState(state: State): State? {
|
||||||
return delegateTo.run { handleState(state as I) }
|
return delegateTo.run { handleState(state as I) }
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default realization of states handler
|
||||||
|
*/
|
||||||
fun interface StatesHandler<I : State> {
|
fun interface StatesHandler<I : State> {
|
||||||
|
/**
|
||||||
|
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
|
||||||
|
* [State] it is assumed that chain is not completed.
|
||||||
|
*/
|
||||||
suspend fun StatesMachine.handleState(state: I): State?
|
suspend fun StatesMachine.handleState(state: I): State?
|
||||||
}
|
}
|
||||||
|
@@ -13,13 +13,53 @@ private suspend fun <I : State> StatesMachine.launchStateHandling(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatesMachine (
|
/**
|
||||||
|
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
|
||||||
|
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
|
||||||
|
* handling until [start] method will be called
|
||||||
|
*/
|
||||||
|
interface StatesMachine : StatesHandler<State> {
|
||||||
|
/**
|
||||||
|
* Starts handling of [State]s
|
||||||
|
*/
|
||||||
|
fun start(scope: CoroutineScope): Job
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start chain of [State]s witn [state]
|
||||||
|
*/
|
||||||
|
suspend fun startChain(state: State)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates [DefaultStatesMachine]
|
||||||
|
*/
|
||||||
|
operator fun invoke(
|
||||||
|
statesManager: StatesManager,
|
||||||
|
handlers: List<StateHandlerHolder<*>>
|
||||||
|
) = DefaultStatesMachine(statesManager, handlers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
||||||
|
* resolving, and uses [launchStateHandling] for [State] handling
|
||||||
|
*/
|
||||||
|
class DefaultStatesMachine (
|
||||||
private val statesManager: StatesManager,
|
private val statesManager: StatesManager,
|
||||||
private val handlers: List<StateHandlerHolder<*>>
|
private val handlers: List<StateHandlerHolder<*>>
|
||||||
) : StatesHandler<State> {
|
) : StatesMachine {
|
||||||
|
/**
|
||||||
|
* Will call [launchStateHandling] for state handling
|
||||||
|
*/
|
||||||
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
||||||
|
|
||||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
/**
|
||||||
|
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
||||||
|
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
|
||||||
|
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
|
||||||
|
* [StatesManager.endChain].
|
||||||
|
*/
|
||||||
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statePerformer: suspend (State) -> Unit = { state: State ->
|
val statePerformer: suspend (State) -> Unit = { state: State ->
|
||||||
val newState = launchStateHandling(state, handlers)
|
val newState = launchStateHandling(state, handlers)
|
||||||
if (newState != null) {
|
if (newState != null) {
|
||||||
@@ -40,7 +80,10 @@ class StatesMachine (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startChain(state: State) {
|
/**
|
||||||
|
* Just calls [StatesManager.startChain] of [statesManager]
|
||||||
|
*/
|
||||||
|
override suspend fun startChain(state: State) {
|
||||||
statesManager.startChain(state)
|
statesManager.startChain(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
interface StatesManager {
|
interface StatesManager {
|
||||||
val onChainStateUpdated: Flow<Pair<State, State>>
|
val onChainStateUpdated: Flow<Pair<State, State>>
|
||||||
@@ -30,63 +28,3 @@ interface StatesManager {
|
|||||||
suspend fun getActiveStates(): List<State>
|
suspend fun getActiveStates(): List<State>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
|
||||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
|
||||||
* new state by using [endChain] with that state
|
|
||||||
*/
|
|
||||||
class InMemoryStatesManager(
|
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
|
||||||
) : StatesManager {
|
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
|
||||||
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
|
||||||
private val _onStartChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
|
||||||
private val _onEndChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
|
||||||
|
|
||||||
private val contextsToStates = mutableMapOf<Any, State>()
|
|
||||||
private val mapMutex = Mutex()
|
|
||||||
|
|
||||||
override suspend fun update(old: State, new: State) = mapMutex.withLock {
|
|
||||||
when {
|
|
||||||
contextsToStates[old.context] != old -> return@withLock
|
|
||||||
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
|
||||||
contextsToStates[old.context] = new
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val stateOnNewOneContext = contextsToStates.getValue(new.context)
|
|
||||||
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
|
||||||
endChainWithoutLock(stateOnNewOneContext)
|
|
||||||
contextsToStates.remove(old.context)
|
|
||||||
contextsToStates[new.context] = new
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun startChain(state: State) = mapMutex.withLock {
|
|
||||||
if (!contextsToStates.containsKey(state.context)) {
|
|
||||||
contextsToStates[state.context] = state
|
|
||||||
_onStartChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: State) {
|
|
||||||
if (contextsToStates[state.context] == state) {
|
|
||||||
contextsToStates.remove(state.context)
|
|
||||||
_onEndChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun endChain(state: State) {
|
|
||||||
mapMutex.withLock {
|
|
||||||
endChainWithoutLock(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common.dsl
|
package dev.inmo.micro_utils.fsm.common.dsl
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.*
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FSMBuilder(
|
class FSMBuilder(
|
||||||
var statesManager: StatesManager = InMemoryStatesManager()
|
var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo())
|
||||||
) {
|
) {
|
||||||
private var states = mutableListOf<StateHandlerHolder<*>>()
|
private var states = mutableListOf<StateHandlerHolder<*>>()
|
||||||
|
|
||||||
|
@@ -0,0 +1,101 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import dev.inmo.micro_utils.fsm.common.StatesManager
|
||||||
|
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]
|
||||||
|
*/
|
||||||
|
interface DefaultStatesManagerRepo {
|
||||||
|
/**
|
||||||
|
* Must save [state] as current state of chain with [State.context] of [state]
|
||||||
|
*/
|
||||||
|
suspend fun set(state: State)
|
||||||
|
/**
|
||||||
|
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
|
||||||
|
* NOT be removed
|
||||||
|
*/
|
||||||
|
suspend fun removeState(state: State)
|
||||||
|
/**
|
||||||
|
* @return Current list of available and saved states
|
||||||
|
*/
|
||||||
|
suspend fun getStates(): List<State>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Current state by [context]
|
||||||
|
*/
|
||||||
|
suspend fun getContextState(context: Any): State?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Current state by [context]
|
||||||
|
*/
|
||||||
|
suspend fun contains(context: Any): Boolean = getContextState(context) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
|
||||||
|
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used
|
||||||
|
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
|
||||||
|
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
|
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||||
|
* new state by using [endChain] with that state
|
||||||
|
*/
|
||||||
|
class DefaultStatesManager(
|
||||||
|
private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(),
|
||||||
|
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
||||||
|
) : StatesManager {
|
||||||
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
||||||
|
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
||||||
|
private val _onStartChain = MutableSharedFlow<State>(0)
|
||||||
|
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
||||||
|
private val _onEndChain = MutableSharedFlow<State>(0)
|
||||||
|
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
|
private val mapMutex = Mutex()
|
||||||
|
|
||||||
|
override suspend fun update(old: State, new: State) = mapMutex.withLock {
|
||||||
|
val stateByOldContext: State? = repo.getContextState(old.context)
|
||||||
|
when {
|
||||||
|
stateByOldContext != old -> return@withLock
|
||||||
|
stateByOldContext == null || old.context == new.context -> {
|
||||||
|
repo.set(new)
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val stateOnNewOneContext = repo.getContextState(new.context)
|
||||||
|
if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||||
|
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
||||||
|
repo.removeState(old)
|
||||||
|
repo.set(new)
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun startChain(state: State) = mapMutex.withLock {
|
||||||
|
if (!repo.contains(state.context)) {
|
||||||
|
repo.set(state)
|
||||||
|
_onStartChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun endChainWithoutLock(state: State) {
|
||||||
|
if (repo.getContextState(state.context) == state) {
|
||||||
|
repo.removeState(state)
|
||||||
|
_onEndChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun endChain(state: State) {
|
||||||
|
mapMutex.withLock {
|
||||||
|
endChainWithoutLock(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getActiveStates(): List<State> = repo.getStates()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
|
||||||
|
* functionality
|
||||||
|
*/
|
||||||
|
class InMemoryDefaultStatesManagerRepo(
|
||||||
|
private val map: MutableMap<Any, State> = mutableMapOf()
|
||||||
|
) : DefaultStatesManagerRepo {
|
||||||
|
override suspend fun set(state: State) {
|
||||||
|
map[state.context] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeState(state: State) {
|
||||||
|
map.remove(state.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStates(): List<State> = map.values.toList()
|
||||||
|
|
||||||
|
override suspend fun getContextState(context: Any): State? = map[context]
|
||||||
|
|
||||||
|
override suspend fun contains(context: Any): Boolean = map.contains(context)
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import dev.inmo.micro_utils.fsm.common.StatesManager
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
|
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||||
|
* new state by using [endChain] with that state
|
||||||
|
*/
|
||||||
|
class InMemoryStatesManager(
|
||||||
|
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
||||||
|
) : StatesManager {
|
||||||
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
||||||
|
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
||||||
|
private val _onStartChain = MutableSharedFlow<State>(0)
|
||||||
|
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
||||||
|
private val _onEndChain = MutableSharedFlow<State>(0)
|
||||||
|
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
|
private val contextsToStates = mutableMapOf<Any, State>()
|
||||||
|
private val mapMutex = Mutex()
|
||||||
|
|
||||||
|
override suspend fun update(old: State, new: State) = mapMutex.withLock {
|
||||||
|
when {
|
||||||
|
contextsToStates[old.context] != old -> return@withLock
|
||||||
|
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
||||||
|
contextsToStates[old.context] = new
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val stateOnNewOneContext = contextsToStates.getValue(new.context)
|
||||||
|
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||||
|
endChainWithoutLock(stateOnNewOneContext)
|
||||||
|
contextsToStates.remove(old.context)
|
||||||
|
contextsToStates[new.context] = new
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun startChain(state: State) = mapMutex.withLock {
|
||||||
|
if (!contextsToStates.containsKey(state.context)) {
|
||||||
|
contextsToStates[state.context] = state
|
||||||
|
_onStartChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun endChainWithoutLock(state: State) {
|
||||||
|
if (contextsToStates[state.context] == state) {
|
||||||
|
contextsToStates.remove(state.context)
|
||||||
|
_onEndChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun endChain(state: State) {
|
||||||
|
mapMutex.withLock {
|
||||||
|
endChainWithoutLock(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.repos.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
|
|
||||||
|
class KeyValueBasedDefaultStatesManagerRepo(
|
||||||
|
private val keyValueRepo: KeyValueRepo<Any, State>
|
||||||
|
) : DefaultStatesManagerRepo {
|
||||||
|
override suspend fun set(state: State) {
|
||||||
|
keyValueRepo.set(state.context, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeState(state: State) {
|
||||||
|
if (keyValueRepo.get(state.context) == state) {
|
||||||
|
keyValueRepo.unset(state.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStates(): List<State> = keyValueRepo.getAll { keys(it) }.map { it.second }
|
||||||
|
override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context)
|
||||||
|
|
||||||
|
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
|
||||||
|
}
|
@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
@Deprecated("Replace with DefaultStatesManager and KeyValueBasedDefaultStatesManagerRepo")
|
||||||
class KeyValueBasedStatesManager(
|
class KeyValueBasedStatesManager(
|
||||||
private val keyValueRepo: KeyValueRepo<Any, State>,
|
private val keyValueRepo: KeyValueRepo<Any, State>,
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
||||||
|
@@ -7,29 +7,29 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
org.gradle.jvmargs=-Xmx2g
|
org.gradle.jvmargs=-Xmx2g
|
||||||
|
|
||||||
kotlin_version=1.5.21
|
kotlin_version=1.5.31
|
||||||
kotlin_coroutines_version=1.5.1
|
kotlin_coroutines_version=1.5.2
|
||||||
kotlin_serialisation_core_version=1.2.2
|
kotlin_serialisation_core_version=1.3.0
|
||||||
kotlin_exposed_version=0.32.1
|
kotlin_exposed_version=0.35.2
|
||||||
|
|
||||||
ktor_version=1.6.2
|
ktor_version=1.6.4
|
||||||
|
|
||||||
klockVersion=2.3.1
|
klockVersion=2.4.5
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
uuidVersion=0.3.0
|
uuidVersion=0.3.1
|
||||||
|
|
||||||
# ANDROID
|
# ANDROID
|
||||||
|
|
||||||
core_ktx_version=1.6.0
|
core_ktx_version=1.6.0
|
||||||
androidx_recycler_version=1.2.1
|
androidx_recycler_version=1.2.1
|
||||||
appcompat_version=1.3.0
|
appcompat_version=1.3.1
|
||||||
|
|
||||||
android_minSdkVersion=19
|
android_minSdkVersion=19
|
||||||
android_compileSdkVersion=30
|
android_compileSdkVersion=31
|
||||||
android_buildToolsVersion=30.0.3
|
android_buildToolsVersion=31.0.0
|
||||||
dexcount_version=2.1.0-RC01
|
dexcount_version=3.0.0
|
||||||
junit_version=4.12
|
junit_version=4.12
|
||||||
test_ext_junit_version=1.1.2
|
test_ext_junit_version=1.1.2
|
||||||
espresso_core=3.3.0
|
espresso_core=3.3.0
|
||||||
@@ -40,10 +40,10 @@ crypto_js_version=4.1.1
|
|||||||
|
|
||||||
# Dokka
|
# Dokka
|
||||||
|
|
||||||
dokka_version=1.4.32
|
dokka_version=1.5.31
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.5.18
|
version=0.7.1
|
||||||
android_code_version=59
|
android_code_version=75
|
||||||
|
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-7.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.text.Normalizer
|
||||||
|
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -29,6 +30,8 @@ fun String.adaptAsTitle() = if (first().isDigit()) {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.normalized() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(Regex("[^\\p{ASCII}]"), "")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
private data class LanguageCodeWithTag(
|
private data class LanguageCodeWithTag(
|
||||||
@SerialName("langType")
|
@SerialName("langType")
|
||||||
@@ -74,11 +77,11 @@ private fun printLanguageCodeAndTags(
|
|||||||
indents: String = " "
|
indents: String = " "
|
||||||
): String = if (tag.subtags.isEmpty()) {
|
): String = if (tag.subtags.isEmpty()) {
|
||||||
"""${indents}${baseClassSerializerAnnotationName}
|
"""${indents}${baseClassSerializerAnnotationName}
|
||||||
${indents}object ${tag.title} : ${parent ?.title ?.let { "$it()" } ?: baseClassName} { override val code: String = "${tag.tag}" }"""
|
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
|
||||||
} else {
|
} else {
|
||||||
"""
|
"""
|
||||||
${indents}${baseClassSerializerAnnotationName}
|
${indents}${baseClassSerializerAnnotationName}
|
||||||
${indents}sealed class ${tag.title} : ${parent ?.title ?.let { "$it()" } ?: baseClassName} {
|
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
||||||
${indents} override val code: String = "${tag.tag}"
|
${indents} override val code: String = "${tag.tag}"
|
||||||
|
|
||||||
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
||||||
@@ -98,13 +101,15 @@ import kotlinx.serialization.Serializable
|
|||||||
* https://datahub.io/core/language-codes/ files (base and tags) and create the whole hierarchy using it.
|
* https://datahub.io/core/language-codes/ files (base and tags) and create the whole hierarchy using it.
|
||||||
*/
|
*/
|
||||||
${baseClassSerializerAnnotationName}
|
${baseClassSerializerAnnotationName}
|
||||||
sealed interface $baseClassName {
|
sealed class $baseClassName {
|
||||||
val code: String
|
abstract val code: String
|
||||||
|
|
||||||
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
||||||
|
|
||||||
$baseClassSerializerAnnotationName
|
$baseClassSerializerAnnotationName
|
||||||
data class $unknownBaseClassName (override val code: String) : $baseClassName
|
data class $unknownBaseClassName (override val code: String) : $baseClassName()
|
||||||
|
|
||||||
|
override fun toString() = code
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
@@ -179,18 +184,14 @@ suspend fun main(vararg args: String) {
|
|||||||
val subtags = unformattedSubtags.mapNotNull {
|
val subtags = unformattedSubtags.mapNotNull {
|
||||||
if (it.endTag == null) {
|
if (it.endTag == null) {
|
||||||
val currentSubtags = (threeLevelTags[it.subtag] ?: emptyList()).map {
|
val currentSubtags = (threeLevelTags[it.subtag] ?: emptyList()).map {
|
||||||
Tag(it.endTagAsTitle!!, it.withSubtag, emptyList())
|
Tag(it.endTagAsTitle!!.normalized(), it.withSubtag, emptyList())
|
||||||
}
|
}
|
||||||
Tag(it.middleTagTitle, it.withSubtag, currentSubtags)
|
Tag(it.middleTagTitle.normalized(), it.withSubtag, currentSubtags)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tag(
|
Tag(it.title.normalized(), it.tag, subtags)
|
||||||
it.title,
|
|
||||||
it.tag,
|
|
||||||
subtags
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File(outputFolder, "LanguageCodes.kt").apply {
|
File(outputFolder, "LanguageCodes.kt").apply {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -461,9 +461,9 @@ fun String.asIetfLanguageCode(): IetfLanguageCode {
|
|||||||
IetfLanguageCode.Burmese.code -> IetfLanguageCode.Burmese
|
IetfLanguageCode.Burmese.code -> IetfLanguageCode.Burmese
|
||||||
IetfLanguageCode.Burmese.MM.code -> IetfLanguageCode.Burmese.MM
|
IetfLanguageCode.Burmese.MM.code -> IetfLanguageCode.Burmese.MM
|
||||||
IetfLanguageCode.Nauru.code -> IetfLanguageCode.Nauru
|
IetfLanguageCode.Nauru.code -> IetfLanguageCode.Nauru
|
||||||
IetfLanguageCode.BokmålNorwegianNorwegianBokmål.code -> IetfLanguageCode.BokmålNorwegianNorwegianBokmål
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal
|
||||||
IetfLanguageCode.BokmålNorwegianNorwegianBokmål.NO.code -> IetfLanguageCode.BokmålNorwegianNorwegianBokmål.NO
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO
|
||||||
IetfLanguageCode.BokmålNorwegianNorwegianBokmål.SJ.code -> IetfLanguageCode.BokmålNorwegianNorwegianBokmål.SJ
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ
|
||||||
IetfLanguageCode.NdebeleNorthNorthNdebele.code -> IetfLanguageCode.NdebeleNorthNorthNdebele
|
IetfLanguageCode.NdebeleNorthNorthNdebele.code -> IetfLanguageCode.NdebeleNorthNorthNdebele
|
||||||
IetfLanguageCode.NdebeleNorthNorthNdebele.ZW.code -> IetfLanguageCode.NdebeleNorthNorthNdebele.ZW
|
IetfLanguageCode.NdebeleNorthNorthNdebele.ZW.code -> IetfLanguageCode.NdebeleNorthNorthNdebele.ZW
|
||||||
IetfLanguageCode.Nepali.code -> IetfLanguageCode.Nepali
|
IetfLanguageCode.Nepali.code -> IetfLanguageCode.Nepali
|
||||||
@@ -639,8 +639,8 @@ fun String.asIetfLanguageCode(): IetfLanguageCode {
|
|||||||
IetfLanguageCode.Venda.code -> IetfLanguageCode.Venda
|
IetfLanguageCode.Venda.code -> IetfLanguageCode.Venda
|
||||||
IetfLanguageCode.Vietnamese.code -> IetfLanguageCode.Vietnamese
|
IetfLanguageCode.Vietnamese.code -> IetfLanguageCode.Vietnamese
|
||||||
IetfLanguageCode.Vietnamese.VN.code -> IetfLanguageCode.Vietnamese.VN
|
IetfLanguageCode.Vietnamese.VN.code -> IetfLanguageCode.Vietnamese.VN
|
||||||
IetfLanguageCode.Volapük.code -> IetfLanguageCode.Volapük
|
IetfLanguageCode.Volapuk.code -> IetfLanguageCode.Volapuk
|
||||||
IetfLanguageCode.Volapük.L001.code -> IetfLanguageCode.Volapük.L001
|
IetfLanguageCode.Volapuk.L001.code -> IetfLanguageCode.Volapuk.L001
|
||||||
IetfLanguageCode.Walloon.code -> IetfLanguageCode.Walloon
|
IetfLanguageCode.Walloon.code -> IetfLanguageCode.Walloon
|
||||||
IetfLanguageCode.Wolof.code -> IetfLanguageCode.Wolof
|
IetfLanguageCode.Wolof.code -> IetfLanguageCode.Wolof
|
||||||
IetfLanguageCode.Wolof.SN.code -> IetfLanguageCode.Wolof.SN
|
IetfLanguageCode.Wolof.SN.code -> IetfLanguageCode.Wolof.SN
|
||||||
|
@@ -24,3 +24,9 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -26,3 +26,9 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -48,3 +48,9 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package dev.inmo.micro_utils.repos
|
|||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import androidx.core.database.*
|
||||||
|
import dev.inmo.micro_utils.repos.getLongOrNull
|
||||||
|
|
||||||
fun createTableQuery(
|
fun createTableQuery(
|
||||||
tableName: String,
|
tableName: String,
|
||||||
@@ -32,6 +34,11 @@ fun SQLiteDatabase.createTable(
|
|||||||
fun Cursor.getString(columnName: String): String = getString(
|
fun Cursor.getString(columnName: String): String = getString(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getStringOrNull(columnName: String): String? {
|
||||||
|
return getStringOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -39,6 +46,11 @@ fun Cursor.getString(columnName: String): String = getString(
|
|||||||
fun Cursor.getShort(columnName: String): Short = getShort(
|
fun Cursor.getShort(columnName: String): Short = getShort(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getShortOrNull(columnName: String): Short? {
|
||||||
|
return getShortOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -46,6 +58,11 @@ fun Cursor.getShort(columnName: String): Short = getShort(
|
|||||||
fun Cursor.getLong(columnName: String): Long = getLong(
|
fun Cursor.getLong(columnName: String): Long = getLong(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getLongOrNull(columnName: String): Long? {
|
||||||
|
return getLongOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -53,6 +70,23 @@ fun Cursor.getLong(columnName: String): Long = getLong(
|
|||||||
fun Cursor.getInt(columnName: String): Int = getInt(
|
fun Cursor.getInt(columnName: String): Int = getInt(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getIntOrNull(columnName: String): Int? {
|
||||||
|
return getIntOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
*/
|
||||||
|
fun Cursor.getFloat(columnName: String): Float = getFloat(
|
||||||
|
getColumnIndexOrThrow(columnName)
|
||||||
|
)
|
||||||
|
fun Cursor.getFloatOrNull(columnName: String): Float? {
|
||||||
|
return getFloatOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -60,6 +94,11 @@ fun Cursor.getInt(columnName: String): Int = getInt(
|
|||||||
fun Cursor.getDouble(columnName: String): Double = getDouble(
|
fun Cursor.getDouble(columnName: String): Double = getDouble(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getDoubleOrNull(columnName: String): Double? {
|
||||||
|
return getDoubleOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun SQLiteDatabase.select(
|
fun SQLiteDatabase.select(
|
||||||
table: String,
|
table: String,
|
||||||
|
@@ -23,13 +23,13 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
private val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
private val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||||
private val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
||||||
private val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
protected val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
||||||
|
|
||||||
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
||||||
|
@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
class ReadMapKeyValueRepo<Key, Value>(
|
class ReadMapKeyValueRepo<Key, Value>(
|
||||||
private val map: Map<Key, Value> = emptyMap()
|
protected val map: Map<Key, Value> = emptyMap()
|
||||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
) : ReadStandardKeyValueRepo<Key, Value> {
|
||||||
override suspend fun get(k: Key): Value? = map[k]
|
override suspend fun get(k: Key): Value? = map[k]
|
||||||
|
|
||||||
|
@@ -8,20 +8,22 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
open class TypedSerializer<T : Any>(
|
open class TypedSerializer<T : Any>(
|
||||||
kClass: KClass<T>,
|
kClass: KClass<T>,
|
||||||
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
presetSerializers: Map<String, KSerializer<out T>> = emptyMap(),
|
||||||
) : KSerializer<T> {
|
) : KSerializer<T> {
|
||||||
protected val serializers = presetSerializers.toMutableMap()
|
protected val serializers = presetSerializers.toMutableMap()
|
||||||
|
@ExperimentalSerializationApi
|
||||||
@InternalSerializationApi
|
@InternalSerializationApi
|
||||||
open override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||||
"TextSourceSerializer",
|
"TypedSerializer",
|
||||||
SerialKind.CONTEXTUAL
|
SerialKind.CONTEXTUAL
|
||||||
) {
|
) {
|
||||||
element("type", String.serializer().descriptor)
|
element("type", String.serializer().descriptor)
|
||||||
element("value", ContextualSerializer(kClass).descriptor)
|
element("value", ContextualSerializer(kClass).descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
@InternalSerializationApi
|
@InternalSerializationApi
|
||||||
open override fun deserialize(decoder: Decoder): T {
|
override fun deserialize(decoder: Decoder): T {
|
||||||
return decoder.decodeStructure(descriptor) {
|
return decoder.decodeStructure(descriptor) {
|
||||||
var type: String? = null
|
var type: String? = null
|
||||||
lateinit var result: T
|
lateinit var result: T
|
||||||
@@ -44,13 +46,15 @@ open class TypedSerializer<T : Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
@InternalSerializationApi
|
@InternalSerializationApi
|
||||||
protected open fun <O: T> CompositeEncoder.encode(value: O) {
|
protected open fun <O: T> CompositeEncoder.encode(value: O) {
|
||||||
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
|
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalSerializationApi
|
||||||
@InternalSerializationApi
|
@InternalSerializationApi
|
||||||
open override fun serialize(encoder: Encoder, value: T) {
|
override fun serialize(encoder: Encoder, value: T) {
|
||||||
encoder.encodeStructure(descriptor) {
|
encoder.encodeStructure(descriptor) {
|
||||||
val valueSerializer = value::class.serializer()
|
val valueSerializer = value::class.serializer()
|
||||||
val type = serializers.keys.first { serializers[it] == valueSerializer }
|
val type = serializers.keys.first { serializers[it] == valueSerializer }
|
||||||
@@ -69,6 +73,16 @@ open class TypedSerializer<T : Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InternalSerializationApi
|
||||||
|
operator fun <T : Any> TypedSerializer<T>.plusAssign(kClass: KClass<T>) {
|
||||||
|
include(kClass.simpleName!!, kClass.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
@InternalSerializationApi
|
||||||
|
operator fun <T : Any> TypedSerializer<T>.minusAssign(kClass: KClass<T>) {
|
||||||
|
exclude(kClass.simpleName!!)
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> TypedSerializer(
|
inline fun <reified T : Any> TypedSerializer(
|
||||||
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
||||||
) = TypedSerializer(T::class, presetSerializers)
|
) = TypedSerializer(T::class, presetSerializers)
|
||||||
|
Reference in New Issue
Block a user