Compare commits

...
Author SHA1 Message Date
amo c7767e68bc Merge branch 'main' into 'observationally-overboard'
Adding an implementation of Observable Collections & utilities:

See merge request cable-mc/cobblemon!1109
2024-09-07 12:54:52 +00:00
amo 444a70e1fa e 2024-09-07 13:52:44 +01:00
Eight 8a58142b62 Implemented ObservableSubList.kt, tests, & updated iterators:
1. ObservableSubList:
    - Switched from returning kotlin sublist to an observable implementation that can still be subscribed to.
    - Split MutableObservableSubList functions to match the other observable collections.
    - Implemented iterators.

2. Tests:
    - Implemented tests for the current functionality to help with future iterations & reliability.
    - AllCollectionTests.kt
    - ConstructionTests.kt
    - EmittingReferenceTypeTests.kt
    - EmittingTests.kt
    - IteratorTests.kt

3. Updated iterators:
    - Removed redundant construction of kotlin iterators with observable collection iterators where possible.
    - Fixed a possible out of range exception when attempting to get an element from an empty sublist iterator & an observable list with no element below the fromIndex.
2024-08-05 17:37:48 -07:00
Eight a108119420 Merge branch 'master' into 'main'
Observable collections & utilities:

See merge request SomeGuyEight/cobblemon!1
2024-08-05 06:19:52 +00:00
Eight 6572478ee1 Adding an implementation of Observable Collections & utilities:
These are collection implementations that extend the observable interface & api. The collections can manage all observables within the stored elements & entries. Each will emit with any mutation to the elements contained or a mutation of the collection like addition, removal, set, or swap. Each collection implements a subscription object that holds the subscriptions of all possible observables for the associated collection.

    Each collection behaves as the kotlin implementation is expected to aside from the sublist. The sublist behaves as the kotlin equivalent until the backing list is mutated. When the backing list of the kotlin sublist is mutated, accessing the sublist will throw an exception, but the MutableObservableSubList.kt will continue to stay synced. It will continue to emit & return values from the backing list for the index window it is constructed with.

1. Observable Collections implemented:
    A) ObservableCollection.kt & MutableObservableCollection.kt interfaces
        - ObservableSet.kt & MutableObservableSet.kt implementations
        - ObservableList.kt & MutableObservableList.kt implementations
        - MutableObservableSubList.kt implementation
    B) ObservableMap.kt & MutableObservableMap.kt

2. Observable Subscriptions:
    A) CollectionSubscription.kt interface
        - SetSubscription.kt
        - ListSubscription.kt
        - IndexedListSubscription
    B) MapSubscription.kt

3. Iterators:
    A) ObservableSetIterator.kt & MutableObservableListIterator.kt
    B) ObservableListIterator.kt & MutableObservableListIterator.kt
    C) MutableObservableSubListIterator.kt
    D) ObservableMapIterator.kt & MutableObservableMapIterator.kt

4. Nbt serialization utils:
    A) ObservableSetSerialization.kt
    B) ObservableListSerialization.kt
    C) ObservableMapSerialization.kt
2024-08-04 22:56:22 -07:00
44 changed files with 4124 additions and 0 deletions
@@ -33,6 +33,7 @@ import java.io.PrintWriter
* @since April 1st, 2022.
*/
object JsonManifestWalker {
// im walkin ere
/**
* Builds a manifest at [manifestPath] using the files inside the same folder.
*
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.CollectionSubscription
interface MutableObservableCollection<T, C : Collection<T>> :
ObservableCollection<T, C>,
MutableCollection<T> {
fun subscribe(
priority: Priority = Priority.NORMAL,
anyChangeHandler: (Pair<C, T>) -> Unit,
additionHandler: ((Pair<C, T>) -> Unit)? = null,
removalHandler: ((Pair<C, T>) -> Unit)? = null,
): CollectionSubscription<T, C>
override fun add(element: T): Boolean
fun addIf(element: T, predicate: (C) -> Boolean): Boolean
override fun remove(element: T): Boolean
fun removeIf(predicate: (T) -> Boolean): Boolean
override fun addAll(elements: Collection<T>): Boolean
override fun retainAll(elements: Collection<T>): Boolean
override fun removeAll(elements: Collection<T>): Boolean
override fun iterator(): MutableIterator<T>
override fun clear()
fun mutableCopy(): MutableObservableCollection<T, C>
}
@@ -0,0 +1,8 @@
package com.cobblemon.mod.common.api.reactive.collections
interface MutableObservableCollectionIterator<T, C : Collection<T>, I : Iterator<T>> :
ObservableCollectionIterator<T, I>,
MutableIterator<T> {
override fun remove()
}
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
interface ObservableCollection<T, C : Collection<T>> :
Collection<T>,
Observable<Pair<C, T>> {
val elements: C
override val size: Int
override fun subscribe(
priority: Priority,
handler: (Pair<C, T>) -> Unit,
): ObservableSubscription<Pair<C, T>>
override fun unsubscribe(subscription: ObservableSubscription<Pair<C, T>>)
override fun isEmpty(): Boolean
fun isNotEmpty(): Boolean
override fun contains(element: T): Boolean
override fun containsAll(elements: Collection<T>): Boolean
override operator fun iterator(): Iterator<T>
fun copy(): ObservableCollection<T, C>
}
@@ -0,0 +1,7 @@
package com.cobblemon.mod.common.api.reactive.collections
interface ObservableCollectionIterator<T, I : Iterator<T>> : Iterator<T> {
override fun hasNext(): Boolean
override fun next(): T
}
@@ -0,0 +1,35 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.reactive.Observable
/**
* This attempts to cast [this] value to [Observable].
*
* @return An observable set with [this] as an [Observable] or an empty set.
*/
fun <T> T.getElementObservables(): Set<Observable<*>> {
this.tryGetObservable()?.let { return setOf(it) }
return emptySet()
}
/**
* This attempts to cast [this] value to [Observable].
*
* @return This value as an [Observable] or null.
*/
fun <T> T.tryGetObservable(): Observable<*>? = if (this is Observable<*>) this else null
fun <T> MutableIterator<T>.removeIf(predicate: (T) -> Boolean): Boolean {
var removed = false
this.forEach { element ->
if (predicate(element)) {
this.remove()
if (!removed) {
removed = true
}
}
}
return removed
}
@@ -0,0 +1,242 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.IndexedListSubscription
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.ListSubscription
import com.cobblemon.mod.common.api.reactive.collections.removeIf
open class MutableObservableList<T>(
list: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) : MutableObservableCollection<T, List<T>>,
ObservableList<T>(list, elementHandler),
MutableList<T> {
private val additionObservable = SimpleObservable<Pair<List<T>, T>>()
private val removalObservable = SimpleObservable<Pair<List<T>, T>>()
private val setObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val swapObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val additionObservableIndexed = SimpleObservable<Triple<List<T>, T, Int>>()
private val removalObservableIndexed = SimpleObservable<Triple<List<T>, T, Int>>()
private val setObservableIndexed = SimpleObservable<Triple<List<T>, Pair<T, T>, Int>>()
private val swapObservableIndexed = SimpleObservable<Triple<List<T>, Pair<T, T>, Pair<Int, Int>>>()
override fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
@Suppress("DuplicatedCode")
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
set = setHandler?.let { setObservable.subscribe(priority, it) },
swap = swapHandler?.let { swapObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler, setHandler, swapHandler)
}
fun subscribeIndexed(
priority: Priority,
additionHandler: ((Triple<List<T>, T, Int>) -> Unit)?,
removalHandler: ((Triple<List<T>, T, Int>) -> Unit)?,
setHandler: ((Triple<List<T>, Pair<T, T>, Int>) -> Unit)?,
swapHandler: ((Triple<List<T>, Pair<T, T>, Pair<Int, Int>>) -> Unit)?,
): IndexedListSubscription<T> {
return IndexedListSubscription(
addition = additionHandler?.let { additionObservableIndexed.subscribe(priority, it) },
removal = removalHandler?.let { removalObservableIndexed.subscribe(priority, it) },
set = setHandler?.let { setObservableIndexed.subscribe(priority, it) },
swap = swapHandler?.let { swapObservableIndexed.subscribe(priority, it) },
)
}
/**
* @return `true` after handling [register] & all subscriptions to [additionObservable],
* [additionObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitAddition(element: T, index: Int): Boolean {
register(element)
additionObservable.emit(list to element)
additionObservableIndexed.emit(Triple(list, element, index))
return emitAnyChange(element)
}
/**
* @return `true` after handling [unregister] & all subscriptions to [removalObservable],
* [removalObservableIndexed] & [ObservableList.emitAnyChange].
*/
private fun emitRemoval(element: T, index: Int): Boolean {
unregister(element)
removalObservable.emit(list to element)
removalObservableIndexed.emit(Triple(list, element, index))
return emitAnyChange(element)
}
/**
* @return `true` after handling [register], [unregister] & all subscriptions to [setObservable],
* [setObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitSet(elements: Pair<T, T>, index: Int): Boolean {
register(elements.first)
unregister(elements.second)
setObservable.emit(list to elements)
setObservableIndexed.emit(Triple(list, elements, index))
emitAnyChange(elements.first)
emitAnyChange(elements.second)
return true
}
/**
* @return `true` after handling all subscriptions to [swapObservable],
* [swapObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitSwap(elements: Pair<T, T>, indices: Pair<Int, Int>): Boolean {
swapObservable.emit(list to elements)
swapObservableIndexed.emit(Triple(list, elements, indices))
emitAnyChange(elements.first)
emitAnyChange(elements.second)
return true
}
override operator fun iterator() = MutableObservableListIterator(this)
override fun listIterator() = MutableObservableListIterator(this)
override fun listIterator(index: Int) = MutableObservableListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): MutableObservableSubList<T> {
return MutableObservableSubList(this, fromIndex, toIndex, elementHandler)
}
override fun addIf(element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(list)) add(element) else false
}
override fun add(element: T): Boolean {
list.add(element)
return emitAddition(element, lastIndex)
}
fun addAtIf(index: Int, element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(list)) {
add(index, element)
true
} else {
false
}
}
override fun add(index: Int, element: T) {
list.add(index, element)
emitAddition(element, index)
}
fun setIf(index: Int, element: T, predicate: (T) -> Boolean): T? {
return if (predicate(list[index])) set(index, element) else null
}
override operator fun set(index: Int, element: T): T {
return list.set(index, element).also { emitSet(element to it, index) }
}
fun swapIf(indexOne: Int, indexTwo: Int, predicate: () -> Boolean): Boolean {
return predicate().also { if (it) swap(indexOne, indexTwo) }
}
fun swap(indexOne: Int, indexTwo: Int) {
val one = list[indexOne]
val two = list.set(indexTwo, one)
list[indexOne] = two
emitSwap(one to two, indexOne to indexTwo)
}
override fun removeIf(predicate: (T) -> Boolean): Boolean = iterator().removeIf(predicate)
override fun remove(element: T): Boolean {
val indexOf = list.indexOfFirst { it == element }
if (indexOf == -1) {
return false
}
list.removeAt(indexOf)
return emitRemoval(element, indexOf)
}
override fun removeAt(index: Int): T {
val element = list.removeAt(index)
emitRemoval(element, index)
return element
}
fun removeAtIf(index: Int, predicate: (T) -> Boolean): T? {
val element = list[index]
if (predicate(element)) {
list.removeAt(index)
emitRemoval(element, index)
}
return element
}
override fun addAll(elements: Collection<T>): Boolean {
elements.forEach { add(it) }
return true
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
var currentIndex = index
elements.forEach { add(currentIndex++, it) }
return true
}
override fun removeAll(elements: Collection<T>):Boolean = removeIf { elements.contains(it) }
override fun retainAll(elements: Collection<T>):Boolean = removeIf { !elements.contains(it) }
override fun clear() {
while (isNotEmpty()) {
removeAt(0)
}
}
}
@@ -0,0 +1,60 @@
package com.cobblemon.mod.common.api.reactive.collections.list
// the name is not quite long enough yet...
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollectionIterator
open class MutableObservableListIterator<T>(
observableList: MutableObservableList<T>,
index: Int = 0,
) : MutableObservableCollectionIterator<T, Set<T>, ListIterator<T>>,
ObservableListIterator<T>(observableList, index),
MutableListIterator<T> {
private val mutableList: MutableObservableList<T> = observableList
private var illegalSetOrRemoveState: Boolean? = null
set (value) { if (field != value) field = value }
override fun next(): T {
illegalSetOrRemoveState = false
return super.next()
}
override fun previous(): T {
illegalSetOrRemoveState = false
return super.previous()
}
override fun remove() {
throwIfIllegalState("remove")
mutableList.removeAt(--index)
illegalSetOrRemoveState = true
}
override fun set(element: T) {
throwIfIllegalState("set")
mutableList[index -1] = element
}
override fun add(element: T) {
when {
mutableList.isEmpty() -> mutableList.add(element).run { index = 0 }
index < mutableList.size -> mutableList.add(index++, element)
else -> mutableList.add(element).run { index++ }
}
illegalSetOrRemoveState = true
}
private fun throwIfIllegalState(function: String) {
if (illegalSetOrRemoveState == null) {
throw IllegalStateException(
"Observable List iterator '$function' was called before 'next' or 'previous'."
)
} else if (illegalSetOrRemoveState == true) {
throw IllegalStateException(
"Observable List iterator '$function' was called, " +
"but 'remove' or 'add' was already called this iteration."
)
}
}
}
@@ -0,0 +1,317 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.ListSubscription
import com.cobblemon.mod.common.api.reactive.collections.removeIf
class MutableObservableSubList<T>(
private val parentList: MutableObservableList<T>,
private val fromIndex: Int,
private val toIndex: Int,
elementHandler: (T) -> Set<Observable<*>>,
) : MutableObservableCollection<T, List<T>>,
ObservableSubList<T>(parentList, fromIndex, toIndex, elementHandler),
MutableList<T> {
private val additionObservable = SimpleObservable<Pair<List<T>, T>>()
private val removalObservable = SimpleObservable<Pair<List<T>, T>>()
private val setObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val swapObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
override val elements: List<T>
get() = when {
isEmpty() -> emptyList()
parentList.size < maxSize -> parentList.elements.subList(fromIndex, parentList.size)
else -> parentList.elements.subList(fromIndex, currentToIndex)
}
private val maxLastIndex: Int = toIndex - 1
private val maxSize: Int = toIndex - fromIndex
override val size: Int get() = when {
isEmpty() -> 0
parentList.size < toIndex -> parentList.lastIndex - fromIndex
else -> maxLastIndex
}
/** This is the last index in the sublist window, or -1 if the window is empty. */
val currentLastIndex: Int get() = size - 1
/** This is either [fromIndex] or -1 if the sublist window is empty. */
val currentFromIndex: Int get() = if (isEmpty()) -1 else fromIndex
/**
* If [parentList] size > [toIndex] this is the toIndex,
* if the parentList size is > [fromIndex] this is the parentList size,
* or this is the [currentFromIndex].
*/
private val currentToIndex: Int
get() = when {
toIndex < parentList.size -> toIndex
fromIndex < parentList.size -> parentList.size
else -> currentFromIndex
}
private val currentLastIndexInParent: Int
get() = when {
isEmpty() -> fromIndex
parentList.lastIndex < toIndex -> parentList.lastIndex
else -> maxLastIndex
}
init {
parentList.subscribeIndexed(
priority = Priority.NORMAL,
additionHandler = { (_, element, index) -> emitAddition(element, index) },
removalHandler = { (_, element, index) -> emitRemoval(element, index) },
setHandler = { (_, elements, index) -> emitSet(elements, index) },
swapHandler = { (_, element, index) -> emitSwap(element, index) },
)
}
override fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
@Suppress("DuplicatedCode")
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
set = setHandler?.let { setObservable.subscribe(priority, it) },
swap = swapHandler?.let { swapObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler, setHandler, swapHandler)
}
private fun emitAddition(element: T, index: Int) {
if (index >= toIndex) {
return
}
val elements = this.elements
if (index >= fromIndex) {
handleAddition(elements, element)
} else if(parentList.lastIndex >= fromIndex) {
handleAddition(elements, parentList[fromIndex])
}
if (parentList.size > toIndex) {
handleRemoval(elements, parentList[toIndex])
}
}
private fun emitRemoval(element: T, index: Int) {
if (index >= toIndex) {
return
}
val elements = this.elements
if (index >= fromIndex) {
handleRemoval(elements, element)
} else if(parentList.lastIndex >= fromIndex - 1) {
handleRemoval(elements, parentList[fromIndex - 1])
}
if (currentLastIndexInParent >= toIndex - 1) {
handleAddition(elements, parentList[toIndex - 1])
}
}
private fun emitSet(elementPair: Pair<T, T>, index: Int) {
if (!index.isInParentRelativeWindow()) {
return
}
register(elementPair.first)
unregister(elementPair.second)
setObservable.emit(elements to elementPair)
emitAnyChange(elementPair.first)
emitAnyChange(elementPair.second)
}
private fun emitSwap(elementPair: Pair<T, T>, indices: Pair<Int, Int>) {
val firstInWindow = indices.first.isInParentRelativeWindow()
val secondInWindow = indices.second.isInParentRelativeWindow()
when {
firstInWindow && !secondInWindow -> {
val elements = this.elements
handleRemoval(elements, elementPair.first)
handleAddition(elements, elementPair.second)
}
!firstInWindow && secondInWindow -> {
val elements = this.elements
handleAddition(elements, elementPair.first)
handleRemoval(elements, elementPair.second)
}
firstInWindow && secondInWindow -> {
swapObservable.emit(elements to elementPair)
emitAnyChange(elementPair.first)
elementPair.second?.let { emitAnyChange(it) }
}
}
}
private fun handleAddition(elements: List<T>, element: T) {
register(element)
additionObservable.emit(elements to element)
emitAnyChange(element)
}
private fun handleRemoval(elements: List<T>, element: T) {
unregister(element)
removalObservable.emit(elements to element)
emitAnyChange(element)
}
override operator fun iterator() = MutableObservableSubListIterator(this)
override fun listIterator() = MutableObservableSubListIterator(this)
override fun listIterator(index: Int) = MutableObservableSubListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): MutableObservableSubList<T> {
return parentList.subList(parentIndex(fromIndex), parentIndex(toIndex))
}
fun swapIf(indexOne: Int, indexTwo: Int, predicate: () -> Boolean): Boolean {
return predicate().also { if (it) swap(indexOne, indexTwo) }
}
fun swap(indexOne: Int, indexTwo: Int) {
val parentIndexOne = parentIndex(indexOne)
val one = parentList[parentIndexOne]
val two = parentList.set(parentIndex(indexTwo), one)
parentList[parentIndexOne] = two
}
override fun addIf(element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(this)) add(element) else false
}
override fun add(element: T): Boolean {
return if (parentList.lastIndex < toIndex) {
parentList.add(element)
} else {
parentList.add(toIndex, element)
true
}
}
fun addAtIf(index: Int, element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(this)) {
add(index, element)
true
} else {
false
}
}
override fun add(index: Int, element: T) {
parentList.add(parentIndex(index), element)
}
override operator fun set(index: Int, element: T): T {
return parentList.set(parentIndex(index), element)
}
override fun removeIf(predicate: (T) -> Boolean): Boolean = iterator().removeIf(predicate)
override fun remove(element: T): Boolean {
for (i in fromIndex until parentList.size) {
val current = elements[i]
if (current == element) {
parentList.removeAt(i)
return true
}
}
return false
}
override fun removeAt(index: Int): T = parentList.removeAt(parentIndex(index))
override fun addAll(elements: Collection<T>): Boolean {
val parentIndex = if (currentLastIndex > -1) currentLastIndex + fromIndex else fromIndex
return if (parentIndex > parentList.lastIndex) {
parentList.addAll(elements)
} else {
parentList.addAll(parentIndex, elements)
}
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
return parentList.addAll(parentIndex(index), elements)
}
override fun removeAll(elements: Collection<T>): Boolean {
return iterator().removeIf { elements.contains(it) }
}
override fun retainAll(elements: Collection<T>): Boolean {
return iterator().removeIf { !elements.contains(it) }
}
/**
* This implementation will remove all elements from the window dynamically. If an element
* is removed & an element shifts into the sublist window that meets the predicate, the
* new element will also be removed.
*
* Note: The other iterators including the ObservableSubListIterator will only operate on
* the window provided before the iteration, even when the iterator is mutable.
*/
private fun removeAllIf(predicate: (T) -> Boolean): Boolean {
if (isEmpty()) {
return false
}
var mutated = false
val outOfParentBounds: (Int) -> Boolean = { it == parentList.size }
for (i in fromIndex until currentToIndex) {
if (outOfParentBounds(i)) {
return mutated
}
while (predicate(parentList[i])) {
parentList.removeAt(i).also { if (!mutated) mutated = true }
if (outOfParentBounds(i)) {
return true
}
}
}
return mutated
}
override fun clear() {
if (isNotEmpty()) {
iterator().removeIf { true }
}
}
}
@@ -0,0 +1,63 @@
package com.cobblemon.mod.common.api.reactive.collections.list
// the name is not quite long enough yet...
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollectionIterator
open class MutableObservableSubListIterator<T>(
subList: MutableObservableSubList<T>,
index: Int = 0,
) : MutableObservableCollectionIterator<T, List<T>, ListIterator<T>>,
ObservableSubListIterator<T>(subList, index),
MutableListIterator<T> {
private val mutableSubList: MutableObservableSubList<T> = subList
private var illegalSetOrRemoveState: Boolean? = null
set (value) { if (field != value) field = value }
override fun next(): T {
illegalSetOrRemoveState = false
return super.next()
}
override fun previous(): T {
illegalSetOrRemoveState = false
return super.previous()
}
override fun remove() {
throwIfIllegalState("remove")
mutableSubList.removeAt(--index)
illegalSetOrRemoveState = true
}
override fun set(element: T) {
throwIfIllegalState("set")
mutableSubList[index -1] = element
}
override fun add(element: T) {
when {
subList.isEmpty() -> {
mutableSubList.add(element)
index = mutableSubList.currentFromIndex
}
index < mutableSubList.currentLastIndex -> mutableSubList.add(index++, element)
else -> mutableSubList.add(element).run { index++ }
}
illegalSetOrRemoveState = true
}
private fun throwIfIllegalState(function: String) {
if (illegalSetOrRemoveState == null) {
throw IllegalStateException(
"Observable Sub List iterator '$function' was called before 'next' or 'previous'."
)
} else if (illegalSetOrRemoveState == true) {
throw IllegalStateException(
"Observable Sub List iterator '$function' was called, " +
"but 'remove' or 'add' was already called this iteration."
)
}
}
}
@@ -0,0 +1,93 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
open class ObservableList<T>(
list: Collection<T>,
protected val elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) : ObservableCollection<T, List<T>>,
List<T>,
Observable<Pair<List<T>, T>> {
protected val list: MutableList<T> = list.toMutableList()
private val subscriptions = PrioritizedList<ObservableSubscription<Pair<List<T>, T>>>()
private val subscriptionMap: MutableMap<Observable<*>, ObservableSubscription<*>> = mutableMapOf()
override val elements: List<T> get() = list
override val size: Int get() = list.size
val lastIndex: Int get() = list.lastIndex
init {
list.forEach { register(it) }
}
override fun subscribe(
priority: Priority,
handler: (Pair<List<T>, T>) -> Unit,
): ObservableSubscription<Pair<List<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
return subscription
}
fun subscribeAndHandle(
priority: Priority,
handler: (Pair<List<T>, T>) -> Unit
): ObservableSubscription<Pair<List<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
list.forEach { handler(this to it) }
return subscription
}
override fun unsubscribe(subscription: ObservableSubscription<Pair<List<T>, T>>) {
subscriptions.remove(subscription)
}
protected fun register(element: T) {
elementHandler(element).forEach{ observable ->
subscriptionMap[observable] = observable.subscribe { emitAnyChange(element) }
}
}
protected fun unregister(element: T) {
elementHandler(element).forEach{ subscriptionMap.remove(it)?.unsubscribe() }
}
/** @return true after handling [subscriptions]. */
protected fun emitAnyChange(element: T): Boolean {
subscriptions.forEach { it.handle(list to element) }
return true
}
override fun isEmpty() = list.isEmpty()
override fun isNotEmpty() = list.isNotEmpty()
override fun contains(element: T) = list.contains(element)
override fun containsAll(elements: Collection<T>) = list.containsAll(elements)
override operator fun get(index: Int): T = list[index]
override fun indexOf(element: T) = list.indexOf(element)
override operator fun iterator(): Iterator<T> = list.iterator()
override fun lastIndexOf(element: T) = list.lastIndexOf(element)
override fun listIterator(): ListIterator<T> = list.listIterator()
override fun listIterator(index: Int): ListIterator<T> = list.listIterator(index = index)
override fun subList(fromIndex: Int, toIndex: Int): List<T> = list.subList(fromIndex, toIndex)
override fun copy() = ObservableList(this.list, elementHandler)
open fun mutableCopy() = MutableObservableList(this.list, elementHandler)
}
@@ -0,0 +1,33 @@
package com.cobblemon.mod.common.api.reactive.collections.list
open class ObservableListIterator<T>(
protected var list: ObservableList<T>,
protected var index: Int = 0,
) : ListIterator<T> {
override fun hasNext(): Boolean = index < list.size
override fun hasPrevious(): Boolean = list.isNotEmpty() && index > 0
override fun next(): T {
if (!hasNext()) {
throw NoSuchElementException(
"Observable List iterator does not have next element from index $index."
)
}
return list[index++]
}
override fun previous(): T {
if (!hasPrevious()) {
throw NoSuchElementException(
"Observable List iterator does not have a previous element at index ${index -1}."
)
}
return list[--index]
}
override fun nextIndex(): Int = index
override fun previousIndex(): Int = index -1
}
@@ -0,0 +1,46 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
import net.minecraft.nbt.NbtCompound
fun <T> List<T>.saveToNbt(
elementKey: String = "element",
elementHandler: (T) -> NbtCompound,
): NbtCompound {
val nbt = NbtCompound()
var size = 0
this.iterator().forEach { nbt.put(elementKey + size++, elementHandler(it)) }
nbt.putInt("size", size)
return nbt
}
fun <T> NbtCompound.loadObservableListOf(
elementKey: String = "element",
loadElementHandler: (NbtCompound) -> T,
elementObservableHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = ObservableList(this.loadList(elementKey, loadElementHandler), elementObservableHandler)
fun <T> NbtCompound.loadMutableObservableListOf(
elementKey: String = "element",
loadElementHandler: (NbtCompound) -> T,
elementObservableHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = MutableObservableList(this.loadList(elementKey, loadElementHandler), elementObservableHandler)
private fun <T> NbtCompound.loadList(
elementKey: String = "element",
elementHandler: (NbtCompound) -> T
): List<T> {
val newList = mutableListOf<T>()
if (this.contains("size")) {
val size = this.getInt("size")
for (i in 0 until size) {
newList.add(elementHandler(this.getCompound(elementKey + i)))
}
}
return newList
}
@@ -0,0 +1,38 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
fun <T> Collection<T>.toObservableList(
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = ObservableList(this, elementHandler)
fun <T> Collection<T>.toMutableObservableList(
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = MutableObservableList(this, elementHandler)
fun <T> observableListOf(
vararg elements: T,
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): ObservableList<T> = ObservableList(elements.toList(), elementHandler)
fun <T> mutableObservableListOf(
vararg elements: T,
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): MutableObservableList<T> = MutableObservableList(elements.toList(), elementHandler)
fun <T> observableListOf(
elements: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): ObservableList<T> = ObservableList(elements, elementHandler)
fun <T> mutableObservableListOf(
elements: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): MutableObservableList<T> = MutableObservableList(elements, elementHandler)
@@ -0,0 +1,142 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollection
open class ObservableSubList<T>(
private val parentList: ObservableList<T>,
private val fromIndex: Int,
private val toIndex: Int,
private val elementHandler: (T) -> Set<Observable<*>>,
) : ObservableCollection<T, List<T>>,
List<T>,
Observable<Pair<List<T>, T>> {
private val subscriptions = PrioritizedList<ObservableSubscription<Pair<List<T>, T>>>()
private val subscriptionMap: MutableMap<Observable<*>, ObservableSubscription<*>> = mutableMapOf()
override val elements: List<T> = parentList.elements.subList(fromIndex, toIndex)
override val size: Int = toIndex - fromIndex
init {
for (i in fromIndex until toIndex) {
register(parentList[i])
}
}
override fun subscribe(
priority: Priority,
handler: (Pair<List<T>, T>) -> Unit,
): ObservableSubscription<Pair<List<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
return subscription
}
fun subscribeAndHandle(
priority: Priority,
handler: (Pair<List<T>, T>) -> Unit
): ObservableSubscription<Pair<List<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
elements.forEach { handler(this to it) }
return subscription
}
override fun unsubscribe(subscription: ObservableSubscription<Pair<List<T>, T>>) {
subscriptions.remove(subscription)
}
protected fun register(element: T) {
elementHandler(element).forEach { observable ->
subscriptionMap[observable] = observable.subscribe { emitAnyChange(element) }
}
}
protected fun unregister(element: T) {
elementHandler(element).forEach { observable ->
subscriptionMap.remove(observable)?.unsubscribe()
}
}
protected fun emitAnyChange(element: T): Boolean {
subscriptions.forEach { it.handle(elements to element) }
return true
}
protected fun Int.isInParentRelativeWindow(): Boolean = this in fromIndex until toIndex
override fun isEmpty() = parentList.lastIndex < fromIndex
override fun isNotEmpty() = parentList.size > fromIndex
override fun contains(element: T) = elements.contains(element)
override fun containsAll(elements: Collection<T>) = this.elements.containsAll(elements)
/** @throws IndexOutOfBoundsException if sublist does not contain [subListIndex]. */
protected fun parentIndex(subListIndex: Int) = throwIfOutOfBounds(subListIndex) + fromIndex
/**
* @throws IndexOutOfBoundsException [index] is above [currentLastIndex] or below [fromIndex]
* @return [index]
*/
private fun throwIfOutOfBounds(index: Int): Int {
return if (index in 0 until size) {
index
} else {
throw IndexOutOfBoundsException("Index $index is outside the sub list window.")
}
}
override operator fun get(index: Int): T {
return parentList[parentIndex(index)]
}
override fun indexOf(element: T): Int {
val endIndex = fromIndex + size
for (i in fromIndex until endIndex) {
if (elements[i] == element) {
return i
}
}
return -1
}
override fun lastIndexOf(element: T): Int {
val startIndex = fromIndex + size
for (i in startIndex downTo fromIndex) {
if (elements[i] == element) {
return i
}
}
return -1
}
override operator fun iterator() = ObservableSubListIterator(this)
override fun listIterator() = ObservableSubListIterator(this)
override fun listIterator(index: Int) = ObservableSubListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): List<T> {
return parentList.subList(parentIndex(fromIndex), parentIndex(toIndex))
}
/** @return a read-only copy that will NOT mutate if the backing list mutates. */
override fun copy() = ObservableList(elements, elementHandler)
/** @return a mutable copy that will NOT mutate if the backing list mutates. */
fun mutableCopy() = MutableObservableList(elements, elementHandler)
/** @return a read-only version of [parentList] that will NOT mutate if the parent list mutates */
fun getParentCopy(): ObservableList<T> = parentList.copy()
/** @return a read-only version of [parentList] that will NOT mutate if the parent list mutates */
fun getParentMutableCopy(): MutableObservableList<T> = parentList.mutableCopy()
/** @return a read-only version of [parentList] that WILL mutate if the backing list mutates */
fun getParent(): ObservableList<T> = parentList
}
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollectionIterator
open class ObservableSubListIterator<T>(
protected val subList: ObservableSubList<T>,
protected var index: Int = 0,
) : ObservableCollectionIterator<T, ListIterator<T>>,
ListIterator<T> {
override fun hasNext(): Boolean = index < subList.size
override fun hasPrevious(): Boolean = subList.isNotEmpty() && index > 0
override fun next(): T {
if (!hasNext()) {
throw NoSuchElementException(
"Observable Sub List iterator does not have next element from index $index."
)
}
return subList[index++]
}
override fun previous(): T {
if (!hasPrevious()) {
throw NoSuchElementException(
"Observable Sub List iterator does not have previous element from index ${index - 1}."
)
}
return subList[--index]
}
override fun nextIndex(): Int = index
override fun previousIndex(): Int = index - 1
}
@@ -0,0 +1,101 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.MapSubscription
import kotlin.collections.Map.Entry
class MutableObservableMap<K, V>(
map: Map<K, V> = mapOf(),
entryHandler: (K, V) -> Set<Observable<*>> = { k, v -> k.getEntryObservables(v) },
) : ObservableMap<K, V>(map = map, entryHandler = entryHandler),
MutableMap<K, V> {
private val additionObservable = SimpleObservable<Pair<Map<K, V>, Pair<K, V>>>()
private val removalObservable = SimpleObservable<Pair<Map<K, V>, Pair<K, V>>>()
fun subscribe(
priority: Priority = Priority.NORMAL,
anyChangeHandler: (Pair<Map<K, V>, Pair<K, V>>) -> Unit,
additionHandler: ((Pair<Map<K, V>, Pair<K, V>>) -> Unit)? = null,
removalHandler: ((Pair<Map<K, V>, Pair<K, V>>) -> Unit)? = null,
): MapSubscription<K, V> {
return MapSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority = Priority.NORMAL,
anyChangeHandler: (Pair<Map<K, V>, Pair<K, V>>) -> Unit,
additionHandler: ((Pair<Map<K, V>, Pair<K, V>>) -> Unit)? = null,
removalHandler: ((Pair<Map<K, V>, Pair<K, V>>) -> Unit)? = null,
): MapSubscription<K, V> {
entries.forEach { anyChangeHandler(this to it.pair()) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
/**
* @return `true` after handling [register] & all subscriptions to [additionObservable],
* & [ObservableMap.emitAnyChange].
*/
private fun emitAddition(key: K, value: V): Boolean {
register(key, value)
additionObservable.emit(map to (key to value))
return emitAnyChange(key, value)
}
/**
* @return `true` after handling [unregister] & all subscriptions to [removalObservable],
* & [ObservableMap.emitAnyChange].
*/
private fun emitRemoval(key: K, value: V): Boolean {
unregister(key, value)
removalObservable.emit(map to (key to value))
return emitAnyChange(key, value)
}
override operator fun iterator(): MutableObservableMapIterator<K, V> {
return MutableObservableMapIterator(this)
}
operator fun set(key: K, value: V): V? = put(key, value)
fun put(pair: Pair<K, V>): V? = put(pair.first, pair.second)
fun put(entry: Entry<K, V>): V? = put(entry.key, entry.value)
override fun put(key: K, value: V): V? {
val previous = map.put(key, value)
emitAddition(key, value)
if (previous != null) {
emitRemoval(key, previous)
}
return previous
}
fun remove(key: K, value: V): Boolean {
return if (map[key] == value) (remove(key) != null) else false
}
override fun remove(key: K): V? {
val value = map.remove(key) ?: return null
emitRemoval(key, value)
return value
}
override fun putAll(from: Map<out K, V>) = from.forEach { put(it.key, it.value) }
fun getOrDefault(key: K, defaultValue: V): V = map[key] ?: defaultValue
fun getOrPut(key: K, newValue: V) = map[key] ?: newValue.also { put(key, it) }
override fun clear() {
if (map.isNotEmpty()) {
keys.toSet().forEach { remove(it) }
}
}
}
@@ -0,0 +1,35 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import kotlin.collections.Map.Entry
open class MutableObservableMapIterator<K, V>(
private val observableMap: MutableObservableMap<K, V>,
) : ObservableMapIterator<K, V>(observableMap.entries.toSet().iterator()),
MutableIterator<Entry<K, V>> {
private lateinit var currentEntry: Entry<K, V>
private var removedThisIteration: Boolean? = null
set (value) { if (field != value) field = value }
override fun next(): Entry<K, V> {
currentEntry = super.next()
removedThisIteration = false
return currentEntry
}
override fun remove() {
if (removedThisIteration == null) {
throw IllegalStateException(
"Observable Map iterator 'remove' was called before 'next' or 'previous'."
)
} else if (removedThisIteration == true) {
throw IllegalStateException(
"Observable Map iterator 'remove' was called, " +
"but 'remove' was already called this iteration."
)
}
observableMap.remove(currentEntry.key)
removedThisIteration = true
}
}
@@ -0,0 +1,124 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import kotlin.Comparator
import kotlin.collections.Map.Entry
open class ObservableMap<K, V>(
map: Map<K, V>,
protected val entryHandler: (K, V) -> Set<Observable<*>> =
{ k: K, v: V -> k.getEntryObservables(v) },
) : Map<K, V>,
Observable<Pair<Map<K, V>, Pair<K, V>>> {
protected val map: MutableMap<K, V> = map.toMutableMap()
private val subscriptions = PrioritizedList<ObservableSubscription<Pair<Map<K, V>, Pair<K, V>>>>()
private val subscriptionMap: MutableMap<Observable<*>, ObservableSubscription<*>> = mutableMapOf()
override val size get() = map.size
override val keys get() = map.keys
override val values get() = map.values
override val entries get() = map.entries
init {
this.map.forEach { (key, value) ->
entryHandler(key, value).forEach{ observable ->
subscriptionMap[observable] = observable.subscribe {
emitAnyChange(key, value)
}
}
}
}
override fun subscribe(
priority: Priority,
handler: (Pair<Map<K, V>, Pair<K, V>>) -> Unit
): ObservableSubscription<Pair<Map<K, V>, Pair<K, V>>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
return subscription
}
fun subscribeAndHandle(
priority: Priority,
handler: (Pair<Map<K, V>, Pair<K, V>>) -> Unit
): ObservableSubscription<Pair<Map<K, V>, Pair<K, V>>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
map.forEach { handler(this to (it.pair())) }
return subscription
}
override fun unsubscribe(subscription: ObservableSubscription<Pair<Map<K, V>, Pair<K, V>>>) {
subscriptions.remove(subscription)
}
protected open fun register(key: K, value: V) {
if (contains(key, value)) {
entryHandler(key, value).forEach{ observable ->
subscriptionMap[observable] = observable.subscribe { emitAnyChange(key, value) }
}
}
}
protected fun unregister(key: K, value: V) {
entryHandler(key, value).forEach{ subscriptionMap.remove(it)?.unsubscribe() }
}
protected fun emitAnyChange(key: K, value: V): Boolean {
subscriptions.forEach { it.handle(map to (key to value)) }
return true
}
override fun isEmpty() = map.isEmpty()
fun isNotEmpty() = map.isNotEmpty()
override fun containsKey(key: K) = map.containsKey(key)
override fun containsValue(value: V) = map.containsValue(value)
fun contains(pair: Pair<K, V>): Boolean = contains(pair.first, pair.second)
fun contains(entry: Entry<K, V>): Boolean = contains(entry.key, entry.value)
fun contains(key: K, value: V) = map[key] == value
fun contains(predicate: (Entry<K, V>) -> Boolean) = map.any(predicate)
fun containsAll(other: Collection<Entry<K, V>>): Boolean {
return map.all { entry -> other.any { it.key == entry.key && it.value == entry.value } }
}
fun containsAll(other: Map<K, V>): Boolean {
return map.all { entry -> other.any { it.key == entry.key && it.value == entry.value } }
}
open operator fun iterator(): Iterator<Entry<K, V>> = map.iterator()
override operator fun get(key: K) = map[key]
fun toSortedMap(comparator: Comparator<in K>) = map.toMap().toSortedMap(comparator)
fun firstKeyOrNull(predicate: (Entry<K, V>) -> Boolean = { true }): K? {
map.forEach { if (predicate(it)) return it.key }
return null
}
fun firstValueOrNull(predicate: (Entry<K, V>) -> Boolean = { true }): V? {
map.forEach { if (predicate(it)) return it.value }
return null
}
fun firstEntryOrNull(predicate: (Entry<K, V>) -> Boolean = { true }): Entry<K, V>? {
map.forEach { if (predicate(it)) return it }
return null
}
fun copy() = ObservableMap(this.map, entryHandler)
fun mutableCopy() = MutableObservableMap(this.map, entryHandler)
}
@@ -0,0 +1,11 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import kotlin.collections.Map.Entry
open class ObservableMapIterator<K, V>(
protected var iterator: Iterator<Entry<K, V>>
) : Iterator<Entry<K, V>> {
override fun hasNext():Boolean = iterator.hasNext()
override fun next(): Entry<K, V> = iterator.next()
}
@@ -0,0 +1,47 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import com.cobblemon.mod.common.api.reactive.Observable
import net.minecraft.nbt.NbtCompound
import kotlin.collections.Map.Entry
fun <K, V> Map<K, V>.saveToNbt(
entryKey: String = "entry",
entryHandler: (Entry<K, V>) -> NbtCompound,
): NbtCompound {
val nbt = NbtCompound()
var size = 0
this.iterator().forEach { nbt.put(entryKey + size++, entryHandler(it)) }
nbt.putInt("size", size)
return nbt
}
fun <K, V> NbtCompound.loadObservableMapOf(
entryKey: String = "entry",
loadEntryHandler: (NbtCompound) -> Pair<K, V>,
entryObservableHandler: (K, V) -> Set<Observable<*>> = { k, v -> k.getEntryObservables(v) },
) = ObservableMap(this.loadMap(entryKey, loadEntryHandler), entryObservableHandler)
fun <K, V> NbtCompound.loadMutableObservableMapOf(
entryKey: String = "entry",
loadEntryHandler: (NbtCompound) -> Pair<K, V>,
entryObservableHandler: (K, V) -> Set<Observable<*>> = { k, v -> k.getEntryObservables(v) },
) = MutableObservableMap(this.loadMap(entryKey, loadEntryHandler), entryObservableHandler)
private fun <K, V> NbtCompound.loadMap(
entryKey: String = "entry",
entryHandler: (NbtCompound) -> Pair<K, V>,
): MutableMap<K, V> {
val newMap: MutableMap<K, V> = mutableMapOf()
if (this.contains("size")) {
val size = this.getInt("size")
for (i in 0 until size) {
val (key, value) = entryHandler(this.getCompound(entryKey + i))
newMap[key] = value
}
}
return newMap
}
@@ -0,0 +1,113 @@
package com.cobblemon.mod.common.api.reactive.collections.map
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.collections.tryGetObservable
import kotlin.collections.Map.Entry
fun <K, V> Entry<K, V>.pair() = this.key to this.value
fun <K, V> Collection<Entry<K, V>>.pairs(): Set<Pair<K, V>> {
mutableSetOf<Pair<K, V>>().let { set ->
this.forEach { set.add(it.key to it.value) }
return set
}
}
fun <K, V> Map<K, V>.pairs(): Set<Pair<K, V>> {
mutableSetOf<Pair<K, V>>().let { set ->
this.forEach { set.add(it.key to it.value) }
return set
}
}
fun <K, V> Collection<Entry<K, V>>.toMap(): Map<K, V> = this.toMutableMap()
fun <K, V> Collection<Entry<K, V>>.toMutableMap(): MutableMap<K, V> {
return mutableMapOf<K, V>().putAll(this)
}
fun <K, V> MutableMap<K, V>.putAll(entries: Collection<Entry<K, V>>): MutableMap<K, V> {
return entries.forEach { this[it.key] = it.value }.let { this }
}
fun <K, V> defaultEntryHandler() = { k: K, v: V -> k.getEntryObservables(v) }
fun <K, V> K.getEntryObservables(value: V): Set<Observable<*>> {
val observables = mutableSetOf<Observable<*>>()
this.tryGetObservable()?.let { observables.add(it) }
value.tryGetObservable()?.let { observables.add(it) }
return observables
}
fun <K, V> Map<K, V>.toObservableMap(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(this, entryHandler)
fun <K, V> Map<K, V>.toMutableObservableMap(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(this, entryHandler)
fun <K, V> Collection<Pair<K, V>>.toObservableMap(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(this.toMap(), entryHandler)
fun <K, V> Collection<Pair<K, V>>.toMutableObservableMap(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(this.toMap(), entryHandler)
fun <K, V> observableMapOf(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(mapOf(), entryHandler)
fun <K, V> mutableObservableMapOf(
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(mapOf(), entryHandler)
fun <K, V> observableMapOf(
map: Map<K, V> = emptyMap(),
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(map, entryHandler)
fun <K, V> observableMapOf(
vararg pairs: Pair<K, V>,
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(pairs.toMap(), entryHandler)
fun <K, V> mutableObservableMapOf(
vararg pairs: Pair<K, V>,
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(pairs.toMap(), entryHandler)
fun <K, V> mutableObservableMapOf(
map: Map<K, V> = emptyMap(),
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(map, entryHandler)
fun <K, V> observableMapOf(
vararg entries: Entry<K, V>,
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(entries.toSet().toMap(), entryHandler)
fun <K, V> mutableObservableMapOf(
vararg entries: Entry<K, V>,
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(entries.toSet().toMap(), entryHandler)
fun <K, V> observableMapOf(
pairs: Collection<Pair<K, V>> = emptySet(),
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = ObservableMap(pairs.toMap(), entryHandler)
fun <K, V> mutableObservableMapOf(
pairs: Collection<Pair<K, V>> = emptySet(),
entryHandler: (K, V) -> Set<Observable<*>> = defaultEntryHandler(),
) = MutableObservableMap(pairs.toMap(), entryHandler)
@@ -0,0 +1,100 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.SetSubscription
import com.cobblemon.mod.common.api.reactive.collections.removeIf
class MutableObservableSet<T>(
set: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) : MutableObservableCollection<T, Set<T>>,
ObservableSet<T>(set, elementHandler),
MutableSet<T> {
private val additionObservable = SimpleObservable<Pair<Set<T>, T>>()
private val removalObservable = SimpleObservable<Pair<Set<T>, T>>()
override fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<Set<T>, T>) -> Unit,
additionHandler: ((Pair<Set<T>, T>) -> Unit)?,
removalHandler: ((Pair<Set<T>, T>) -> Unit)?,
): SetSubscription<T> {
return SetSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<Set<T>, T>) -> Unit,
additionHandler: ((Pair<Set<T>, T>) -> Unit)?,
removalHandler: ((Pair<Set<T>, T>) -> Unit)?,
): SetSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
/**
* @return `true` after handling [register] & all subscriptions to [additionObservable]
* & [ObservableSet.emitAnyChange].
*/
private fun emitAddition(element: T): Boolean {
register(element)
additionObservable.emit(set to element)
return emitAnyChange(element)
}
/**
* @return `true` after handling [unregister] & all subscriptions to [removalObservable]
* & [ObservableSet.emitAnyChange].
*/
private fun emitRemoval(element: T): Boolean {
unregister(element)
removalObservable.emit(set to element)
return emitAnyChange(element)
}
override operator fun iterator() = MutableObservableSetIterator(this)
override fun addIf(element: T, predicate: (Set<T>) -> Boolean): Boolean {
return if (predicate(set)) add(element) else false
}
override fun add(element: T): Boolean {
return if (set.add(element)) emitAddition(element) else false
}
override fun removeIf(predicate: (T) -> Boolean): Boolean {
return iterator().removeIf(predicate)
}
fun removeIf(element: T, predicate: (Set<T>) -> Boolean): Boolean {
return if (predicate(set)) remove(element) else false
}
override fun remove(element: T): Boolean {
return if (set.remove(element)) emitRemoval(element) else false
}
override fun addAll(elements: Collection<T>): Boolean {
var mutated = false
elements.forEach { if (add(it) && !mutated) mutated = true }
return mutated
}
override fun removeAll(elements: Collection<T>):Boolean = removeIf { elements.contains(it) }
override fun retainAll(elements: Collection<T>):Boolean = removeIf { !elements.contains(it) }
override fun clear() {
// just a simpler way to call the mutable iterator & emit with each removal
removeIf { true }
}
}
@@ -0,0 +1,33 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollectionIterator
open class MutableObservableSetIterator<T>(
private val observableSet: MutableObservableSet<T>,
) : MutableObservableCollectionIterator<T, Set<T>, Iterator<T>>,
ObservableSetIterator<T>(observableSet.elements.toSet().iterator()),
MutableIterator<T> {
private var currentElement: T? = null
set (value) { if (field != value) field = value }
private var removedThisIteration: Boolean = false
set (value) { if (field != value) field = value }
override fun next(): T {
currentElement = iterator.next()
removedThisIteration = false
return currentElement!!
}
override fun remove() {
if (currentElement == null) {
throw IllegalStateException("Observable Set iterator 'remove' was called before 'next' or 'previous'.")
} else if (removedThisIteration) {
throw IllegalStateException("Observable Set iterator 'remove' was called, but 'remove' was already called this iteration.")
}
observableSet.remove(currentElement)
currentElement = null
removedThisIteration = true
}
}
@@ -0,0 +1,86 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
open class ObservableSet<T>(
set: Collection<T>,
protected val elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) : ObservableCollection<T, Set<T>>,
Set<T>,
Observable<Pair<Set<T>, T>> {
protected val set: MutableSet<T> = set.toMutableSet()
private val subscriptions = PrioritizedList<ObservableSubscription<Pair<Set<T>, T>>>()
private val subscriptionMap: MutableMap<Observable<*>, ObservableSubscription<*>> = mutableMapOf()
// Keep as 'this.set' & not 'set,' b/c compiler doesn't like it & cries a little.
override val elements: Set<T> get() = this.set
override val size: Int get() = set.size
init {
set.forEach { register(it) }
}
override fun subscribe(
priority: Priority,
handler: (Pair<Set<T>, T>) -> Unit,
): ObservableSubscription<Pair<Set<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
return subscription
}
fun subscribeAndHandle(
priority: Priority,
handler: (Pair<Set<T>, T>) -> Unit
): ObservableSubscription<Pair<Set<T>, T>> {
val subscription = ObservableSubscription(this, handler)
subscriptions.add(priority, subscription)
set.forEach { handler(this to it) }
return subscription
}
/*
TODO: fix issue where passing subscription to addition or removal does not unsubscribe
Does this issue really matter?
Calling unsubscribe from the subscription directly works as intended
*/
override fun unsubscribe(subscription: ObservableSubscription<Pair<Set<T>, T>>) {
subscriptions.remove(subscription)
}
protected fun register(element: T) {
elementHandler(element).forEach{ observable ->
subscriptionMap[observable] = observable.subscribe { emitAnyChange(element) }
}
}
protected fun unregister(element: T) {
elementHandler(element).forEach{ subscriptionMap.remove(it)?.unsubscribe() }
}
/** @return true after handling [subscriptions]. */
protected fun emitAnyChange(element: T): Boolean {
subscriptions.forEach { it.handle(set to element) }
return true
}
override fun isEmpty() = set.isEmpty()
override fun isNotEmpty() = set.isNotEmpty()
override fun contains(element: T) = set.contains(element)
override fun containsAll(elements: Collection<T>) = set.containsAll(elements)
override operator fun iterator(): Iterator<T> = set.iterator()
override fun copy() = ObservableSet(set, elementHandler)
fun mutableCopy() = MutableObservableSet(set, elementHandler)
}
@@ -0,0 +1,11 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollectionIterator
open class ObservableSetIterator<T>(
protected var iterator: Iterator<T>
) : ObservableCollectionIterator<T, Iterator<T>> {
override fun hasNext():Boolean = iterator.hasNext()
override fun next(): T = iterator.next()
}
@@ -0,0 +1,46 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
import net.minecraft.nbt.NbtCompound
fun <T> Set<T>.saveToNbt(
elementKey: String = "element",
elementHandler: (T) -> NbtCompound,
): NbtCompound {
val nbt = NbtCompound()
var size = 0
this.iterator().forEach { nbt.put(elementKey + size++, elementHandler(it)) }
nbt.putInt("size", size)
return nbt
}
fun <T> NbtCompound.loadObservableSetOf(
elementKey: String = "element",
loadElementHandler: (NbtCompound) -> T,
elementObservableHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = ObservableSet(this.loadSet(elementKey, loadElementHandler), elementObservableHandler)
fun <T> NbtCompound.loadMutableObservableSetOf(
elementKey: String = "element",
loadElementHandler: (NbtCompound) -> T,
elementObservableHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = MutableObservableSet(this.loadSet(elementKey, loadElementHandler), elementObservableHandler)
private fun <T> NbtCompound.loadSet(
elementKey: String = "element",
elementHandler: (NbtCompound) -> T,
): Set<T> {
val newSet = mutableSetOf<T>()
if (this.contains("size")) {
val size = this.getInt("size")
for (i in 0 until size) {
newSet.add(elementHandler(this.getCompound(elementKey + i)))
}
}
return newSet
}
@@ -0,0 +1,38 @@
package com.cobblemon.mod.common.api.reactive.collections.set
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
fun <T> Collection<T>.toObservableSet(
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = ObservableSet(this, elementHandler)
fun <T> Collection<T>.toMutableObservableSet(
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) = MutableObservableSet(this, elementHandler)
fun <T> observableSetOf(
vararg elements: T,
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): ObservableSet<T> = ObservableSet(elements.toSet(), elementHandler)
fun <T> mutableObservableSetOf(
vararg elements: T,
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): MutableObservableSet<T> = MutableObservableSet(elements.toSet(), elementHandler)
fun <T> observableSetOf(
elements: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): ObservableSet<T> = ObservableSet(elements, elementHandler)
fun <T> mutableObservableSetOf(
elements: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
): MutableObservableSet<T> = MutableObservableSet(elements, elementHandler)
@@ -0,0 +1,27 @@
package com.cobblemon.mod.common.api.reactive.collections.subscriptions
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
/**
* Immutable observable collections implement an [anyChange] observable subscription directly.
*
* Mutable observable collections implement [anyChange], [addition], & [removal] subscriptions
* through a [CollectionSubscription].
*
* @property anyChange Emits with any mutation of the collection or the collection's observable elements.
*
* @property addition Emits when any collection mutation results from the addition of an element.
*
* @property removal Emits when any collection mutation results from the removal of an element.
*
* @see ListSubscription.set
* @see ListSubscription.swap
*/
interface CollectionSubscription<T, C : Collection<T>> {
val anyChange: ObservableSubscription<Pair<C, T>>
val addition: ObservableSubscription<Pair<C, T>>?
val removal: ObservableSubscription<Pair<C, T>>?
fun unsubscribe()
}
@@ -0,0 +1,18 @@
package com.cobblemon.mod.common.api.reactive.collections.subscriptions
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
data class IndexedListSubscription<T>(
val addition: ObservableSubscription<Triple<List<T>, T, Int>>? = null,
val removal: ObservableSubscription<Triple<List<T>, T, Int>>? = null,
val set: ObservableSubscription<Triple<List<T>, Pair<T, T>, Int>>? = null,
val swap: ObservableSubscription<Triple<List<T>, Pair<T, T>, Pair<Int, Int>>>? = null,
) {
fun unsubscribe() {
addition?.unsubscribe()
removal?.unsubscribe()
set?.unsubscribe()
swap?.unsubscribe()
}
}
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.api.reactive.collections.subscriptions
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
data class ListSubscription<T>(
override val anyChange: ObservableSubscription<Pair<List<T>, T>>,
override val addition: ObservableSubscription<Pair<List<T>, T>>? = null,
override val removal: ObservableSubscription<Pair<List<T>, T>>? = null,
val set: ObservableSubscription<Pair<List<T>, Pair<T, T>>>? = null,
val swap: ObservableSubscription<Pair<List<T>, Pair<T, T>>>? = null,
) : CollectionSubscription<T, List<T>> {
override fun unsubscribe() {
this.anyChange.unsubscribe()
addition?.unsubscribe()
removal?.unsubscribe()
set?.unsubscribe()
swap?.unsubscribe()
}
}
@@ -0,0 +1,31 @@
package com.cobblemon.mod.common.api.reactive.collections.subscriptions
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
import com.cobblemon.mod.common.api.reactive.collections.map.ObservableMap
import com.cobblemon.mod.common.api.reactive.collections.map.MutableObservableMap
/**
* The immutable [ObservableMap] implements an [anyChange] observable subscription directly.
*
* The [MutableObservableMap] implements [anyChange], [addition], & [removal] subscriptions
* through this class.
*
* @property anyChange Emits with any mutation from removal, addition, or a mutation in place by
* an entry extending the observable interface.
*
* @property addition Emits when any mutation results from the addition of an element.
*
* @property removal Emits when any mutation results from the removal of an element.
*/
data class MapSubscription<K, T>(
val anyChange: ObservableSubscription<Pair<Map<K, T>, Pair<K, T>>>,
val addition: ObservableSubscription<Pair<Map<K, T>, Pair<K, T>>>? = null,
val removal: ObservableSubscription<Pair<Map<K, T>, Pair<K, T>>>? = null,
) {
fun unsubscribe() {
anyChange.unsubscribe()
addition?.unsubscribe()
removal?.unsubscribe()
}
}
@@ -0,0 +1,16 @@
package com.cobblemon.mod.common.api.reactive.collections.subscriptions
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
data class SetSubscription<T>(
override val anyChange: ObservableSubscription<Pair<Set<T>, T>>,
override val addition: ObservableSubscription<Pair<Set<T>, T>>? = null,
override val removal: ObservableSubscription<Pair<Set<T>, T>>? = null,
) : CollectionSubscription<T, Set<T>> {
override fun unsubscribe() {
this.anyChange.unsubscribe()
addition?.unsubscribe()
removal?.unsubscribe()
}
}
@@ -0,0 +1,46 @@
package com.cobblemon.mod.common.api.reactive
import org.junit.jupiter.api.Test
object AllCollectionTests {
@Test
fun runConstructionTests() {
ConstructionTests.setConstruction()
ConstructionTests.listConstruction()
ConstructionTests.subListConstruction()
ConstructionTests.mapConstruction()
}
@Test
fun runSerializationTests() {
SerializationTests.setSerialization()
SerializationTests.listSerialization()
SerializationTests.subListSerialization()
SerializationTests.mapSerialization()
}
@Test
fun runIteratorTests() {
IteratorTests.setIterator()
IteratorTests.listIterator()
IteratorTests.subListIterator()
IteratorTests.mapIterator()
}
@Test
fun runEmittingTests() {
EmittingTests.setAsCollectionEmitChange()
EmittingTests.listAsCollectionEmitChange()
EmittingTests.listAndSubListEmitChange()
EmittingTests.mapEmitChange()
}
@Test
fun runEmittingReferenceTypesTests() {
EmittingReferenceTypeTests.setElementsEmittingChange()
EmittingReferenceTypeTests.listElementsEmittingChange()
EmittingReferenceTypeTests.subListElementsEmittingChange()
EmittingReferenceTypeTests.entriesEmittingChange()
}
}
@@ -0,0 +1,403 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.list.*
import com.cobblemon.mod.common.api.reactive.collections.map.*
import com.cobblemon.mod.common.api.reactive.collections.set.*
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
object ConstructionTests {
@Test
fun setConstruction() {
val sets = TestSets.default()
val inputSet = sets.input
val observableSet = sets.observable
val mutableObservableSet = sets.mutableObservable
val emptyObservableSet = sets.emptyObservable
val emptyMutableObservableSet = sets.emptyMutableObservable
Assertions.assertTrue(observableSet.containsAll(inputSet))
Assertions.assertTrue(mutableObservableSet.containsAll(inputSet))
Assertions.assertTrue(emptyObservableSet.isEmpty())
Assertions.assertTrue(emptyMutableObservableSet.isEmpty())
Assertions.assertTrue(ObservableSet(inputSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet(observableSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet(emptyObservableSet).isEmpty())
Assertions.assertTrue(ObservableSet(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(ObservableSet<String>(inputSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet<String>(observableSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet<String>(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(ObservableSet<String>(emptyObservableSet).isEmpty())
Assertions.assertTrue(ObservableSet<String>(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(MutableObservableSet(inputSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet(observableSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet(emptyObservableSet).isEmpty())
Assertions.assertTrue(MutableObservableSet(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(MutableObservableSet<String>(inputSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet<String>(observableSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet<String>(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(MutableObservableSet<String>(emptyObservableSet).isEmpty())
Assertions.assertTrue(MutableObservableSet<String>(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(inputSet.toObservableSet().containsAll(inputSet))
Assertions.assertTrue(observableSet.toObservableSet().containsAll(inputSet))
Assertions.assertTrue(mutableObservableSet.toObservableSet().containsAll(inputSet))
Assertions.assertTrue(emptyObservableSet.toObservableSet().isEmpty())
Assertions.assertTrue(emptyMutableObservableSet.toObservableSet().isEmpty())
Assertions.assertTrue(inputSet.toObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(observableSet.toObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(mutableObservableSet.toObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(emptyObservableSet.toObservableSet<String>().isEmpty())
Assertions.assertTrue(emptyMutableObservableSet.toObservableSet<String>().isEmpty())
Assertions.assertTrue(inputSet.toMutableObservableSet().containsAll(inputSet))
Assertions.assertTrue(observableSet.toMutableObservableSet().containsAll(inputSet))
Assertions.assertTrue(mutableObservableSet.toMutableObservableSet().containsAll(inputSet))
Assertions.assertTrue(emptyObservableSet.toMutableObservableSet().isEmpty())
Assertions.assertTrue(emptyMutableObservableSet.toMutableObservableSet().isEmpty())
Assertions.assertTrue(inputSet.toMutableObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(observableSet.toMutableObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(mutableObservableSet.toMutableObservableSet<String>().containsAll(inputSet))
Assertions.assertTrue(emptyObservableSet.toMutableObservableSet<String>().isEmpty())
Assertions.assertTrue(emptyMutableObservableSet.toMutableObservableSet<String>().isEmpty())
Assertions.assertTrue(observableSetOf(inputSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf(observableSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf(emptyObservableSet).isEmpty())
Assertions.assertTrue(observableSetOf(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(observableSetOf<String>(inputSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(observableSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(emptyObservableSet).isEmpty())
Assertions.assertTrue(observableSetOf<String>(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(mutableObservableSetOf(inputSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(observableSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(emptyObservableSet).isEmpty())
Assertions.assertTrue(mutableObservableSetOf(emptyMutableObservableSet).isEmpty())
Assertions.assertTrue(mutableObservableSetOf<String>(inputSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(observableSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(mutableObservableSet).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(emptyObservableSet).isEmpty())
Assertions.assertTrue(mutableObservableSetOf<String>(emptyMutableObservableSet).isEmpty())
val ordered = inputSet.toList()
Assertions.assertTrue(observableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(observableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(observableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(observableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
Assertions.assertTrue(mutableObservableSetOf<String>(ordered[0], ordered[1], ordered[2], ordered[3]).containsAll(inputSet))
}
@Test
fun listConstruction() {
val lists = TestLists.default()
val input = lists.input
val observable = lists.observable
val mutableObservable = lists.mutableObservable
val emptyObservable = lists.emptyObservable
val emptyMutableObservable = lists.emptyMutableObservable
Assertions.assertTrue(observable.containsAll(input))
Assertions.assertTrue(mutableObservable.containsAll(input))
Assertions.assertTrue(emptyObservable.isEmpty())
Assertions.assertTrue(emptyMutableObservable.isEmpty())
Assertions.assertTrue(ObservableList(input).containsAll(input))
Assertions.assertTrue(ObservableList(observable).containsAll(input))
Assertions.assertTrue(ObservableList(mutableObservable).containsAll(input))
Assertions.assertTrue(ObservableList(emptyObservable).isEmpty())
Assertions.assertTrue(ObservableList(emptyMutableObservable).isEmpty())
Assertions.assertTrue(ObservableList<String>(input).containsAll(input))
Assertions.assertTrue(ObservableList<String>(observable).containsAll(input))
Assertions.assertTrue(ObservableList<String>(mutableObservable).containsAll(input))
Assertions.assertTrue(ObservableList<String>(emptyObservable).isEmpty())
Assertions.assertTrue(ObservableList<String>(emptyMutableObservable).isEmpty())
Assertions.assertTrue(MutableObservableList(input).containsAll(input))
Assertions.assertTrue(MutableObservableList(observable).containsAll(input))
Assertions.assertTrue(MutableObservableList(mutableObservable).containsAll(input))
Assertions.assertTrue(MutableObservableList(emptyObservable).isEmpty())
Assertions.assertTrue(MutableObservableList(emptyMutableObservable).isEmpty())
Assertions.assertTrue(MutableObservableList<String>(input).containsAll(input))
Assertions.assertTrue(MutableObservableList<String>(observable).containsAll(input))
Assertions.assertTrue(MutableObservableList<String>(mutableObservable).containsAll(input))
Assertions.assertTrue(MutableObservableList<String>(emptyObservable).isEmpty())
Assertions.assertTrue(MutableObservableList<String>(emptyMutableObservable).isEmpty())
Assertions.assertTrue(input.toObservableList().containsAll(input))
Assertions.assertTrue(observable.toObservableList().containsAll(input))
Assertions.assertTrue(mutableObservable.toObservableList().containsAll(input))
Assertions.assertTrue(emptyObservable.toObservableList().isEmpty())
Assertions.assertTrue(emptyMutableObservable.toObservableList().isEmpty())
Assertions.assertTrue(input.toObservableList<String>().containsAll(input))
Assertions.assertTrue(observable.toObservableList<String>().containsAll(input))
Assertions.assertTrue(mutableObservable.toObservableList<String>().containsAll(input))
Assertions.assertTrue(emptyObservable.toObservableList<String>().isEmpty())
Assertions.assertTrue(emptyMutableObservable.toObservableList<String>().isEmpty())
Assertions.assertTrue(input.toMutableObservableList().containsAll(input))
Assertions.assertTrue(observable.toMutableObservableList().containsAll(input))
Assertions.assertTrue(mutableObservable.toMutableObservableList().containsAll(input))
Assertions.assertTrue(emptyObservable.toMutableObservableList().isEmpty())
Assertions.assertTrue(emptyMutableObservable.toMutableObservableList().isEmpty())
Assertions.assertTrue(input.toMutableObservableList<String>().containsAll(input))
Assertions.assertTrue(observable.toMutableObservableList<String>().containsAll(input))
Assertions.assertTrue(mutableObservable.toMutableObservableList<String>().containsAll(input))
Assertions.assertTrue(emptyObservable.toMutableObservableList<String>().isEmpty())
Assertions.assertTrue(emptyMutableObservable.toMutableObservableList<String>().isEmpty())
Assertions.assertTrue(observableListOf(input).containsAll(input))
Assertions.assertTrue(observableListOf(observable).containsAll(input))
Assertions.assertTrue(observableListOf(mutableObservable).containsAll(input))
Assertions.assertTrue(observableListOf(emptyObservable).isEmpty())
Assertions.assertTrue(observableListOf(emptyMutableObservable).isEmpty())
Assertions.assertTrue(observableListOf<String>(input).containsAll(input))
Assertions.assertTrue(observableListOf<String>(observable).containsAll(input))
Assertions.assertTrue(observableListOf<String>(mutableObservable).containsAll(input))
Assertions.assertTrue(observableListOf<String>(emptyObservable).isEmpty())
Assertions.assertTrue(observableListOf<String>(emptyMutableObservable).isEmpty())
Assertions.assertTrue(mutableObservableListOf(input).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(observable).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(mutableObservable).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(emptyObservable).isEmpty())
Assertions.assertTrue(mutableObservableListOf(emptyMutableObservable).isEmpty())
Assertions.assertTrue(mutableObservableListOf<String>(input).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(observable).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(mutableObservable).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(emptyObservable).isEmpty())
Assertions.assertTrue(mutableObservableListOf<String>(emptyMutableObservable).isEmpty())
Assertions.assertTrue(observableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(observableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(observableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(observableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(observableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(observableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
Assertions.assertTrue(mutableObservableListOf<String>(input[0], input[1], input[2], input[3]).containsAll(input))
}
@Test
fun subListConstruction() {
val lists = TestLists.default()
val inputSubList = lists.input.subList(1, 3)
val inputSubSubList = inputSubList.subList(0, 1)
val subList = lists.observable.subList(1, 3)
val subSubList = subList.subList(0, 1)
val mutableObservableSubList = lists.mutableObservable.subList(1, 3)
val mutableObservableSubSubList = mutableObservableSubList.subList(0, 1)
Assertions.assertTrue(subList.containsAll(inputSubList))
Assertions.assertTrue(mutableObservableSubList.containsAll(inputSubList))
Assertions.assertTrue(ObservableList(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList(subList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(ObservableList(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(ObservableList(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(ObservableList<String>(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList<String>(subList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList<String>(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(ObservableList<String>(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(ObservableList<String>(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(ObservableList<String>(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList(subList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList<String>(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList<String>(subList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList<String>(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(MutableObservableList<String>(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList<String>(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(MutableObservableList<String>(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf(subList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf<String>(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf<String>(subList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf<String>(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(observableListOf<String>(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf<String>(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(observableListOf<String>(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf(subList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf<String>(inputSubList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf<String>(subList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf<String>(mutableObservableSubList).containsAll(inputSubList))
Assertions.assertTrue(mutableObservableListOf<String>(inputSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf<String>(subSubList).containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableListOf<String>(mutableObservableSubSubList).containsAll(inputSubSubList))
Assertions.assertTrue(inputSubList.toObservableList().containsAll(inputSubList))
Assertions.assertTrue(subList.toObservableList().containsAll(inputSubList))
Assertions.assertTrue(mutableObservableSubList.toObservableList().containsAll(inputSubList))
Assertions.assertTrue(inputSubSubList.toObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(subSubList.toObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableSubSubList.toObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(inputSubList.toObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(subList.toObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(mutableObservableSubList.toObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(inputSubSubList.toObservableList<String>().containsAll(inputSubSubList))
Assertions.assertTrue(subSubList.toObservableList<String>().containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableSubSubList.toObservableList<String>().containsAll(inputSubSubList))
Assertions.assertTrue(inputSubList.toMutableObservableList().containsAll(inputSubList))
Assertions.assertTrue(subList.toMutableObservableList().containsAll(inputSubList))
Assertions.assertTrue(mutableObservableSubList.toMutableObservableList().containsAll(inputSubList))
Assertions.assertTrue(inputSubSubList.toMutableObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(subSubList.toMutableObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableSubSubList.toMutableObservableList().containsAll(inputSubSubList))
Assertions.assertTrue(inputSubList.toMutableObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(subList.toMutableObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(mutableObservableSubList.toMutableObservableList<String>().containsAll(inputSubList))
Assertions.assertTrue(inputSubSubList.toMutableObservableList<String>().containsAll(inputSubSubList))
Assertions.assertTrue(subSubList.toMutableObservableList<String>().containsAll(inputSubSubList))
Assertions.assertTrue(mutableObservableSubSubList.toMutableObservableList<String>().containsAll(inputSubSubList))
}
@Test
fun mapConstruction() {
val testMaps = TestMaps.default()
val inputMap = testMaps.input
val map = testMaps.observable
val mutableMap = testMaps.mutableObservable
val emptyMap = testMaps.emptyObservable
val emptyMutableMap = testMaps.emptyMutableObservable
val entries = inputMap.entries.toList()
val pairs = entries.pairs().toList()
Assertions.assertTrue(map.containsAll(inputMap))
Assertions.assertTrue(mutableMap.containsAll(inputMap))
Assertions.assertTrue(emptyMap.isEmpty())
Assertions.assertTrue(emptyMutableMap.isEmpty())
Assertions.assertTrue(ObservableMap(inputMap).containsAll(inputMap))
Assertions.assertTrue(ObservableMap(map).containsAll(inputMap))
Assertions.assertTrue(ObservableMap(mutableMap).containsAll(inputMap))
Assertions.assertTrue(ObservableMap(emptyMap).isEmpty())
Assertions.assertTrue(ObservableMap(emptyMutableMap).isEmpty())
Assertions.assertTrue(ObservableMap<Int, String>(inputMap).containsAll(inputMap))
Assertions.assertTrue(ObservableMap<Int, String>(map).containsAll(inputMap))
Assertions.assertTrue(ObservableMap<Int, String>(mutableMap).containsAll(inputMap))
Assertions.assertTrue(ObservableMap<Int, String>(emptyMap).isEmpty())
Assertions.assertTrue(ObservableMap<Int, String>(emptyMutableMap).isEmpty())
Assertions.assertTrue(MutableObservableMap(inputMap).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap(map).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap(mutableMap).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap(emptyMap).isEmpty())
Assertions.assertTrue(MutableObservableMap(emptyMutableMap).isEmpty())
Assertions.assertTrue(MutableObservableMap<Int, String>(inputMap).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap<Int, String>(map).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap<Int, String>(mutableMap).containsAll(inputMap))
Assertions.assertTrue(MutableObservableMap<Int, String>(emptyMap).isEmpty())
Assertions.assertTrue(MutableObservableMap<Int, String>(emptyMutableMap).isEmpty())
Assertions.assertTrue(inputMap.toObservableMap().containsAll(inputMap))
Assertions.assertTrue(map.toObservableMap().containsAll(inputMap))
Assertions.assertTrue(mutableMap.toObservableMap().containsAll(inputMap))
Assertions.assertTrue(pairs.toObservableMap().containsAll(inputMap))
Assertions.assertTrue(emptyMap.toObservableMap().isEmpty())
Assertions.assertTrue(emptyMutableMap.toObservableMap().isEmpty())
Assertions.assertTrue(inputMap.toObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(map.toObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(mutableMap.toObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(pairs.toObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(emptyMap.toObservableMap<Int, String>().isEmpty())
Assertions.assertTrue(emptyMutableMap.toObservableMap<Int, String>().isEmpty())
Assertions.assertTrue(inputMap.toMutableObservableMap().containsAll(inputMap))
Assertions.assertTrue(map.toMutableObservableMap().containsAll(inputMap))
Assertions.assertTrue(mutableMap.toMutableObservableMap().containsAll(inputMap))
Assertions.assertTrue(pairs.toMutableObservableMap().containsAll(inputMap))
Assertions.assertTrue(emptyMap.toMutableObservableMap().isEmpty())
Assertions.assertTrue(emptyMutableMap.toMutableObservableMap().isEmpty())
Assertions.assertTrue(inputMap.toMutableObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(map.toMutableObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(mutableMap.toMutableObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(pairs.toMutableObservableMap<Int, String>().containsAll(inputMap))
Assertions.assertTrue(emptyMap.toMutableObservableMap<Int, String>().isEmpty())
Assertions.assertTrue(emptyMutableMap.toMutableObservableMap<Int, String>().isEmpty())
Assertions.assertTrue(observableMapOf(inputMap).containsAll(inputMap))
Assertions.assertTrue(observableMapOf(map).containsAll(inputMap))
Assertions.assertTrue(observableMapOf(mutableMap).containsAll(inputMap))
Assertions.assertTrue(observableMapOf(pairs).containsAll(inputMap))
Assertions.assertTrue(observableMapOf(emptyMap).isEmpty())
Assertions.assertTrue(observableMapOf(emptyMutableMap).isEmpty())
Assertions.assertTrue(observableMapOf<Int, String>(inputMap).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(map).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(mutableMap).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(pairs).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(emptyMap).isEmpty())
Assertions.assertTrue(observableMapOf<Int, String>(emptyMutableMap).isEmpty())
Assertions.assertTrue(mutableObservableMapOf(inputMap).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(map).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(mutableMap).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(pairs).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(emptyMap).isEmpty())
Assertions.assertTrue(mutableObservableMapOf(emptyMutableMap).isEmpty())
Assertions.assertTrue(mutableObservableMapOf<Int, String>(inputMap).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(map).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(mutableMap).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(pairs).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(emptyMap).isEmpty())
Assertions.assertTrue(mutableObservableMapOf<Int, String>(emptyMutableMap).isEmpty())
Assertions.assertTrue(observableMapOf(entries[0], entries[1], entries[2], entries[3]).containsAll(inputMap))
Assertions.assertTrue(observableMapOf(pairs[0], pairs[1], pairs[2], pairs[3]).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(entries[0], entries[1], entries[2], entries[3]).containsAll(inputMap))
Assertions.assertTrue(observableMapOf<Int, String>(pairs[0], pairs[1], pairs[2], pairs[3]).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(entries[0], entries[1], entries[2], entries[3]).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf(pairs[0], pairs[1], pairs[2], pairs[3]).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(entries[0], entries[1], entries[2], entries[3]).containsAll(inputMap))
Assertions.assertTrue(mutableObservableMapOf<Int, String>(pairs[0], pairs[1], pairs[2], pairs[3]).containsAll(inputMap))
}
}
@@ -0,0 +1,280 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.list.MutableObservableList
import com.cobblemon.mod.common.api.reactive.collections.map.MutableObservableMap
import com.cobblemon.mod.common.api.reactive.collections.set.MutableObservableSet
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
object EmittingReferenceTypeTests {
@Test
fun setElementsEmittingChange() = elementsEmittingChange(MutableObservableSet())
@Test
fun listElementsEmittingChange() = elementsEmittingChange(MutableObservableList())
fun <C : Collection<SettableObservable<Int>>> elementsEmittingChange(
mutableObservableCollection: MutableObservableCollection<SettableObservable<Int>, C>
) {
val getNew = { SettableObservable(0) }
val addNew = { c: MutableCollection<SettableObservable<Int>>, count: Int ->
for (i in 0 until count) {
c.add(getNew())
}
}
addNew(mutableObservableCollection, 4)
val observableCollection = mutableObservableCollection.copy()
var anyChangeTally = 0
var subscription = observableCollection.subscribe { anyChangeTally++ }
var mutableAnyChangeTally = 0
var mutableSubscription = mutableObservableCollection.subscribe { mutableAnyChangeTally++ }
val incrementValues = { iterations: Int ->
for (i in 0 until iterations) {
mutableObservableCollection.forEach { it.set(it.get() + 1) }
}
iterations * observableCollection.size
}
var newEmits = incrementValues(1)
var expectedTally = newEmits
var mutableExpectedTally = newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableSubscription.unsubscribe()
newEmits = incrementValues(1)
expectedTally += newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableSubscription = mutableObservableCollection.subscribe { mutableAnyChangeTally++ }
newEmits = incrementValues(1)
expectedTally += newEmits
mutableExpectedTally += newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
addNew(mutableObservableCollection, 4)
mutableExpectedTally += 4
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
newEmits = incrementValues(1)
expectedTally += newEmits
mutableExpectedTally += newEmits + (mutableObservableCollection.size - observableCollection.size)
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableExpectedTally += mutableObservableCollection.size
mutableObservableCollection.clear()
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
incrementValues(1)
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
}
@Test
fun subListElementsEmittingChange() {
val getNew = { SettableObservable(0) }
val addNew = { list: MutableObservableList<SettableObservable<Int>>, count: Int ->
for (i in 0 until count) {
list.add(getNew())
}
}
val mutableObservableList = MutableObservableList<SettableObservable<Int>>()
addNew(mutableObservableList, 4)
val subList = mutableObservableList.subList(1, 3)
var anyChangeTally = 0
var subListAnyChangeTally = 0
val listSub = mutableObservableList.subscribe { anyChangeTally++ }
val subListSub = subList.subscribe { subListAnyChangeTally++ }
val originalZeroElement = mutableObservableList[0]
originalZeroElement.set(originalZeroElement.get() + 1)
var expectedTally = 1
var expectedSubListTally = 0
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
// originalZeroElement pushed into of subList window
mutableObservableList.add(0, getNew())
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 1
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
mutableObservableList.add(0, getNew())
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 1
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
// originalZeroElement pushed out of subList window
mutableObservableList.add(0, getNew())
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 0
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
// originalZeroElement drops back into subList window
mutableObservableList.removeAt(0)
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 1
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
mutableObservableList.removeAt(0)
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 1
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
// originalZeroElement drops out of subList window
mutableObservableList.removeAt(0)
expectedTally += 1
expectedSubListTally += 2
originalZeroElement.set(originalZeroElement.get() + 1)
expectedTally += 1
expectedSubListTally += 0
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(subListAnyChangeTally == expectedSubListTally)
}
@Test
fun entriesEmittingChange() {
val getNew = { SettableObservable(0) }
val putNew = {
map: MutableObservableMap<SettableObservable<Int>, SettableObservable<Int>>,
count: Int,
->
for (i in 0 until count) {
map.put(getNew(), getNew())
}
}
val mutableObservableMap = MutableObservableMap<SettableObservable<Int>, SettableObservable<Int>>()
putNew(mutableObservableMap, 4)
val observableMap = mutableObservableMap.copy()
var anyChangeTally = 0
var subscription = observableMap.subscribe { anyChangeTally++ }
var mutableAnyChangeTally = 0
var mutableSubscription = mutableObservableMap.subscribe { mutableAnyChangeTally++ }
val incrementValues = { iterations: Int ->
for (i in 0 until iterations) {
mutableObservableMap.forEach { (key, value) ->
key.set(key.get() + 1)
value.set(value.get() + 1)
}
}
iterations * observableMap.size * 2
}
var newEmits = incrementValues(1)
var expectedTally = newEmits
var mutableExpectedTally = newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableSubscription.unsubscribe()
newEmits = incrementValues(1)
expectedTally += newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableSubscription = mutableObservableMap.subscribe { mutableAnyChangeTally++ }
newEmits = incrementValues(1)
expectedTally += newEmits
mutableExpectedTally += newEmits
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
putNew(mutableObservableMap, 4)
mutableExpectedTally += 4
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
newEmits = incrementValues(1)
expectedTally += newEmits
mutableExpectedTally += newEmits + (2 * (mutableObservableMap.size - observableMap.size))
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
mutableExpectedTally += mutableObservableMap.size
mutableObservableMap.clear()
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
incrementValues(1)
Assertions.assertTrue(anyChangeTally == expectedTally)
Assertions.assertTrue(mutableAnyChangeTally == mutableExpectedTally)
}
}
@@ -0,0 +1,835 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.list.MutableObservableList
import com.cobblemon.mod.common.api.reactive.collections.map.mutableObservableMapOf
import com.cobblemon.mod.common.api.reactive.collections.set.MutableObservableSet
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
object EmittingTests {
@Test
fun setAsCollectionEmitChange() = collectionEmitChange(MutableObservableSet())
@Test
fun listAsCollectionEmitChange() = collectionEmitChange(MutableObservableList())
private fun collectionEmitChange(elements: MutableObservableCollection<String, *>) {
var anyChangeOne = 0
var additionOne = 0
var removalOne = 0
var anyChangeTwo = 0
var additionTwo = 0
var removalTwo = 0
var expectedAnyChangeOne = 0
var expectedAdditionOne = 0
var expectedRemovalOne = 0
var expectedAnyChangeTwo = 0
var expectedAdditionTwo = 0
var expectedRemovalTwo = 0
val confirmExpected = {
Assertions.assertTrue(anyChangeOne == expectedAnyChangeOne)
Assertions.assertTrue(additionOne == expectedAdditionOne)
Assertions.assertTrue(removalOne == expectedRemovalOne)
Assertions.assertTrue(anyChangeTwo == expectedAnyChangeTwo)
Assertions.assertTrue(additionTwo == expectedAdditionTwo)
Assertions.assertTrue(removalTwo == expectedRemovalTwo)
}
val getNewSubOne = {
elements.subscribe(
anyChangeHandler = { anyChangeOne++ },
additionHandler = { additionOne++ },
removalHandler = { removalOne++ },
)
}
val getNewSubTwo = {
elements.subscribe(
anyChangeHandler = { anyChangeTwo++ },
additionHandler = { additionTwo++ },
removalHandler = { removalTwo++ },
)
}
val subscriptionOne = getNewSubOne()
val subscriptionTwo = getNewSubTwo()
var subbedAnyChangeOne = true
var subbedAdditionOne = true
var subbedRemovalOne = true
var subbedAnyChangeTwo = true
var subbedAdditionTwo = true
var subbedRemovalTwo = true
val incrementExpectedAddition = { value: Int ->
if (subbedAnyChangeOne) expectedAnyChangeOne += value
if (subbedAdditionOne) expectedAdditionOne += value
if (subbedAnyChangeTwo) expectedAnyChangeTwo += value
if (subbedAdditionTwo) expectedAdditionTwo += value
}
val incrementExpectedRemoval = { value: Int ->
if (subbedAnyChangeOne) expectedAnyChangeOne += value
if (subbedRemovalOne) expectedRemovalOne += value
if (subbedAnyChangeTwo) expectedAnyChangeTwo += value
if (subbedRemovalTwo) expectedRemovalTwo += value
}
var currentCount = 0
val next = { "${currentCount++}" }
val bulkInput = listOf(next(), next(), next(), next())
val addNew = { iterations: Int ->
for (i in 0 until iterations) {
if (elements.add(next())) incrementExpectedAddition(1)
}
confirmExpected()
}
val addDuplicates = { iterations: Int ->
elements.firstOrNull() ?. let{ dupe ->
for (i in 0 until iterations) {
if (elements.add(dupe)) incrementExpectedAddition(1)
}
}
confirmExpected()
}
val addNewIfTrue = { iterations: Int ->
for (i in 0 until iterations) {
if (elements.addIf(next()) { true }) incrementExpectedAddition(1)
}
confirmExpected()
}
val addNewIfFalse = { iterations: Int ->
for (i in 0 until iterations) {
if (elements.addIf(next()) { false }) incrementExpectedAddition(1)
}
confirmExpected()
}
val addDupeIfTrue = { iterations: Int ->
elements.firstOrNull() ?. let{ dupe ->
for (i in 0 until iterations) {
if (elements.addIf(dupe) { true }) incrementExpectedAddition(1)
}
}
confirmExpected()
}
val addDupeIfFalse = { iterations: Int ->
elements.firstOrNull() ?. let{ dupe ->
for (i in 0 until iterations) {
if (elements.addIf(dupe) { false }) incrementExpectedAddition(1)
}
}
confirmExpected()
}
val remove = { iterations: Int ->
for (i in 0 until iterations) {
elements.firstOrNull()?.let { if (elements.remove(it)) incrementExpectedRemoval(1) }
}
confirmExpected()
}
val removeNotContained = { iterations: Int ->
for (i in 0 until iterations) {
if (elements.remove("I am not here...")) incrementExpectedRemoval(1)
}
confirmExpected()
}
val removeDupeIfTrue = { iterations: Int ->
elements.firstOrNull() ?. let{ dupe ->
for (i in 0 until iterations) {
val size = elements.size
elements.removeIf { it == dupe }
val change = size - elements.size
if (change > 0) incrementExpectedRemoval(change)
}
}
confirmExpected()
}
val removeDupeIfFalse = { iterations: Int ->
elements.firstOrNull() ?. let{ dupe ->
for (i in 0 until iterations) {
if (elements.removeIf { false }) incrementExpectedRemoval(1)
}
}
confirmExpected()
}
val addAll = { iterations: Int ->
for (i in 0 until iterations) {
val size = elements.size
if (elements.addAll(bulkInput)) incrementExpectedAddition(elements.size - size)
}
confirmExpected()
}
val removeAll = { iterations: Int ->
for (i in 0 until iterations) {
val size = elements.size
if (elements.removeAll(bulkInput)) incrementExpectedRemoval(size - elements.size)
}
confirmExpected()
}
val retainAll = { iterations: Int ->
for (i in 0 until iterations) {
val size = elements.size
if (elements.retainAll(bulkInput)) incrementExpectedRemoval(size - elements.size)
}
confirmExpected()
}
val clear = { iterations: Int ->
for (i in 0 until iterations) {
val size = elements.size
elements.clear()
val change = size - elements.size
if (change > 0) incrementExpectedRemoval(change)
}
confirmExpected()
}
val runAll = { iterations: Int ->
addNew(iterations)
addDuplicates(iterations)
addNewIfTrue(iterations)
addNewIfFalse(iterations)
addDupeIfTrue(iterations)
addDupeIfFalse(iterations)
remove(iterations)
removeNotContained(iterations)
removeDupeIfTrue(iterations)
removeDupeIfFalse(iterations)
addAll(iterations)
removeAll(iterations)
retainAll(iterations)
clear(iterations)
}
runAll(8)
subscriptionOne.unsubscribe()
subbedAnyChangeOne = false
subbedAdditionOne = false
subbedRemovalOne = false
runAll(8)
subscriptionTwo.anyChange.unsubscribe()
subbedAnyChangeTwo = false
runAll(8)
subscriptionTwo.addition?.unsubscribe()
subscriptionTwo.removal?.unsubscribe()
subbedAdditionTwo = false
subbedRemovalTwo = false
runAll(8)
val newSubOne = getNewSubOne()
newSubOne.addition?.unsubscribe()
newSubOne.removal?.unsubscribe()
subbedAnyChangeOne = true
runAll(8)
val newSubTwo = getNewSubTwo()
newSubTwo.addition?.unsubscribe()
newSubTwo.removal?.unsubscribe()
subbedAnyChangeTwo = true
runAll(8)
}
@Test
fun listAndSubListEmitChange() {
var anyChangeList = 0
var additionList = 0
var removalList = 0
var setList = 0
var swapList = 0
var anyChangeSubList = 0
var additionSubList = 0
var removalSubList = 0
var setSubList = 0
var swapSubList = 0
var expectedAnyChangeList = 0
var expectedAdditionList = 0
var expectedRemovalList = 0
var expectedSetList = 0
var expectedSwapList = 0
var expectedAnyChangeSubList = 0
var expectedAdditionSubList = 0
var expectedRemovalSubList = 0
var expectedSetSubList = 0
var expectedSwapSubList = 0
val input = listOf("zero", "one", "two", "three")
val list = MutableObservableList(input)
val fromIndex = 1
val toIndex = 3
val subList = list.subList(fromIndex, toIndex)
val isInWindow: Int.() -> Boolean = { this in fromIndex until toIndex }
val listSub = list.subscribe(
priority = Priority.NORMAL,
anyChangeHandler = { anyChangeList++ },
additionHandler = { additionList++ },
removalHandler = { removalList++ },
setHandler = { setList++ },
swapHandler = { swapList++ },
)
val subListSub = subList.subscribe(
priority = Priority.NORMAL,
anyChangeHandler = { anyChangeSubList++ },
additionHandler = { additionSubList++ },
removalHandler = { removalSubList++ },
setHandler = { setSubList++ },
swapHandler = { swapSubList++ },
)
val subscribedToAnyChange = true
val subscribedToAddition = true
val subscribedToRemoval = true
val subscribedToSet = true
val subscribedToSwap = true
val subscribedToSubListAnyChange = true
val subscribedToSubListAddition = true
val subscribedToSubListRemoval = true
val subscribedToSubListSet = true
val subscribedToSubListSwap = true
val incrementAddition = { count: Int, index: Int ->
if (subscribedToAnyChange) expectedAnyChangeList += count
if (subscribedToAddition) expectedAdditionList += count
if (index < toIndex && fromIndex <= list.lastIndex) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count
if (subscribedToSubListAddition) expectedAdditionSubList += count
// removal for the elements pushed out of the sublist window
if (toIndex <= list.lastIndex) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count
if (subscribedToSubListRemoval) expectedRemovalSubList += count
}
}
}
val incrementRemoval = { count: Int, index: Int ->
if (subscribedToAnyChange) expectedAnyChangeList += count
if (subscribedToRemoval) expectedRemovalList += count
if (index < toIndex) {
if (fromIndex <= list.size) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count
if (subscribedToSubListRemoval) expectedRemovalSubList += count
}
// addition for the elements dropping into the sublist window
if (toIndex <= list.size) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count
if (subscribedToSubListAddition) expectedAdditionSubList += count
}
}
}
val incrementSet = { count: Int, index: Int ->
if (subscribedToAnyChange) expectedAnyChangeList += count * 2
if (subscribedToSet) expectedSetList += count
if (index.isInWindow()) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count * 2
if (subscribedToSubListSet) expectedSetSubList += count
}
}
val incrementSwap = { count: Int, indexOne: Int, indexTwo: Int ->
if (subscribedToAnyChange) expectedAnyChangeList += count * 2
if (subscribedToSwap) expectedSwapList += count
if (indexOne.isInWindow() && indexTwo.isInWindow()) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count * 2
if (subscribedToSubListSwap) expectedSwapSubList += count
} else if ((!indexOne.isInWindow() && indexTwo.isInWindow()) ||
(indexOne.isInWindow() && !indexTwo.isInWindow())) {
if (subscribedToSubListAnyChange) expectedAnyChangeSubList += count * 2
if (subscribedToSubListRemoval) expectedAdditionSubList += count
if (subscribedToAnyChange) expectedRemovalSubList += count
}
}
val confirmExpected = {
Assertions.assertTrue(anyChangeList == expectedAnyChangeList)
Assertions.assertTrue(additionList == expectedAdditionList)
Assertions.assertTrue(removalList == expectedRemovalList)
Assertions.assertTrue(setList == expectedSetList)
Assertions.assertTrue(swapList == expectedSwapList)
Assertions.assertTrue(anyChangeSubList == expectedAnyChangeSubList)
Assertions.assertTrue(additionSubList == expectedAdditionSubList)
Assertions.assertTrue(removalSubList == expectedRemovalSubList)
Assertions.assertTrue(setSubList == expectedSetSubList)
Assertions.assertTrue(swapSubList == expectedSwapSubList)
}
val confirmSynced: () -> Unit = {
for (i in 0 until subList.size) {
if (list.size > i + fromIndex) {
Assertions.assertTrue(subList[i] == list[i + fromIndex])
}
}
}
var currentCount = 0
val next = { "${currentCount++}" }
val add = { iterations: Int ->
for (i in 0 until iterations) {
list.add(next())
incrementAddition(1, list.lastIndex)
}
confirmExpected()
confirmSynced()
}
val addIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
list.addIf(next()) { bool }
if (bool) {
incrementAddition(1, list.lastIndex)
}
}
confirmExpected()
confirmSynced()
}
val addAtStartIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.addAtIf(0, next()) { bool }
if (bool) {
incrementAddition(1, 0)
}
}
confirmExpected()
confirmSynced()
}
val addAtEndIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.addAtIf(list.lastIndex, next()) { bool }
if (bool) {
incrementAddition(1, list.lastIndex - 1)
}
}
confirmExpected()
confirmSynced()
}
val removeFromStart = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.firstOrNull()?.let {
if (list.remove(it)) {
incrementRemoval(1, 0)
}
}
}
confirmExpected()
confirmSynced()
}
val removeFromEnd = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.removeLast()
incrementRemoval(1, list.size)
}
confirmExpected()
confirmSynced()
}
val removeAtStartIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.removeAtIf(0) { bool }
if (bool) {
incrementRemoval(1, 0)
}
}
confirmExpected()
confirmSynced()
}
val removeAtEndIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.removeAtIf(list.lastIndex) { bool }
if (bool) {
incrementRemoval(1, list.lastIndex)
}
}
confirmExpected()
confirmSynced()
}
val setStart = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list[0] = next()
incrementSet(1, 0)
}
confirmExpected()
confirmSynced()
}
val setEnd = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list[list.lastIndex] = next()
incrementSet(1, list.lastIndex)
}
confirmExpected()
confirmSynced()
}
val setStartIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.setIf(0, next()) { bool }
if (bool) {
incrementSet(1, 0)
}
}
confirmExpected()
confirmSynced()
}
val setEndIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list.setIf(list.lastIndex, next()) { bool }
if (bool) {
incrementSet(1, list.lastIndex)
}
}
confirmExpected()
confirmSynced()
}
val swapStart = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list[0] = next()
incrementSet(1, 0)
}
confirmExpected()
confirmSynced()
}
val swapEnd = { iterations: Int ->
for (i in 0 until iterations) {
if (list.isEmpty()) {
add(1)
}
list[list.lastIndex] = next()
incrementSet(1, list.lastIndex)
}
confirmExpected()
confirmSynced()
}
val swapStartIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.size < 2) {
add(2)
}
list.swapIf(0, 1) { bool }
if (bool) {
incrementSwap(1, 0, 1)
}
}
confirmExpected()
confirmSynced()
}
val swapEndIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.size < 2) {
add(2)
}
list.swapIf(list.lastIndex, list.lastIndex - 1) { bool }
if (bool) {
incrementSwap(1, list.lastIndex, list.lastIndex - 1)
}
}
confirmExpected()
confirmSynced()
}
val swapInsideOut = { iterations: Int ->
for (i in 0 until iterations) {
if (list.size < fromIndex) {
add(fromIndex + 1)
}
list.swap(0, fromIndex)
incrementSwap(1, 0, fromIndex)
}
confirmExpected()
confirmSynced()
}
val swapInsideOutIf = { iterations: Int, bool: Boolean ->
for (i in 0 until iterations) {
if (list.size < fromIndex) {
add(fromIndex + 1)
}
list.swapIf(0, fromIndex) { bool }
if (bool) {
incrementSwap(1, 0, fromIndex)
}
}
confirmExpected()
confirmSynced()
}
val runAll = { startingSize: Int ->
if (list.size < startingSize) {
add(startingSize - list.size)
} else if (list.size > startingSize) {
removeFromStart(list.size - startingSize)
}
add(1)
addIf(1, true)
addIf(1, false)
addAtStartIf(1, true)
addAtStartIf(1, false)
addAtEndIf(1, true)
addAtEndIf(1, false)
removeFromStart(1)
removeFromEnd(1)
removeAtStartIf(1, true)
removeAtEndIf(1, false)
setStart(1)
setStartIf(1, true)
setStartIf(1, false)
setEnd(1)
setEndIf(1, true)
setEndIf(1, false)
swapStart(1)
swapStartIf(1, true)
swapStartIf(1, false)
swapEnd(1)
swapEndIf(1, true)
swapEndIf(1, false)
swapInsideOut(1)
swapInsideOutIf(1, true)
swapInsideOutIf(1, false)
}
runAll(0)
runAll(2)
runAll(10)
}
@Test
fun mapEmitChange() {
val mutableMap = mutableObservableMapOf(TestMaps.defaultMap())
var anyChangeOne = 0
var additionOne = 0
var removalOne = 0
var anyChangeTwo = 0
var additionTwo = 0
var removalTwo = 0
var expectedAnyChangeOne = 0
var expectedAdditionOne = 0
var expectedRemovalOne = 0
var expectedAnyChangeTwo = 0
var expectedAdditionTwo = 0
var expectedRemovalTwo = 0
val confirmExpected = {
Assertions.assertTrue(anyChangeOne == expectedAnyChangeOne)
Assertions.assertTrue(additionOne == expectedAdditionOne)
Assertions.assertTrue(removalOne == expectedRemovalOne)
Assertions.assertTrue(anyChangeTwo == expectedAnyChangeTwo)
Assertions.assertTrue(additionTwo == expectedAdditionTwo)
Assertions.assertTrue(removalTwo == expectedRemovalTwo)
}
val getNewSubOne = {
mutableMap.subscribe(
anyChangeHandler = { anyChangeOne++ },
additionHandler = { additionOne++ },
removalHandler = { removalOne++ },
)
}
val getNewSubTwo = {
mutableMap.subscribe(
anyChangeHandler = { anyChangeTwo++ },
additionHandler = { additionTwo++ },
removalHandler = { removalTwo++ },
)
}
val subscriptionOne = getNewSubOne()
val subscriptionTwo = getNewSubTwo()
var subbedAnyChangeOne = true
var subbedAdditionOne = true
var subbedRemovalOne = true
var subbedAnyChangeTwo = true
var subbedAdditionTwo = true
var subbedRemovalTwo = true
val incrementExpectedAddition = { value: Int ->
if (subbedAnyChangeOne) expectedAnyChangeOne += value
if (subbedAdditionOne) expectedAdditionOne += value
if (subbedAnyChangeTwo) expectedAnyChangeTwo += value
if (subbedAdditionTwo) expectedAdditionTwo += value
}
val incrementExpectedRemoval = { value: Int ->
if (subbedAnyChangeOne) expectedAnyChangeOne += value
if (subbedRemovalOne) expectedRemovalOne += value
if (subbedAnyChangeTwo) expectedAnyChangeTwo += value
if (subbedRemovalTwo) expectedRemovalTwo += value
}
var currentCount = 0
val next = { currentCount to "${currentCount++}" }
val bulkInput = mapOf(next(), next(), next(), next())
val putNew = { iterations: Int ->
for (i in 0 until iterations) {
if (mutableMap.put(next()) != null) {
incrementExpectedRemoval(1)
}
incrementExpectedAddition(1)
}
confirmExpected()
}
val putCurrentKeys = { iterations: Int ->
mutableMap.firstKeyOrNull()?. let { dupe ->
for (i in 0 until iterations) {
if (mutableMap.put(dupe, next().second) != null) {
incrementExpectedRemoval(1)
}
incrementExpectedAddition(1)
}
}
confirmExpected()
}
val removeNew = { iterations: Int ->
for (i in 0 until iterations) {
if (mutableMap.remove(next().first) != null) {
incrementExpectedRemoval(1)
}
}
confirmExpected()
}
val removeCurrent = { iterations: Int ->
for (i in 0 until iterations) {
if (mutableMap.remove(next().first) != null) {
incrementExpectedRemoval(1)
}
}
confirmExpected()
}
val putAll = { iterations: Int ->
for (i in 0 until iterations) {
var replacedCount = 0
bulkInput.forEach { if (mutableMap.keys.contains(it.key)) replacedCount++ }
mutableMap.putAll(bulkInput)
incrementExpectedAddition(bulkInput.size)
incrementExpectedRemoval(replacedCount)
}
confirmExpected()
}
val clear = { iterations: Int ->
for (i in 0 until iterations) {
val size = mutableMap.size
mutableMap.clear()
val change = size - mutableMap.size
if (change > 0) incrementExpectedRemoval(change)
}
confirmExpected()
}
val runAll = { iterations: Int ->
putNew(iterations)
putCurrentKeys(iterations)
removeNew(iterations)
removeCurrent(iterations)
putAll(iterations)
clear(iterations)
}
runAll(8)
subscriptionOne.unsubscribe()
subbedAnyChangeOne = false
subbedAdditionOne = false
subbedRemovalOne = false
runAll(8)
subscriptionTwo.anyChange.unsubscribe()
subbedAnyChangeTwo = false
runAll(8)
subscriptionTwo.addition?.unsubscribe()
subscriptionTwo.removal?.unsubscribe()
subbedAdditionTwo = false
subbedRemovalTwo = false
runAll(8)
val newSubOne = getNewSubOne()
newSubOne.addition?.unsubscribe()
newSubOne.removal?.unsubscribe()
subbedAnyChangeOne = true
runAll(8)
val newSubTwo = getNewSubTwo()
newSubTwo.addition?.unsubscribe()
newSubTwo.removal?.unsubscribe()
subbedAnyChangeTwo = true
runAll(8)
}
}
@@ -0,0 +1,242 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.removeIf
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import kotlin.collections.Map.Entry
object IteratorTests {
@Test
fun setIterator() {
val testSets = TestSets.default()
iteratorTest(testSets)
bulkMutationTest(testSets)
}
@Test
fun listIterator() {
val testLists = TestLists.default()
iteratorTest(testLists)
bulkMutationTest(testLists)
}
@Test
fun subListIterator() {
val lists = TestSubLists.default()
subListIteratorTest(lists)
subListBulkMutationTest(lists)
}
private fun <T> subListIteratorTest(lists: TestSubLists<T>) {
var inputSubListCount = 0
var mutableInputSubListCount = 0
var subListCount = 0
var mutableSubListCount = 0
lists.inputSub.iterator().forEach { _ -> inputSubListCount++ }
lists.mutableInputSub.iterator().forEach { _ -> mutableInputSubListCount++ }
lists.observableSub.iterator().forEach { _ -> subListCount++ }
lists.mutableObservableSub.iterator().forEach { _ -> mutableSubListCount++ }
Assertions.assertTrue(inputSubListCount == lists.inputSub.size)
Assertions.assertTrue(mutableInputSubListCount == lists.mutableInputSub.size)
Assertions.assertTrue(subListCount == lists.observableSub.size)
Assertions.assertTrue(mutableSubListCount == lists.mutableObservableSub.size)
}
private fun <T> subListBulkMutationTest(lists: TestSubLists<T>) {
// confirm mutations via iterator internally are tracked & reflected consistent with
// kotlin mutable set implementations
lists.mutableInputSub.removeAll(lists.inputSub)
lists.mutableObservableSub.removeAll(lists.inputSub)
Assertions.assertTrue(lists.mutableObservableSub.containsAll(lists.mutableInputSub))
Assertions.assertTrue(lists.mutableInputSub.containsAll(lists.mutableObservableSub))
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableInputSub))
Assertions.assertTrue(!lists.mutableInputSub.containsAll(lists.inputSub))
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableObservableSub))
Assertions.assertTrue(!lists.mutableObservableSub.containsAll(lists.inputSub))
lists.mutableInputSub.addAll(lists.inputSub)
lists.mutableObservableSub.addAll(lists.inputSub)
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableInputSub))
Assertions.assertTrue(lists.mutableInputSub.containsAll(lists.inputSub))
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableObservableSub))
Assertions.assertTrue(lists.mutableObservableSub.containsAll(lists.inputSub))
lists.mutableInputSub.retainAll(lists.inputSub)
lists.mutableObservableSub.retainAll(lists.inputSub)
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableInputSub))
Assertions.assertTrue(lists.mutableInputSub.containsAll(lists.inputSub))
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableObservableSub))
Assertions.assertTrue(lists.mutableObservableSub.containsAll(lists.inputSub))
Assertions.assertTrue(lists.mutableObservable.containsAll(lists.mutableInput))
Assertions.assertTrue(lists.mutableObservable.size == lists.mutableInput.size)
// back to starting lists
lists.mutableInputSub.clear()
lists.mutableObservableSub.clear()
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableInputSub))
Assertions.assertTrue(!lists.mutableInputSub.containsAll(lists.inputSub))
Assertions.assertTrue(lists.inputSub.containsAll(lists.mutableObservableSub))
Assertions.assertTrue(!lists.mutableObservableSub.containsAll(lists.inputSub))
}
private fun iteratorTest(collections: TestCollections<*, *, *>) {
var inputCount = 0
var mutableInputCount = 0
var collectionCount = 0
var mutableCollectionCount = 0
var emptyCollectionCount = 0
var emptyMutableCollectionCount = 0
collections.input.iterator().forEach { _ -> inputCount++ }
collections.mutableInput.iterator().forEach { _ -> mutableInputCount++ }
collections.observable.iterator().forEach { _ -> collectionCount++ }
collections.mutableObservable.iterator().forEach { _ -> mutableCollectionCount++ }
collections.emptyObservable.iterator().forEach { _ -> emptyCollectionCount++ }
collections.emptyMutableObservable.iterator().forEach { _ -> emptyMutableCollectionCount++ }
Assertions.assertTrue(inputCount == collections.input.size)
Assertions.assertTrue(mutableInputCount == collections.mutableInput.size)
Assertions.assertTrue(collectionCount == collections.observable.size)
Assertions.assertTrue(mutableCollectionCount == collections.mutableObservable.size)
Assertions.assertTrue(emptyCollectionCount == collections.emptyObservable.size)
Assertions.assertTrue(emptyMutableCollectionCount == collections.emptyMutableObservable.size)
}
private fun <T, C : Collection<T>, M : MutableCollection<T>> bulkMutationTest(
collections: TestCollections<T, C, M>
) {
// confirm mutations via iterator internally are tracked & reflected consistent with
// kotlin mutable set implementations
collections.mutableInput.removeAll(collections.input)
collections.mutableObservable.removeAll(collections.input)
collections.emptyMutableObservable.removeAll(collections.input)
iteratorTest(collections)
Assertions.assertTrue(collections.input.containsAll(collections.mutableInput))
Assertions.assertTrue(!collections.mutableInput.containsAll(collections.input))
Assertions.assertTrue(collections.input.containsAll(collections.mutableObservable))
Assertions.assertTrue(!collections.mutableObservable.containsAll(collections.input))
Assertions.assertTrue(collections.emptyMutableObservable.isEmpty())
collections.mutableInput.addAll(collections.input)
collections.mutableObservable.addAll(collections.input)
collections.emptyMutableObservable.addAll(collections.input)
iteratorTest(collections)
Assertions.assertTrue(collections.input.containsAll(collections.mutableInput))
Assertions.assertTrue(collections.mutableInput.containsAll(collections.input))
Assertions.assertTrue(collections.input.containsAll(collections.mutableObservable))
Assertions.assertTrue(collections.mutableObservable.containsAll(collections.input))
Assertions.assertTrue(collections.emptyMutableObservable.isNotEmpty())
collections.mutableInput.retainAll(collections.input)
collections.mutableObservable.retainAll(collections.input)
collections.emptyMutableObservable.retainAll(collections.input)
iteratorTest(collections)
Assertions.assertTrue(collections.input.containsAll(collections.mutableInput))
Assertions.assertTrue(collections.mutableInput.containsAll(collections.input))
Assertions.assertTrue(collections.input.containsAll(collections.mutableObservable))
Assertions.assertTrue(collections.mutableObservable.containsAll(collections.input))
Assertions.assertTrue(collections.emptyMutableObservable.isNotEmpty())
}
@Test
fun mapIterator() {
val testMaps = TestMaps.default()
mapIteratorTest(testMaps)
bulkMapMutationTest(testMaps)
}
private fun <K, V> mapIteratorTest(maps: TestMaps<K, V>) {
var inputCount = 0
var mutableInputCount = 0
var mapCount = 0
var mutableMapCount = 0
var emptyMapCount = 0
var emptyMutableMapCount = 0
maps.input.iterator().forEach { _ -> inputCount++ }
maps.mutableInput.iterator().forEach { _ -> mutableInputCount++ }
maps.observable.iterator().forEach { _ -> mapCount++ }
maps.mutableObservable.iterator().forEach { _ -> mutableMapCount++ }
maps.emptyObservable.iterator().forEach { _ -> emptyMapCount++ }
maps.emptyMutableObservable.iterator().forEach { _ -> emptyMutableMapCount++ }
Assertions.assertTrue(inputCount == maps.input.size)
Assertions.assertTrue(mutableInputCount == maps.mutableInput.size)
Assertions.assertTrue(mapCount == maps.observable.size)
Assertions.assertTrue(mutableMapCount == maps.mutableObservable.size)
Assertions.assertTrue(emptyMapCount == maps.emptyObservable.size)
Assertions.assertTrue(emptyMutableMapCount == maps.emptyMutableObservable.size)
}
private fun <K, V> bulkMapMutationTest(maps: TestMaps<K, V>) {
removeIf(maps.mutableInput) { entry -> maps.input.any { match(entry, it) } }
removeIf(maps.mutableObservable) { entry -> maps.input.any { match(entry, it) } }
removeIf(maps.emptyMutableObservable) { entry -> maps.input.any { match(entry, it) } }
mapIteratorTest(maps)
Assertions.assertTrue(containsAll(maps.input, maps.mutableInput))
Assertions.assertTrue(!containsAll(maps.mutableInput, maps.input))
Assertions.assertTrue(containsAll(maps.input, maps.mutableObservable))
Assertions.assertTrue(!containsAll(maps.mutableObservable, maps.input))
Assertions.assertTrue(maps.emptyMutableObservable.isEmpty())
maps.mutableInput.putAll(maps.input)
maps.mutableObservable.putAll(maps.input)
maps.emptyMutableObservable.putAll(maps.input)
mapIteratorTest(maps)
Assertions.assertTrue(containsAll(maps.input, maps.mutableInput))
Assertions.assertTrue(containsAll(maps.mutableInput, maps.input))
Assertions.assertTrue(containsAll(maps.input, maps.mutableObservable))
Assertions.assertTrue(containsAll(maps.mutableObservable, maps.input))
Assertions.assertTrue(maps.emptyMutableObservable.isNotEmpty())
removeIf(maps.mutableInput) { entry -> !maps.input.any { match(entry, it) } }
removeIf(maps.mutableObservable) { entry -> !maps.input.any { match(entry, it) } }
removeIf(maps.emptyMutableObservable) { entry -> !maps.input.any { match(entry, it) } }
mapIteratorTest(maps)
Assertions.assertTrue(containsAll(maps.input, maps.mutableInput))
Assertions.assertTrue(containsAll(maps.mutableInput, maps.input))
Assertions.assertTrue(containsAll(maps.input, maps.mutableObservable))
Assertions.assertTrue(containsAll(maps.mutableObservable, maps.input))
Assertions.assertTrue(maps.emptyMutableObservable.isNotEmpty())
}
private fun <K, V> match(entry: Entry<K, V>, other: Entry<K, V>): Boolean {
return entry.key == other.key && entry.value == other.value
}
private fun <K, V> removeIf(
map: MutableMap<K, V>,
predicate: (Entry<K, V>) -> Boolean,
) {
map.iterator().removeIf { predicate(it) }
}
private fun <K, V> containsAll(map: Map<K, V>, other: Map<K, V>): Boolean {
return when {
other.isEmpty() -> true
map.isEmpty() -> false
else -> {
map.forEach { entry ->
if (!other.any { it.key == entry.key && it.value == entry.value }) {
return false
}
}
return true
}
}
}
}
@@ -0,0 +1,177 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.list.loadMutableObservableListOf
import com.cobblemon.mod.common.api.reactive.collections.list.loadObservableListOf
import com.cobblemon.mod.common.api.reactive.collections.list.saveToNbt
import com.cobblemon.mod.common.api.reactive.collections.map.loadMutableObservableMapOf
import com.cobblemon.mod.common.api.reactive.collections.map.loadObservableMapOf
import com.cobblemon.mod.common.api.reactive.collections.map.saveToNbt
import com.cobblemon.mod.common.api.reactive.collections.set.loadMutableObservableSetOf
import com.cobblemon.mod.common.api.reactive.collections.set.loadObservableSetOf
import com.cobblemon.mod.common.api.reactive.collections.set.saveToNbt
import net.minecraft.nbt.NbtCompound
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import kotlin.collections.Map.Entry
object SerializationTests {
@Test
fun setSerialization() {
val sets = TestSets.default()
val saveElementHandler = { element: String -> NbtCompound().also { it.putString("element", element) } }
val setNbt = sets.observable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(setNbt.getInt("size") == sets.observable.size)
val setFromNbt = setNbt.loadObservableSetOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(setFromNbt.containsAll(sets.input))
val mutableSetNbt = sets.mutableObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(mutableSetNbt.getInt("size") == sets.mutableObservable.size)
val mutableSetFromNbt = mutableSetNbt.loadMutableObservableSetOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(mutableSetFromNbt.containsAll(sets.input))
val emptySetNbt = sets.emptyObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(emptySetNbt.getInt("size") == sets.emptyObservable.size)
val emptySetFromNbt = emptySetNbt.loadObservableSetOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(emptySetFromNbt.isEmpty())
val emptyMutableSetNbt = sets.emptyMutableObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(emptyMutableSetNbt.getInt("size") == sets.emptyMutableObservable.size)
val emptyMutableSetFromNbt = emptyMutableSetNbt.loadMutableObservableSetOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(emptyMutableSetFromNbt.isEmpty())
}
@Test
fun listSerialization() {
val lists = TestLists.default()
val saveElementHandler = { element: String -> NbtCompound().also { it.putString("element", element) } }
val listNbt = lists.observable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(listNbt.getInt("size") == lists.observable.size)
val listFromNbt = listNbt.loadObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(listFromNbt.containsAll(lists.input))
val mutableListNbt = lists.mutableObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(mutableListNbt.getInt("size") == lists.mutableObservable.size)
val mutableListFromNbt = mutableListNbt.loadMutableObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(mutableListFromNbt.containsAll(lists.input))
val emptyListNbt = lists.emptyObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(emptyListNbt.getInt("size") == lists.emptyObservable.size)
val emptyListFromNbt = emptyListNbt.loadObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(emptyListFromNbt.isEmpty())
val emptyMutableListNbt = lists.emptyMutableObservable.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(emptyMutableListNbt.getInt("size") == lists.emptyMutableObservable.size)
val emptyMutableListFromNbt = emptyMutableListNbt.loadMutableObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(emptyMutableListFromNbt.isEmpty())
}
@Test
fun subListSerialization() {
val lists = TestSubLists.default()
val saveElementHandler = { element: String -> NbtCompound().also { it.putString("element", element) } }
val subListNbt = lists.observableSub.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(subListNbt.getInt("size") == lists.observableSub.size)
val subListFromNbt = subListNbt.loadObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(subListFromNbt.containsAll(lists.observableSub))
val mutableSubListNbt = lists.mutableObservableSub.saveToNbt { saveElementHandler(it) }
Assertions.assertTrue(mutableSubListNbt.getInt("size") == lists.mutableObservableSub.size)
val mutableSubListFromNbt = mutableSubListNbt.loadMutableObservableListOf(
elementKey = "element",
loadElementHandler = { it.getString("element") },
)
Assertions.assertTrue(mutableSubListFromNbt.containsAll(lists.mutableObservableSub))
}
@Test
fun mapSerialization() {
val maps = TestMaps.default()
val saveEntryHandler = { (key, value): Entry<Int, String> ->
val entryNbt = NbtCompound()
entryNbt.putInt("key", key)
entryNbt.putString("value", value)
entryNbt
}
val mapNbt = maps.observable.saveToNbt { saveEntryHandler(it) }
Assertions.assertTrue(mapNbt.getInt("size") == maps.observable.size)
val mapFromNbt = mapNbt.loadObservableMapOf(
entryKey = "entry",
loadEntryHandler = { it.getInt("key") to it.getString("value") },
)
Assertions.assertTrue(mapFromNbt.containsAll(maps.input))
val mutableMapNbt = maps.mutableObservable.saveToNbt { saveEntryHandler(it) }
Assertions.assertTrue(mutableMapNbt.getInt("size") == maps.mutableObservable.size)
val mutableMapFromNbt = mutableMapNbt.loadMutableObservableMapOf(
entryKey = "entry",
loadEntryHandler = { it.getInt("key") to it.getString("value") },
)
Assertions.assertTrue(mutableMapFromNbt.containsAll(maps.input))
val emptyMapNbt = maps.emptyObservable.saveToNbt { saveEntryHandler(it) }
Assertions.assertTrue(emptyMapNbt.getInt("size") == maps.emptyObservable.size)
val emptyMapFromNbt = emptyMapNbt.loadObservableMapOf(
entryKey = "entry",
loadEntryHandler = { it.getInt("key") to it.getString("value") },
)
Assertions.assertTrue(emptyMapFromNbt.isEmpty())
val emptyMutableMapNbt = maps.emptyMutableObservable.saveToNbt { saveEntryHandler(it) }
Assertions.assertTrue(emptyMutableMapNbt.getInt("size") == maps.emptyMutableObservable.size)
val emptyMutableMapFromNbt = emptyMutableMapNbt.loadMutableObservableMapOf(
entryKey = "entry",
loadEntryHandler = { it.getInt("key") to it.getString("value") },
)
Assertions.assertTrue(emptyMutableMapFromNbt.isEmpty())
}
}
@@ -0,0 +1,14 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.ObservableCollection
interface TestCollections<T, C : Collection<T>, M : MutableCollection<T>> {
val input: C
val mutableInput: M
val observable: ObservableCollection<T, C>
val mutableObservable: MutableObservableCollection<T, C>
val emptyObservable: ObservableCollection<T, C>
val emptyMutableObservable: MutableObservableCollection<T, C>
}
@@ -0,0 +1,21 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.list.MutableObservableList
import com.cobblemon.mod.common.api.reactive.collections.list.ObservableList
open class TestLists<T>(final override val input: List<T>) :
TestCollections<T, List<T>, MutableList<T>> {
override val mutableInput: MutableList<T> = input.toMutableList()
override val observable: ObservableList<T> = ObservableList(input)
override val mutableObservable: MutableObservableList<T> = MutableObservableList(input)
override val emptyObservable = ObservableList<T>(setOf())
override val emptyMutableObservable = MutableObservableList<T>()
companion object {
fun default() = testListsOf("zero", "one", "two", "three")
fun defaultList() = listOf("zero", "one", "two", "three")
}
}
fun <T> testListsOf(vararg elements: T) = TestLists(listOf(*elements))
@@ -0,0 +1,20 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.map.MutableObservableMap
import com.cobblemon.mod.common.api.reactive.collections.map.ObservableMap
class TestMaps<K, V>(val input: Map<K, V>) {
val mutableInput: MutableMap<K, V> = input.toMutableMap()
val observable: ObservableMap<K, V> = ObservableMap(input)
val mutableObservable: MutableObservableMap<K, V> = MutableObservableMap(input)
val emptyObservable = ObservableMap<K, V>(mapOf())
val emptyMutableObservable = MutableObservableMap<K, V>()
companion object {
fun default() = testMapsOf(0 to "zero", 1 to "one", 2 to "two", 3 to "three")
fun defaultMap() = mapOf(0 to "zero", 1 to "one", 2 to "two", 3 to "three")
}
}
fun <K, V> testMapsOf(vararg pairs: Pair<K, V>) = TestMaps(mapOf(*pairs))
@@ -0,0 +1,22 @@
package com.cobblemon.mod.common.api.reactive
import com.cobblemon.mod.common.api.reactive.collections.set.MutableObservableSet
import com.cobblemon.mod.common.api.reactive.collections.set.ObservableSet
class TestSets<T>(override val input: Set<T>) :
TestCollections<T, Set<T>, MutableSet<T>> {
override val mutableInput: MutableSet<T> = input.toMutableSet()
override val observable: ObservableSet<T> = ObservableSet(input)
override val mutableObservable: MutableObservableSet<T> = MutableObservableSet(input)
override val emptyObservable = ObservableSet<T>(setOf())
override val emptyMutableObservable = MutableObservableSet<T>()
companion object {
fun default() = testSetsOf("zero", "one", "two", "three")
fun defaultSet() = setOf("zero", "one", "two", "three")
}
}
fun <T> testSetsOf(vararg elements: T) = TestSets(setOf(*elements))
@@ -0,0 +1,18 @@
package com.cobblemon.mod.common.api.reactive
class TestSubLists<T>(
input: List<T>,
subIndices: Pair<Int, Int> = 1 to 3,
) : TestLists<T>(input) {
val inputSub = this.input.subList(subIndices.first, subIndices.second)
val mutableInputSub = mutableInput.subList(subIndices.first, subIndices.second)
val observableSub = observable.subList(subIndices.first, subIndices.second)
val mutableObservableSub = mutableObservable.subList(subIndices.first, subIndices.second)
companion object {
fun default() = testSubListsOf("zero", "one", "two", "three")
}
}
fun <T> testSubListsOf(vararg elements: T) = TestSubLists(listOf(*elements))