Compare commits

...

17 Commits

Author SHA1 Message Date
438fefa7a3 fixes 2023-05-09 15:09:50 +06:00
5d74eac814 Update libs.versions.toml 2023-05-09 01:41:25 +06:00
9fb62e1e25 start 0.18.2 and make json of Startup customizable 2023-05-09 01:38:04 +06:00
3e366ea73b Update libs.versions.toml 2023-05-06 01:54:15 +06:00
0ff895bffa Update gradle.properties 2023-05-06 01:53:33 +06:00
c5bb120280 Merge pull request #248 from InsanusMokrassar/0.18.1
0.18.1
2023-05-05 22:26:53 +06:00
4b26a92b37 improve old diff API 2023-05-05 12:01:29 +06:00
0a8453b4d2 add MapDiff#reversed and MutableMap#applyDiff(MapDiff) 2023-05-05 11:58:27 +06:00
c9872a61b6 now applyDiff will return its receiver 2023-05-05 11:50:23 +06:00
149a1aa278 fix in new applyDiff 2023-05-05 11:33:22 +06:00
13d0e1b682 add MapDiff utils 2023-05-04 21:12:26 +06:00
6f9be2a9f8 start 0.18.1 and add SmartMutex 2023-05-03 23:47:05 +06:00
93ba98d993 Merge pull request #246 from InsanusMokrassar/revert-245-renovate/ksp
Revert "Update ksp to v1.8.21-1.0.11"
2023-04-28 00:34:20 +06:00
940ee3df06 Revert "Update ksp to v1.8.21-1.0.11" 2023-04-28 00:34:10 +06:00
2e7bab10fd Merge pull request #245 from InsanusMokrassar/renovate/ksp
Update ksp to v1.8.21-1.0.11
2023-04-28 00:14:25 +06:00
renovate[bot]
3ed70a37ea Update ksp to v1.8.21-1.0.11 2023-04-27 18:10:41 +00:00
fe8f80b9d9 Merge pull request #243 from InsanusMokrassar/0.18.0
0.18.0
2023-04-27 17:08:42 +06:00
8 changed files with 379 additions and 22 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## 0.18.2
* `Startup`:
* Now internal `Json` is fully customizable
## 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**

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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 }

View File

@@ -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.2
android_code_version=191 android_code_version=193

View File

@@ -2,6 +2,7 @@ plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "application" id "application"
id "com.google.devtools.ksp"
} }
apply from: "$mppJvmJsLinuxMingwProjectPresetPath" apply from: "$mppJvmJsLinuxMingwProjectPresetPath"
@@ -11,6 +12,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api internalProject("micro_utils.startup.plugin") api internalProject("micro_utils.startup.plugin")
api internalProject("micro_utils.koin")
} }
} }
commonTest { commonTest {
@@ -29,3 +31,10 @@ java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
dependencies {
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
}
ksp {
}

View File

@@ -0,0 +1,38 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: StartLauncherPlugin.kt
package dev.inmo.micro_utils.startup.launcher
import kotlin.Boolean
import kotlinx.serialization.json.Json
import org.koin.core.Koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
/**
* @return Definition by key "baseJsonProvider"
*/
public val Scope.baseJsonProvider: Json?
get() = getOrNull(named("baseJsonProvider"))
/**
* @return Definition by key "baseJsonProvider"
*/
public val Koin.baseJsonProvider: Json?
get() = getOrNull(named("baseJsonProvider"))
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "baseJsonProvider"
*/
public fun Module.baseJsonProviderSingle(createdAtStart: Boolean = false, definition: Definition<Json>):
KoinDefinition<Json> = single(named("baseJsonProvider"), createdAtStart = createdAtStart, definition
= definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "baseJsonProvider"
*/
public fun Module.baseJsonProviderFactory(definition: Definition<Json>): KoinDefinition<Json> =
factory(named("baseJsonProvider"), definition = definition)

View File

@@ -1,9 +1,11 @@
@file:GenerateKoinDefinition("baseJsonProvider", Json::class)
package dev.inmo.micro_utils.startup.launcher package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.i import dev.inmo.kslog.common.i
import dev.inmo.kslog.common.taggedLogger import dev.inmo.kslog.common.taggedLogger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin
import dev.inmo.micro_utils.startup.plugin.StartPlugin import dev.inmo.micro_utils.startup.plugin.StartPlugin
@@ -13,9 +15,10 @@ import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerialFormat
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.modules.SerializersModule
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.KoinApplication import org.koin.core.KoinApplication
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
@@ -35,7 +38,20 @@ object StartLauncherPlugin : StartPlugin {
single { rawJsonObject } single { rawJsonObject }
single { config } single { config }
single { CoroutineScope(Dispatchers.Default) } single { CoroutineScope(Dispatchers.Default) }
single { defaultJson } binds arrayOf(StringFormat::class, SerialFormat::class) single {
val serializersModules = getAll<SerializersModule>().distinct()
val baseJson = baseJsonProvider ?: defaultJson
if (serializersModules.isEmpty()) {
baseJson
} else {
Json(baseJson) {
serializersModule = SerializersModule {
include(baseJson.serializersModule)
serializersModules.forEach { include(it) }
}
}
}
} binds arrayOf(StringFormat::class, SerialFormat::class)
includes( includes(
config.plugins.mapNotNull { config.plugins.mapNotNull {