mirror of
https://github.com/corda/corda.git
synced 2025-04-07 19:34:41 +00:00
client: Fix FlattenedList, add ReplayedList for testing ObservableList Changes
This commit is contained in:
parent
93b8a507f4
commit
118d5c485e
@ -1,10 +1,12 @@
|
||||
package com.r3corda.client.fxutils
|
||||
|
||||
import javafx.beans.InvalidationListener
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.collections.transformation.TransformationList
|
||||
import org.eclipse.jetty.server.Authentication
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -20,21 +22,30 @@ class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Note also that we're wrapping each ObservableValue, this is required because we want to support reusing of
|
||||
* ObservableValues and we need each to have a different hash.
|
||||
*/
|
||||
val indexMap = HashMap<ObservableValue<out A>, Pair<Int, InvalidationListener>>()
|
||||
class WrappedObservableValue<A>(
|
||||
val observableValue: ObservableValue<A>
|
||||
)
|
||||
val indexMap = HashMap<WrappedObservableValue<out A>, Pair<Int, ChangeListener<A>>>()
|
||||
init {
|
||||
sourceList.forEachIndexed { index, observableValue ->
|
||||
indexMap[observableValue] = Pair(index, createListener(observableValue))
|
||||
val wrappedObservableValue = WrappedObservableValue(observableValue)
|
||||
indexMap[wrappedObservableValue] = Pair(index, createListener(wrappedObservableValue))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createListener(observableValue: ObservableValue<out A>): InvalidationListener {
|
||||
return InvalidationListener {
|
||||
val currentIndex = indexMap[observableValue]!!.first
|
||||
private fun createListener(wrapped: WrappedObservableValue<out A>): ChangeListener<A> {
|
||||
val listener = ChangeListener<A> { _observableValue, oldValue, newValue ->
|
||||
val currentIndex = indexMap[wrapped]!!.first
|
||||
beginChange()
|
||||
nextAdd(currentIndex, currentIndex + 1)
|
||||
nextReplace(currentIndex, currentIndex + 1, listOf(oldValue))
|
||||
endChange()
|
||||
}
|
||||
wrapped.observableValue.addListener(listener)
|
||||
return listener
|
||||
}
|
||||
|
||||
override fun sourceChanged(c: ListChangeListener.Change<out ObservableValue<out A>>) {
|
||||
@ -51,17 +62,17 @@ class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>
|
||||
} 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
|
||||
// TODO this assumes that if wasAdded() == true then we are adding elements to the getFrom() position
|
||||
val removeStart = c.from
|
||||
val removeRange = c.removed.size
|
||||
val removeEnd = c.from + removeRange
|
||||
val iterator = indexMap.iterator()
|
||||
for (entry in iterator) {
|
||||
val (observableValue, pair) = entry
|
||||
val (wrapped, pair) = entry
|
||||
val (index, listener) = pair
|
||||
if (index >= removeStart) {
|
||||
if (index < removeEnd) {
|
||||
observableValue.removeListener(listener)
|
||||
wrapped.observableValue.removeListener(listener)
|
||||
iterator.remove()
|
||||
} else {
|
||||
// Shift indices
|
||||
@ -87,7 +98,8 @@ class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>
|
||||
}
|
||||
}
|
||||
c.addedSubList.forEachIndexed { sublistIndex, observableValue ->
|
||||
indexMap[observableValue] = Pair(addStart + sublistIndex, createListener(observableValue))
|
||||
val wrapped = WrappedObservableValue(observableValue)
|
||||
indexMap[wrapped] = Pair(addStart + sublistIndex, createListener(wrapped))
|
||||
}
|
||||
nextAdd(addStart, addEnd)
|
||||
}
|
||||
|
@ -9,81 +9,105 @@ class FlattenedListTest {
|
||||
|
||||
var sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
|
||||
var flattenedList = FlattenedList(sourceList)
|
||||
var replayedList = ReplayedList(flattenedList)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
|
||||
flattenedList = FlattenedList(sourceList)
|
||||
replayedList = ReplayedList(flattenedList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addWorks() {
|
||||
require(flattenedList.size == 1)
|
||||
require(flattenedList[0] == 1234)
|
||||
require(replayedList.size == 1)
|
||||
require(replayedList[0] == 1234)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(12))
|
||||
require(flattenedList.size == 2)
|
||||
require(flattenedList[0] == 1234)
|
||||
require(flattenedList[1] == 12)
|
||||
require(replayedList.size == 2)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 12)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(34))
|
||||
require(flattenedList.size == 3)
|
||||
require(flattenedList[0] == 1234)
|
||||
require(flattenedList[1] == 12)
|
||||
require(flattenedList[2] == 34)
|
||||
require(replayedList.size == 3)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 12)
|
||||
require(replayedList[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)
|
||||
require(replayedList.size == 4)
|
||||
require(replayedList[0] == 56)
|
||||
require(replayedList[1] == 1234)
|
||||
require(replayedList[2] == 12)
|
||||
require(replayedList[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)
|
||||
require(replayedList.size == 6)
|
||||
require(replayedList[0] == 56)
|
||||
require(replayedList[1] == 1234)
|
||||
require(replayedList[2] == 78)
|
||||
require(replayedList[3] == 910)
|
||||
require(replayedList[4] == 12)
|
||||
require(replayedList[5] == 34)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeWorks() {
|
||||
val firstRemoved = sourceList.removeAt(0)
|
||||
require(firstRemoved.get() == 1234)
|
||||
require(flattenedList.size == 0)
|
||||
require(replayedList.size == 0)
|
||||
firstRemoved.set(123)
|
||||
|
||||
sourceList.add(SimpleObjectProperty(12))
|
||||
sourceList.add(SimpleObjectProperty(34))
|
||||
sourceList.add(SimpleObjectProperty(56))
|
||||
require(flattenedList.size == 3)
|
||||
require(replayedList.size == 3)
|
||||
val secondRemoved = sourceList.removeAt(1)
|
||||
require(secondRemoved.get() == 34)
|
||||
require(flattenedList.size == 2)
|
||||
require(flattenedList[0] == 12)
|
||||
require(flattenedList[1] == 56)
|
||||
require(replayedList.size == 2)
|
||||
require(replayedList[0] == 12)
|
||||
require(replayedList[1] == 56)
|
||||
secondRemoved.set(123)
|
||||
|
||||
sourceList.clear()
|
||||
require(flattenedList.size == 0)
|
||||
require(replayedList.size == 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updatingObservableWorks() {
|
||||
require(flattenedList[0] == 1234)
|
||||
require(replayedList[0] == 1234)
|
||||
sourceList[0].set(4321)
|
||||
require(flattenedList[0] == 4321)
|
||||
require(replayedList[0] == 4321)
|
||||
|
||||
sourceList.add(0, SimpleObjectProperty(12))
|
||||
sourceList[1].set(8765)
|
||||
require(flattenedList[0] == 12)
|
||||
require(flattenedList[1] == 8765)
|
||||
require(replayedList[0] == 12)
|
||||
require(replayedList[1] == 8765)
|
||||
|
||||
sourceList[0].set(34)
|
||||
require(flattenedList[0] == 34)
|
||||
require(flattenedList[1] == 8765)
|
||||
require(replayedList[0] == 34)
|
||||
require(replayedList[1] == 8765)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun reusingObservableWorks() {
|
||||
val observable = SimpleObjectProperty(12)
|
||||
sourceList.add(observable)
|
||||
sourceList.add(observable)
|
||||
require(replayedList.size == 3)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 12)
|
||||
require(replayedList[2] == 12)
|
||||
|
||||
observable.set(34)
|
||||
require(replayedList.size == 3)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 34)
|
||||
require(replayedList[2] == 34)
|
||||
|
||||
sourceList.removeAt(1)
|
||||
require(replayedList.size == 2)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 34)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.r3corda.client.fxutils
|
||||
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.collections.transformation.TransformationList
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This list type just replays changes propagated from the underlying source list. Used for testing changes.
|
||||
*/
|
||||
class ReplayedList<A>(sourceList: ObservableList<A>) : TransformationList<A, A>(sourceList) {
|
||||
|
||||
val replayedList = ArrayList<A>(sourceList)
|
||||
|
||||
override val size: Int get() = replayedList.size
|
||||
|
||||
override fun sourceChanged(c: ListChangeListener.Change<out A>) {
|
||||
|
||||
beginChange()
|
||||
while (c.next()) {
|
||||
if (c.wasPermutated()) {
|
||||
val from = c.from
|
||||
val to = c.to
|
||||
val permutation = IntArray(to, { c.getPermutation(it) })
|
||||
val permutedSubList = ArrayList<A>(to - from)
|
||||
for (i in 0 .. (to - from - 1)) {
|
||||
permutedSubList.add(replayedList[permutation[from + i]])
|
||||
}
|
||||
permutedSubList.forEachIndexed { i, element ->
|
||||
replayedList[from + i] = element
|
||||
}
|
||||
nextPermutation(from, to, permutation)
|
||||
} else if (c.wasUpdated()) {
|
||||
for (i in c.from .. c.to - 1) {
|
||||
replayedList[i] = c.list[i]
|
||||
nextUpdate(i)
|
||||
}
|
||||
} else {
|
||||
if (c.wasRemoved()) {
|
||||
// TODO this assumes that if wasAdded() == true then we are adding elements to the getFrom() position
|
||||
val removePosition = c.from
|
||||
for (i in 0 .. c.removedSize - 1) {
|
||||
replayedList.removeAt(removePosition)
|
||||
}
|
||||
nextRemove(c.from, c.removed)
|
||||
}
|
||||
if (c.wasAdded()) {
|
||||
val addStart = c.from
|
||||
val addEnd = c.to
|
||||
for (i in addStart .. addEnd - 1) {
|
||||
replayedList.add(i, c.list[i])
|
||||
}
|
||||
nextAdd(addStart, addEnd)
|
||||
}
|
||||
}
|
||||
}
|
||||
endChange()
|
||||
}
|
||||
|
||||
override fun getSourceIndex(index: Int) = index
|
||||
|
||||
override fun get(index: Int) = replayedList[index]
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.r3corda.client.fxutils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ReplayedListTest {
|
||||
|
||||
var sourceList = FXCollections.observableArrayList(1234)
|
||||
var replayedList = ReplayedList(sourceList)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sourceList = FXCollections.observableArrayList(1234)
|
||||
replayedList = ReplayedList(sourceList)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addWorks() {
|
||||
require(replayedList.size == 1)
|
||||
require(replayedList[0] == 1234)
|
||||
|
||||
sourceList.add(12)
|
||||
require(replayedList.size == 2)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 12)
|
||||
|
||||
sourceList.add(34)
|
||||
require(replayedList.size == 3)
|
||||
require(replayedList[0] == 1234)
|
||||
require(replayedList[1] == 12)
|
||||
require(replayedList[2] == 34)
|
||||
|
||||
sourceList.add(0, 56)
|
||||
require(replayedList.size == 4)
|
||||
require(replayedList[0] == 56)
|
||||
require(replayedList[1] == 1234)
|
||||
require(replayedList[2] == 12)
|
||||
require(replayedList[3] == 34)
|
||||
|
||||
sourceList.addAll(2, listOf(78, 910))
|
||||
require(replayedList.size == 6)
|
||||
require(replayedList[0] == 56)
|
||||
require(replayedList[1] == 1234)
|
||||
require(replayedList[2] == 78)
|
||||
require(replayedList[3] == 910)
|
||||
require(replayedList[4] == 12)
|
||||
require(replayedList[5] == 34)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeWorks() {
|
||||
val firstRemoved = sourceList.removeAt(0)
|
||||
require(firstRemoved == 1234)
|
||||
require(replayedList.size == 0)
|
||||
|
||||
sourceList.add(12)
|
||||
sourceList.add(34)
|
||||
sourceList.add(56)
|
||||
require(replayedList.size == 3)
|
||||
val secondRemoved = sourceList.removeAt(1)
|
||||
require(secondRemoved == 34)
|
||||
require(replayedList.size == 2)
|
||||
require(replayedList[0] == 12)
|
||||
require(replayedList[1] == 56)
|
||||
|
||||
sourceList.clear()
|
||||
require(replayedList.size == 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateWorks() {
|
||||
require(replayedList[0] == 1234)
|
||||
sourceList[0] = 4321
|
||||
require(replayedList[0] == 4321)
|
||||
|
||||
sourceList.add(0, 12)
|
||||
sourceList[1] = 8765
|
||||
require(replayedList[0] == 12)
|
||||
require(replayedList[1] == 8765)
|
||||
|
||||
sourceList[0] = 34
|
||||
require(replayedList[0] == 34)
|
||||
require(replayedList[1] == 8765)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user