@file:Suppress("NOTHING_TO_INLINE") package dev.inmo.micro_utils.common private inline fun getObject( additional: MutableList, iterator: Iterator ): T? = when { additional.isNotEmpty() -> additional.removeFirst() iterator.hasNext() -> iterator.next() else -> null } /** * Diff object which contains information about differences between two [Iterable]s * * @see calculateDiff */ data class Diff internal constructor( val removed: List>, /** * Old-New values pairs */ val replaced: List, IndexedValue>>, val added: List> ) private inline fun performChanges( potentialChanges: MutableList?, IndexedValue?>>, additionalsInOld: MutableList, additionalsInNew: MutableList, changedList: MutableList, IndexedValue>>, removedList: MutableList>, addedList: MutableList>, 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> 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 Iterable.calculateDiff( other: Iterable, strictComparison: Boolean = false ): Diff { var i = -1 var j = -1 val additionalInOld = mutableListOf() val additionalInNew = mutableListOf() val oldIterator = iterator() val newIterator = other.iterator() val potentiallyChangedObjects = mutableListOf?, IndexedValue?>>() val changedObjects = mutableListOf, IndexedValue>>() val addedObjects = mutableListOf>() val removedObjects = mutableListOf>() 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> }) 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 Diff(old: Iterable, new: Iterable) = old.calculateDiff(new) inline fun StrictDiff(old: Iterable, new: Iterable) = old.calculateDiff(new, true) /** * This method call [calculateDiff] with strict mode enabled */ inline fun Iterable.calculateStrictDiff( other: Iterable ) = calculateDiff(other, true) /** * Compare one-to-one */ @Deprecated("Will be removed or replaced with some new function. Use calculateDiff instead") inline fun Iterable.syncWith( other: Iterable, noinline removed: (List) -> Unit = {}, noinline added: (List) -> Unit = {} ) { calculateDiff(other).also { removed(it.removed.map { it.value }) added(it.added.map { it.value }) } }