mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-12-23 17:17:14 +00:00
commit
c5bb120280
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.18.1
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* Add `MapDiff`
|
||||||
|
* `Coroutines`:
|
||||||
|
* Add `SmartMutex`
|
||||||
|
|
||||||
## 0.18.0
|
## 0.18.0
|
||||||
|
|
||||||
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
|
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
|
||||||
|
@ -200,20 +200,18 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
|
|||||||
) = calculateDiff(other, strictComparison = true)
|
) = calculateDiff(other, strictComparison = true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
|
* Applies [diff] to [this] [MutableList]
|
||||||
* mutable list
|
|
||||||
*/
|
*/
|
||||||
fun <T> MutableList<T>.applyDiff(
|
fun <T> MutableList<T>.applyDiff(
|
||||||
source: Iterable<T>,
|
diff: Diff<T>
|
||||||
strictComparison: Boolean = false
|
) {
|
||||||
): Diff<T> = calculateDiff(source, strictComparison).also {
|
for (i in diff.removed.indices.sortedDescending()) {
|
||||||
for (i in it.removed.indices.sortedDescending()) {
|
removeAt(diff.removed[i].index)
|
||||||
removeAt(it.removed[i].index)
|
|
||||||
}
|
}
|
||||||
it.added.forEach { (i, t) ->
|
diff.added.forEach { (i, t) ->
|
||||||
add(i, t)
|
add(i, t)
|
||||||
}
|
}
|
||||||
it.replaced.forEach { (_, new) ->
|
diff.replaced.forEach { (_, new) ->
|
||||||
set(new.index, new.value)
|
set(new.index, new.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,17 +220,30 @@ fun <T> MutableList<T>.applyDiff(
|
|||||||
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
|
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
|
||||||
* mutable list
|
* mutable list
|
||||||
*/
|
*/
|
||||||
|
fun <T> MutableList<T>.applyDiff(
|
||||||
|
source: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> = calculateDiff(source, strictComparison).also {
|
||||||
|
applyDiff(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method call [calculateDiff] and then apply differences to [this]
|
||||||
|
* mutable list
|
||||||
|
*/
|
||||||
fun <T> MutableList<T>.applyDiff(
|
fun <T> MutableList<T>.applyDiff(
|
||||||
source: Iterable<T>,
|
source: Iterable<T>,
|
||||||
comparisonFun: (T?, T?) -> Boolean
|
comparisonFun: (T?, T?) -> Boolean
|
||||||
): Diff<T> = calculateDiff(source, comparisonFun).also {
|
): Diff<T> = calculateDiff(source, comparisonFun).also {
|
||||||
for (i in it.removed.indices.sortedDescending()) {
|
applyDiff(it)
|
||||||
removeAt(it.removed[i].index)
|
|
||||||
}
|
|
||||||
it.added.forEach { (i, t) ->
|
|
||||||
add(i, t)
|
|
||||||
}
|
|
||||||
it.replaced.forEach { (_, new) ->
|
|
||||||
set(new.index, new.value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the
|
||||||
|
* [Diff.replaced] values will be reversed too
|
||||||
|
*/
|
||||||
|
fun <T> Diff<T>.reversed() = Diff(
|
||||||
|
removed = added,
|
||||||
|
replaced = replaced.map { it.second to it.first },
|
||||||
|
added = removed
|
||||||
|
)
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains diff based on the comparison of objects with the same [K].
|
||||||
|
*
|
||||||
|
* @param removed Contains map with keys removed from parent map
|
||||||
|
* @param changed Contains map with keys values changed new map in comparison with old one
|
||||||
|
* @param added Contains map with new keys and values
|
||||||
|
*/
|
||||||
|
data class MapDiff<K, V> @Warning(warning) constructor(
|
||||||
|
val removed: Map<K, V>,
|
||||||
|
val changed: Map<K, Pair<V, V>>,
|
||||||
|
val added: Map<K, V>
|
||||||
|
) {
|
||||||
|
fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty()
|
||||||
|
inline fun isNotEmpty() = !isEmpty()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
|
||||||
|
fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <K, V> createCompareFun(
|
||||||
|
strictComparison: Boolean
|
||||||
|
): (K, V, V) -> Boolean = if (strictComparison) {
|
||||||
|
{ _, first, second -> first === second }
|
||||||
|
} else {
|
||||||
|
{ _, first, second -> first == second }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
|
||||||
|
*
|
||||||
|
* @param compareFun Will be used to determine changed values
|
||||||
|
*/
|
||||||
|
fun <K, V> Map<K, V>.diff(
|
||||||
|
other: Map<K, V>,
|
||||||
|
compareFun: (K, V, V) -> Boolean
|
||||||
|
): MapDiff<K, V> {
|
||||||
|
val removed: Map<K, V> = (keys - other.keys).associateWith {
|
||||||
|
getValue(it)
|
||||||
|
}
|
||||||
|
val added: Map<K, V> = (other.keys - keys).associateWith {
|
||||||
|
other.getValue(it)
|
||||||
|
}
|
||||||
|
val changed = keys.intersect(other.keys).mapNotNull {
|
||||||
|
val old = getValue(it)
|
||||||
|
val new = other.getValue(it)
|
||||||
|
if (compareFun(it, old, new)) {
|
||||||
|
return@mapNotNull null
|
||||||
|
} else {
|
||||||
|
it to (old to new)
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
return MapDiff(
|
||||||
|
removed,
|
||||||
|
changed,
|
||||||
|
added
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
|
||||||
|
*
|
||||||
|
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
|
||||||
|
* `equals` will be used
|
||||||
|
*/
|
||||||
|
fun <K, V> Map<K, V>.diff(
|
||||||
|
other: Map<K, V>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): MapDiff<K, V> = diff(
|
||||||
|
other,
|
||||||
|
compareFun = createCompareFun(strictComparison)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will apply [mapDiff] to [this] [MutableMap]
|
||||||
|
*/
|
||||||
|
fun <K, V> MutableMap<K, V>.applyDiff(
|
||||||
|
mapDiff: MapDiff<K, V>
|
||||||
|
) {
|
||||||
|
mapDiff.apply {
|
||||||
|
removed.keys.forEach { remove(it) }
|
||||||
|
changed.forEach { (k, oldNew) ->
|
||||||
|
put(k, oldNew.second)
|
||||||
|
}
|
||||||
|
added.forEach { (k, new) ->
|
||||||
|
put(k, new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will apply changes with [from] map into [this] one
|
||||||
|
*
|
||||||
|
* @param compareFun Will be used to determine changed values
|
||||||
|
*
|
||||||
|
* @return [MapDiff] applied to [this] [MutableMap]
|
||||||
|
*/
|
||||||
|
fun <K, V> MutableMap<K, V>.applyDiff(
|
||||||
|
from: Map<K, V>,
|
||||||
|
compareFun: (K, V, V) -> Boolean
|
||||||
|
): MapDiff<K, V> {
|
||||||
|
return diff(from, compareFun).also {
|
||||||
|
applyDiff(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will apply changes with [from] map into [this] one
|
||||||
|
*
|
||||||
|
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
|
||||||
|
* `equals` will be used
|
||||||
|
*
|
||||||
|
* @return [MapDiff] applied to [this] [MutableMap]
|
||||||
|
*/
|
||||||
|
fun <K, V> MutableMap<K, V>.applyDiff(
|
||||||
|
from: Map<K, V>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): MapDiff<K, V> = applyDiff(
|
||||||
|
from,
|
||||||
|
compareFun = createCompareFun(strictComparison)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the
|
||||||
|
* [MapDiff.changed] values will be reversed too
|
||||||
|
*/
|
||||||
|
fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff(
|
||||||
|
removed = added,
|
||||||
|
changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first },
|
||||||
|
added = removed
|
||||||
|
)
|
@ -0,0 +1,136 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the
|
||||||
|
* [SmartMutex] state.
|
||||||
|
*
|
||||||
|
* There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need
|
||||||
|
* [Mutable] [SmartMutex]. Otherwise, [Immutable].
|
||||||
|
*
|
||||||
|
* Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its
|
||||||
|
* [Mutable] creator
|
||||||
|
*/
|
||||||
|
sealed interface SmartMutex {
|
||||||
|
val lockStateFlow: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * True - locked
|
||||||
|
* * False - unlocked
|
||||||
|
*/
|
||||||
|
val isLocked: Boolean
|
||||||
|
get() = lockStateFlow.value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow]
|
||||||
|
*/
|
||||||
|
class Immutable(override val lockStateFlow: StateFlow<Boolean>) : SmartMutex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable variant of [SmartMutex]. With that variant you may [lock] and [unlock]. Besides, you may create
|
||||||
|
* [Immutable] variant of [this] instance with [immutable] factory
|
||||||
|
*
|
||||||
|
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
|
||||||
|
*/
|
||||||
|
class Mutable(locked: Boolean = false) : SmartMutex {
|
||||||
|
private val _lockStateFlow = MutableStateFlow<Boolean>(locked)
|
||||||
|
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
|
||||||
|
|
||||||
|
private val internalChangesMutex = Mutex()
|
||||||
|
|
||||||
|
fun immutable() = Immutable(lockStateFlow)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will
|
||||||
|
* wait for [isLocked] == false and then try to lock
|
||||||
|
*/
|
||||||
|
suspend fun lock() {
|
||||||
|
do {
|
||||||
|
waitUnlock()
|
||||||
|
val shouldContinue = internalChangesMutex.withLock {
|
||||||
|
if (_lockStateFlow.value) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
_lockStateFlow.value = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (shouldContinue && currentCoroutineContext().isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will try to lock this [SmartMutex] immediataly
|
||||||
|
*
|
||||||
|
* @return True if lock was successful. False otherwise
|
||||||
|
*/
|
||||||
|
suspend fun tryLock(): Boolean {
|
||||||
|
return if (!_lockStateFlow.value) {
|
||||||
|
internalChangesMutex.withLock {
|
||||||
|
if (!_lockStateFlow.value) {
|
||||||
|
_lockStateFlow.value = true
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [isLocked] == true - will change it to false and return true. If current call will not unlock this
|
||||||
|
* [SmartMutex] - false
|
||||||
|
*/
|
||||||
|
suspend fun unlock(): Boolean {
|
||||||
|
return if (_lockStateFlow.value) {
|
||||||
|
internalChangesMutex.withLock {
|
||||||
|
if (_lockStateFlow.value) {
|
||||||
|
_lockStateFlow.value = false
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
lock()
|
||||||
|
try {
|
||||||
|
return action()
|
||||||
|
} finally {
|
||||||
|
unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false.
|
||||||
|
*
|
||||||
|
* Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact
|
||||||
|
* that some other parties may lock it again
|
||||||
|
*/
|
||||||
|
suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it }
|
@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.18.0
|
version=0.18.1
|
||||||
android_code_version=191
|
android_code_version=192
|
||||||
|
Loading…
Reference in New Issue
Block a user