mirror of
https://github.com/corda/corda.git
synced 2024-12-25 23:51:10 +00:00
client: Add FlattenedList
This commit is contained in:
parent
53be2e0c99
commit
f1f8b3180c
@ -0,0 +1,105 @@
|
||||
package com.r3corda.client.fxutils
|
||||
|
||||
import javafx.beans.InvalidationListener
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.collections.transformation.TransformationList
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* [FlattenedList] flattens the passed in list of [ObservableValue]s so that changes in individual updates to the values
|
||||
* are reflected in the exposed list as expected.
|
||||
*/
|
||||
class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>>) : TransformationList<A, ObservableValue<out A>>(sourceList) {
|
||||
|
||||
/**
|
||||
* We maintain an ObservableValue->index map. This is needed because we need the ObservableValue's index in order to
|
||||
* propagate a change and if the listener closure captures the index at the time of the call to
|
||||
* [ObservableValue.addListener] it will become incorrect if the indices shift around later.
|
||||
*
|
||||
* Note that because of the bookkeeping required for this map, any remove operation and any add operation that
|
||||
* inserts to the middle of the list will be O(N) as we need to scan the map and shift indices accordingly.
|
||||
*/
|
||||
val indexMap = HashMap<ObservableValue<out A>, Pair<Int, InvalidationListener>>()
|
||||
init {
|
||||
sourceList.forEachIndexed { index, observableValue ->
|
||||
indexMap[observableValue] = Pair(index, createListener(observableValue))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createListener(observableValue: ObservableValue<out A>): InvalidationListener {
|
||||
return InvalidationListener {
|
||||
val currentIndex = indexMap[observableValue]!!.first
|
||||
beginChange()
|
||||
nextAdd(currentIndex, currentIndex + 1)
|
||||
endChange()
|
||||
}
|
||||
}
|
||||
|
||||
override fun sourceChanged(c: ListChangeListener.Change<out ObservableValue<out A>>) {
|
||||
beginChange()
|
||||
while (c.next()) {
|
||||
if (c.wasPermutated()) {
|
||||
val from = c.from
|
||||
val to = c.to
|
||||
val permutation = IntArray(to, { c.getPermutation(it) })
|
||||
indexMap.replaceAll { _observableValue, pair -> Pair(permutation[pair.first], pair.second) }
|
||||
nextPermutation(from, to, permutation)
|
||||
} else if (c.wasUpdated()) {
|
||||
throw UnsupportedOperationException("FlattenedList doesn't support Update changes")
|
||||
} else {
|
||||
val removed = c.removed
|
||||
if (removed.size != 0) {
|
||||
val removeStart = indexMap[removed.first()]!!.first
|
||||
val removeEnd = indexMap[removed.last()]!!.first + 1
|
||||
require(removeStart < removeEnd)
|
||||
val removeRange = removeEnd - removeStart
|
||||
val iterator = indexMap.iterator()
|
||||
for (entry in iterator) {
|
||||
val (observableValue, pair) = entry
|
||||
val (index, listener) = pair
|
||||
if (index >= removeStart) {
|
||||
if (index < removeEnd) {
|
||||
observableValue.removeListener(listener)
|
||||
iterator.remove()
|
||||
} else {
|
||||
// Shift indices
|
||||
entry.setValue(Pair(index - removeRange, listener))
|
||||
}
|
||||
}
|
||||
}
|
||||
nextRemove(removeStart, removed.map { it.value })
|
||||
}
|
||||
if (c.wasAdded()) {
|
||||
val addStart = c.from
|
||||
val addEnd = c.to
|
||||
val addRange = addEnd - addStart
|
||||
// If it was a push to the end we don't need to shift indices
|
||||
if (addStart != indexMap.size) {
|
||||
val iterator = indexMap.iterator()
|
||||
for (entry in iterator) {
|
||||
val (index, listener) = entry.value
|
||||
if (index >= addStart) {
|
||||
// Shift indices
|
||||
entry.setValue(Pair(index + addRange, listener))
|
||||
}
|
||||
}
|
||||
}
|
||||
c.addedSubList.forEachIndexed { sublistIndex, observableValue ->
|
||||
indexMap[observableValue] = Pair(addStart + sublistIndex, createListener(observableValue))
|
||||
}
|
||||
nextAdd(addStart, addEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
endChange()
|
||||
require(sourceList.size == indexMap.size)
|
||||
}
|
||||
|
||||
override fun get(index: Int) = sourceList.get(index).value
|
||||
|
||||
override fun getSourceIndex(index: Int) = index
|
||||
|
||||
override val size: Int get() = sourceList.size
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.r3corda.client.fxutils
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.collections.FXCollections
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class FlattenedListTest {
|
||||
|
||||
var sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
|
||||
var flattenedList = FlattenedList(sourceList)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
|
||||
flattenedList = FlattenedList(sourceList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addWorks() {
|
||||
require(flattenedList.size == 1)
|
||||
require(flattenedList[0] == 1234)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(12))
|
||||
require(flattenedList.size == 2)
|
||||
require(flattenedList[0] == 1234)
|
||||
require(flattenedList[1] == 12)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(34))
|
||||
require(flattenedList.size == 3)
|
||||
require(flattenedList[0] == 1234)
|
||||
require(flattenedList[1] == 12)
|
||||
require(flattenedList[2] == 34)
|
||||
|
||||
sourceList.add(0, SimpleObjectProperty(56))
|
||||
require(flattenedList.size == 4)
|
||||
require(flattenedList[0] == 56)
|
||||
require(flattenedList[1] == 1234)
|
||||
require(flattenedList[2] == 12)
|
||||
require(flattenedList[3] == 34)
|
||||
|
||||
sourceList.addAll(2, listOf(SimpleObjectProperty(78), SimpleObjectProperty(910)))
|
||||
require(flattenedList.size == 6)
|
||||
require(flattenedList[0] == 56)
|
||||
require(flattenedList[1] == 1234)
|
||||
require(flattenedList[2] == 78)
|
||||
require(flattenedList[3] == 910)
|
||||
require(flattenedList[4] == 12)
|
||||
require(flattenedList[5] == 34)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeWorks() {
|
||||
val firstRemoved = sourceList.removeAt(0)
|
||||
require(firstRemoved.get() == 1234)
|
||||
require(flattenedList.size == 0)
|
||||
firstRemoved.set(123)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(12))
|
||||
sourceList.add(SimpleObjectProperty(34))
|
||||
sourceList.add(SimpleObjectProperty(56))
|
||||
require(flattenedList.size == 3)
|
||||
val secondRemoved = sourceList.removeAt(1)
|
||||
require(secondRemoved.get() == 34)
|
||||
require(flattenedList.size == 2)
|
||||
require(flattenedList[0] == 12)
|
||||
require(flattenedList[1] == 56)
|
||||
secondRemoved.set(123)
|
||||
|
||||
sourceList.clear()
|
||||
require(flattenedList.size == 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updatingObservableWorks() {
|
||||
require(flattenedList[0] == 1234)
|
||||
sourceList[0].set(4321)
|
||||
require(flattenedList[0] == 4321)
|
||||
|
||||
sourceList.add(0, SimpleObjectProperty(12))
|
||||
sourceList[1].set(8765)
|
||||
require(flattenedList[0] == 12)
|
||||
require(flattenedList[1] == 8765)
|
||||
|
||||
sourceList[0].set(34)
|
||||
require(flattenedList[0] == 34)
|
||||
require(flattenedList[1] == 8765)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user