mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-11-26 12:08:45 +00:00
commit
d242d8dcf4
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Serialization`: `1.0.0` -> `1.0.1`
|
||||||
|
* `Common`
|
||||||
|
* Full rework of `DiffUtils`
|
||||||
|
* Data class `Diff` has been added
|
||||||
|
* Extension `Iterable#calculateDiff` has been added
|
||||||
|
* Extension `Iterable#calculateStrictDiff` as replacement for `Iterable#calculateDiff` with
|
||||||
|
`strictComparison` mode enabled
|
||||||
|
* Functions `Diff` (as analog of `Iterable#calculateDiff`) and `StrictDiff` (as analog of
|
||||||
|
`Iterable#calculateStrictDiff`)
|
||||||
|
* `Coroutines`
|
||||||
|
* `BroadcastFlow` now is deprecated
|
||||||
|
* `BroadcastStateFlow` now is deprecated
|
||||||
|
* New extensions for `Flow`s:
|
||||||
|
* `Flow#subscribe`
|
||||||
|
* `Flow#subscribeSafely`
|
||||||
|
* `Flow#subscribeSafelyWithoutExceptions`
|
||||||
|
|
||||||
## 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
* `Versions`
|
* `Versions`
|
||||||
|
@ -1,10 +1,166 @@
|
|||||||
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
||||||
package dev.inmo.micro_utils.common
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
fun <T> Iterable<T>.syncWith(
|
private inline fun <T> getObject(
|
||||||
other: Iterable<T>,
|
additional: MutableList<T>,
|
||||||
removed: (List<T>) -> Unit = {},
|
iterator: Iterator<T>
|
||||||
added: (List<T>) -> Unit = {}
|
): T? = when {
|
||||||
) {
|
additional.isNotEmpty() -> additional.removeFirst()
|
||||||
removed(filter { it !in other })
|
iterator.hasNext() -> iterator.next()
|
||||||
added(other.filter { it !in this })
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diff object which contains information about differences between two [Iterable]s
|
||||||
|
*
|
||||||
|
* @see calculateDiff
|
||||||
|
*/
|
||||||
|
data class Diff<T> internal constructor(
|
||||||
|
val removed: List<IndexedValue<T>>,
|
||||||
|
/**
|
||||||
|
* Old-New values pairs
|
||||||
|
*/
|
||||||
|
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
|
val added: List<IndexedValue<T>>
|
||||||
|
)
|
||||||
|
|
||||||
|
private inline fun <T> performChanges(
|
||||||
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
|
additionalsInOld: MutableList<T>,
|
||||||
|
additionalsInNew: MutableList<T>,
|
||||||
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
|
strictComparison: Boolean
|
||||||
|
) {
|
||||||
|
var i = -1
|
||||||
|
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||||
|
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||||
|
i++
|
||||||
|
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
||||||
|
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
||||||
|
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||||
|
changedList.addAll(
|
||||||
|
potentialChanges.take(i).mapNotNull {
|
||||||
|
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val newPotentials = potentialChanges.drop(i).take(potentialChanges.size - i)
|
||||||
|
when {
|
||||||
|
oldOneEqualToNewObject -> {
|
||||||
|
newPotentials.first().second ?.let { addedList.add(it) }
|
||||||
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
|
addedList.add(newOne!!)
|
||||||
|
oldOne ?.let { additionalsInOld.add(oldOne.value) }
|
||||||
|
}
|
||||||
|
if (newPotentials.size > 1) {
|
||||||
|
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOneEqualToOldObject -> {
|
||||||
|
newPotentials.first().first ?.let { removedList.add(it) }
|
||||||
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
|
removedList.add(oldOne!!)
|
||||||
|
newOne ?.let { additionalsInNew.add(newOne.value) }
|
||||||
|
}
|
||||||
|
if (newPotentials.size > 1) {
|
||||||
|
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
potentialChanges.clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (potentialChanges.isNotEmpty() && potentialChanges.last().let { it.first == null && it.second == null }) {
|
||||||
|
potentialChanges.dropLast(1).forEach { (old, new) ->
|
||||||
|
when {
|
||||||
|
old != null && new != null -> changedList.add(old to new)
|
||||||
|
old != null -> removedList.add(old)
|
||||||
|
new != null -> addedList.add(new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculating [Diff] object
|
||||||
|
*
|
||||||
|
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
|
||||||
|
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
|
||||||
|
*/
|
||||||
|
fun <T> Iterable<T>.calculateDiff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> {
|
||||||
|
var i = -1
|
||||||
|
var j = -1
|
||||||
|
|
||||||
|
val additionalInOld = mutableListOf<T>()
|
||||||
|
val additionalInNew = mutableListOf<T>()
|
||||||
|
|
||||||
|
val oldIterator = iterator()
|
||||||
|
val newIterator = other.iterator()
|
||||||
|
|
||||||
|
val potentiallyChangedObjects = mutableListOf<Pair<IndexedValue<T>?, IndexedValue<T>?>>()
|
||||||
|
val changedObjects = mutableListOf<Pair<IndexedValue<T>, IndexedValue<T>>>()
|
||||||
|
val addedObjects = mutableListOf<IndexedValue<T>>()
|
||||||
|
val removedObjects = mutableListOf<IndexedValue<T>>()
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
|
||||||
|
val oldObject = getObject(additionalInOld, oldIterator)
|
||||||
|
val newObject = getObject(additionalInNew, newIterator)
|
||||||
|
|
||||||
|
if (oldObject == null && newObject == null) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||||
|
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
|
||||||
|
potentiallyChangedObjects.clear()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||||
|
val previousOldsAdditionsSize = additionalInOld.size
|
||||||
|
val previousNewsAdditionsSize = additionalInNew.size
|
||||||
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
|
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||||
|
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
potentiallyChangedObjects.add(null to null)
|
||||||
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||||
|
|
||||||
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method call [calculateDiff] with strict mode enabled
|
||||||
|
*/
|
||||||
|
inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||||
|
other: Iterable<T>
|
||||||
|
) = calculateDiff(other, strictComparison = true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare one-to-one
|
||||||
|
*/
|
||||||
|
@Deprecated("Will be removed or replaced with some new function. Use calculateDiff instead")
|
||||||
|
inline fun <T> Iterable<T>.syncWith(
|
||||||
|
other: Iterable<T>,
|
||||||
|
noinline removed: (List<T>) -> Unit = {},
|
||||||
|
noinline added: (List<T>) -> Unit = {}
|
||||||
|
) {
|
||||||
|
calculateDiff(other).also {
|
||||||
|
removed(it.removed.map { it.value })
|
||||||
|
added(it.added.map { it.value })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlin.math.floor
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class DiffUtilsTests {
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleRemoveWorks() {
|
||||||
|
val oldList = (0 until 10).toList()
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
if (i + count > oldList.lastIndex) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val removedSublist = oldList.subList(i, i + count)
|
||||||
|
oldList.calculateDiff(oldList - removedSublist).apply {
|
||||||
|
assertEquals(
|
||||||
|
removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||||
|
removed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleAddWorks() {
|
||||||
|
val oldList = (0 until 10).map { it.toString() }
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
if (i + count > oldList.lastIndex) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
|
||||||
|
val mutable = oldList.toMutableList()
|
||||||
|
mutable.addAll(i, addedSublist)
|
||||||
|
oldList.calculateDiff(mutable).apply {
|
||||||
|
assertEquals(
|
||||||
|
addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) },
|
||||||
|
added
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThatSimpleChangesWorks() {
|
||||||
|
val oldList = (0 until 10).map { it.toString() }
|
||||||
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
|
for (step in 0 until oldList.size) {
|
||||||
|
for ((i, v) in withIndex) {
|
||||||
|
val mutable = oldList.toMutableList()
|
||||||
|
val changes = (
|
||||||
|
if (step == 0) i until oldList.size else (i until oldList.size step step)
|
||||||
|
).map { index ->
|
||||||
|
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
|
||||||
|
mutable[index] = it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldList.calculateDiff(mutable).apply {
|
||||||
|
assertEquals(
|
||||||
|
changes,
|
||||||
|
replaced
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.channels.*
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
fun <T> BroadcastFlow(
|
fun <T> BroadcastFlow(
|
||||||
internalChannelSize: Int = Channel.BUFFERED
|
internalChannelSize: Int = Channel.BUFFERED
|
||||||
): BroadcastFlow<T> {
|
): BroadcastFlow<T> {
|
||||||
@ -15,6 +16,7 @@ fun <T> BroadcastFlow(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
class BroadcastFlow<T> internal constructor(
|
class BroadcastFlow<T> internal constructor(
|
||||||
private val channel: BroadcastChannel<T>,
|
private val channel: BroadcastChannel<T>,
|
||||||
private val flow: Flow<T>
|
private val flow: Flow<T>
|
||||||
|
@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
const val defaultBroadcastStateFlowReplayCacheSize = 1
|
const val defaultBroadcastStateFlowReplayCacheSize = 1
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
class BroadcastStateFlow<T> internal constructor(
|
class BroadcastStateFlow<T> internal constructor(
|
||||||
parentFlow: Flow<T>,
|
parentFlow: Flow<T>,
|
||||||
initial: T,
|
initial: T,
|
||||||
@ -34,17 +35,20 @@ class BroadcastStateFlow<T> internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
fun <T> BroadcastChannel<T>.asStateFlow(
|
fun <T> BroadcastChannel<T>.asStateFlow(
|
||||||
value: T,
|
value: T,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||||
): StateFlow<T> = BroadcastStateFlow(asFlow(), value, replayCacheSize, scope)
|
): StateFlow<T> = BroadcastStateFlow(asFlow(), value, replayCacheSize, scope)
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
fun <T> BroadcastChannel<T?>.asStateFlow(
|
fun <T> BroadcastChannel<T?>.asStateFlow(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||||
): StateFlow<T?> = asStateFlow(null, scope, replayCacheSize)
|
): StateFlow<T?> = asStateFlow(null, scope, replayCacheSize)
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
fun <T> broadcastStateFlow(
|
fun <T> broadcastStateFlow(
|
||||||
initial: T, scope: CoroutineScope,
|
initial: T, scope: CoroutineScope,
|
||||||
channelSize: Int = Channel.BUFFERED,
|
channelSize: Int = Channel.BUFFERED,
|
||||||
@ -55,6 +59,7 @@ fun <T> broadcastStateFlow(
|
|||||||
it to it.asStateFlow(initial, scope, replayCacheSize)
|
it to it.asStateFlow(initial, scope, replayCacheSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow")
|
||||||
fun <T> broadcastStateFlow(
|
fun <T> broadcastStateFlow(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
channelSize: Int = Channel.BUFFERED,
|
channelSize: Int = Channel.BUFFERED,
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
@file:Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend (T) -> Unit) = onEach(block).launchIn(scope)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use [subscribe], but all [block]s will be called inside of [safely] function.
|
||||||
|
* Use [onException] to set up your reaction for [Throwable]s
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribeSafely(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
noinline onException: ExceptionHandler<Unit> = { throw it },
|
||||||
|
noinline block: suspend (T) -> Unit
|
||||||
|
) = subscribe(scope) {
|
||||||
|
safely(onException) {
|
||||||
|
block(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped
|
||||||
|
*/
|
||||||
|
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||||
|
scope: CoroutineScope,
|
||||||
|
noinline block: suspend (T) -> Unit
|
||||||
|
) = subscribeSafely(
|
||||||
|
scope,
|
||||||
|
{},
|
||||||
|
block
|
||||||
|
)
|
@ -6,7 +6,7 @@ kotlin.incremental.js=true
|
|||||||
|
|
||||||
kotlin_version=1.4.10
|
kotlin_version=1.4.10
|
||||||
kotlin_coroutines_version=1.4.0
|
kotlin_coroutines_version=1.4.0
|
||||||
kotlin_serialisation_core_version=1.0.0
|
kotlin_serialisation_core_version=1.0.1
|
||||||
kotlin_exposed_version=0.28.1
|
kotlin_exposed_version=0.28.1
|
||||||
|
|
||||||
ktor_version=1.4.1
|
ktor_version=1.4.1
|
||||||
@ -19,4 +19,4 @@ github_release_plugin_version=2.2.12
|
|||||||
uuidVersion=0.2.2
|
uuidVersion=0.2.2
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.2.3
|
version=0.2.4
|
||||||
|
Loading…
Reference in New Issue
Block a user