client: Fix FlattenedList, add ReplayedList for testing ObservableList Changes

This commit is contained in:
Andras Slemmer 2016-09-16 10:57:46 +01:00
parent 93b8a507f4
commit 118d5c485e
4 changed files with 231 additions and 46 deletions

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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)
}
}