mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +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