Compare commits

..

12 Commits

14 changed files with 287 additions and 12 deletions

View File

@@ -1,5 +1,20 @@
# Changelog
## 0.17.4
* `Serialization`:
* `Mapper`:
* Module inited
* `Versions`:
* `Compose`: `1.3.1-rc02` -> `1.3.1`
## 0.17.3
* `Common`:
* Add `fixed` extensions for `Float` and `Double`
* New function `emptyDiff`
* Now you may pass custom `comparisonFun` to all `diff` functions
## 0.17.2
* `FSM`:

View File

@@ -34,7 +34,11 @@ data class Diff<T> internal constructor(
*/
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
)
) {
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
}
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
private inline fun <T> performChanges(
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
@@ -43,14 +47,14 @@ private inline fun <T> performChanges(
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
removedList: MutableList<IndexedValue<T>>,
addedList: MutableList<IndexedValue<T>>,
strictComparison: Boolean
comparisonFun: (T?, T?) -> 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)
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll(
potentialChanges.take(i).mapNotNull {
@@ -104,7 +108,7 @@ private inline fun <T> performChanges(
*/
fun <T> Iterable<T>.calculateDiff(
other: Iterable<T>,
strictComparison: Boolean = false
comparisonFun: (T?, T?) -> Boolean
): Diff<T> {
var i = -1
var j = -1
@@ -132,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
}
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
comparisonFun(oldObject, newObject) -> {
changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
@@ -143,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
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)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
i -= (additionalInOld.size - previousOldsAdditionsSize)
j -= (additionalInNew.size - previousNewsAdditionsSize)
}
}
}
potentiallyChangedObjects.add(null to null)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
}
/**
* 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> = calculateDiff(
other,
comparisonFun = if (strictComparison) {
{ t1, t2 ->
t1 === t2
}
} else {
{ t1, t2 ->
t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
}
}
)
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(other, strictComparison)
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
noinline comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(other, comparisonFun)
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, strictComparison = false)
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
/**
@@ -187,3 +217,22 @@ fun <T> MutableList<T>.applyDiff(
set(new.index, new.value)
}
}
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(source, comparisonFun).also {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.common
val FixedSignsRange = 0 .. 100
expect fun Float.fixed(signs: Int): Float
expect fun Double.fixed(signs: Int): Double

View File

@@ -0,0 +1,4 @@
package dev.inmo.micro_utils.common
actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.common
import java.math.BigDecimal
import java.math.RoundingMode
actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toFloat();
actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toDouble();

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.17.2
android_code_version=184
version=0.17.4
android_code_version=186

View File

@@ -6,7 +6,7 @@ kt-coroutines = "1.6.4"
kslog = "1.0.0"
jb-compose = "1.3.1-rc01"
jb-compose = "1.3.1"
jb-exposed = "0.41.1"
jb-dokka = "1.7.20"

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,99 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.serializer
import kotlin.reflect.KClass
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
inline fun <reified I : Any, O> SerializationStrategy<I>.mapSerialization(
noinline serialize: (O) -> I
) = MapperSerializationStrategy(
this,
serialize
)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
inline fun <reified I : Any, O> DeserializationStrategy<I>.mapDeserialization(
noinline deserialize: (I) -> O
) = MapperDeserializationStrategy(
this,
deserialize
)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
inline fun <reified I : Any, O> KSerializer<I>.mapFullSerialization(
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = MapperSerializer(
this,
serialize,
deserialize
)
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapSerialization(
serializer: SerializationStrategy<I> = serializer(),
noinline serialize: (O) -> I
) = serializer.mapSerialization(serialize)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapDeserialization(
serializer: DeserializationStrategy<I> = serializer(),
noinline deserialize: (I) -> O
) = serializer.mapDeserialization(deserialize)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapFullSerialization(
serializer: KSerializer<I> = serializer(),
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = serializer.mapFullSerialization(serialize, deserialize)
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
inline fun <reified I : Any, O> mappedSerializationStrategy(
noinline serialize: (O) -> I,
) = serializer<I>().mapSerialization(serialize)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
inline fun <reified I : Any, O> mappedDeserializationStrategy(
noinline deserialize: (I) -> O
) = serializer<I>().mapDeserialization(deserialize)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
inline fun <reified I : Any, O> mappedSerializer(
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = serializer<I>().mapFullSerialization(serialize, deserialize)

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have deserializable type [I] and want to map it to some [O] in process of
* deserialization
*
* @param base Serializer for [I]
* @param deserialize Will be used in [deserialize] method to convert deserialized by [base] [I] to [O]
*/
open class MapperDeserializationStrategy<I, O>(
private val base: DeserializationStrategy<I>,
private val deserialize: (I) -> O
) : DeserializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun deserialize(decoder: Decoder): O {
return deserialize(base.deserialize(decoder))
}
}

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have serializable type [I] and want to map it to some [O] in process of
* serialization
*
* @param base Serializer for [I]
* @param serialize Will be used in [serialize] method to convert incoming [O] to [I] and serialize with [base]
*/
open class MapperSerializationStrategy<I, O>(
private val base: SerializationStrategy<I>,
private val serialize: (O) -> I
) : SerializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun serialize(encoder: Encoder, value: O) {
base.serialize(encoder, serialize(value))
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have serializable type [I] and want to map it to some [O] in process of
* serialization/deserialization
*
* @param base Serializer for [I]
* @param serialize Will be used in [serialize] method to convert incoming [O] to [I] and serialize with [base]
* @param deserialize Will be used in [deserialize] method to convert deserialized by [base] [I] to [O]
*/
open class MapperSerializer<I, O>(
private val base: KSerializer<I>,
private val serialize: (O) -> I,
private val deserialize: (I) -> O
) : KSerializer<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun deserialize(decoder: Decoder): O {
return deserialize(base.deserialize(decoder))
}
override fun serialize(encoder: Encoder, value: O) {
base.serialize(encoder, serialize(value))
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.serialization.mapper"/>

View File

@@ -37,6 +37,7 @@ String[] includes = [
":serialization:base64",
":serialization:encapsulator",
":serialization:typed_serializer",
":serialization:mapper",
":startup:plugin",
":startup:launcher",