Coverage Summary for Class: ObservableList (kweb.state)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
ObservableList |
72.7%
(24/33)
|
46.2%
(24/52)
|
64.7%
(77/119)
|
70.1%
(444/633)
|
ObservableList$listIterator$1 |
100%
(4/4)
|
|
100%
(4/4)
|
100%
(27/27)
|
ObservableList$Modification |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(2/2)
|
ObservableList$Modification$Change |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(13/13)
|
ObservableList$Modification$Deletion |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(8/8)
|
ObservableList$Modification$Insertion |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(13/13)
|
ObservableList$Modification$Move |
100%
(1/1)
|
|
100%
(1/1)
|
100%
(13/13)
|
Total |
78.6%
(33/42)
|
46.2%
(24/52)
|
67.2%
(86/128)
|
73.3%
(520/709)
|
package kweb.state
import java.util.concurrent.ConcurrentHashMap
/**
* A list of items that can be observed for changes like [add], [remove], [set], etc. Typically passed to
* [renderEach].
*/
class ObservableList<ITEM : Any>(
initialItems: List<ITEM> = emptyList(),
) : MutableList<ITEM>, AutoCloseable {
private val items = ArrayList(initialItems)
fun getItems(): ArrayList<ITEM> {
synchronized(items) {
return ArrayList(items)
}
}
private val listeners = ConcurrentHashMap<Long, (Iterable<Modification<ITEM>>) -> Unit>()
private val closeListeners = HashMap<Long, () -> Unit>()
private fun insert(position: Int, item: ITEM) = applyModifications(listOf(Modification.Insertion(position, item)))
private fun change(position: Int, newItem: ITEM) =
applyModifications(listOf(Modification.Change(position, newItem)))
private fun delete(position: Int) = applyModifications(listOf(Modification.Deletion(position)))
override val size: Int get() = items.size
fun move(oldPosition: Int, newPosition: Int) =
applyModifications(listOf(Modification.Move(oldPosition, newPosition)))
fun applyModifications(modifications: Iterable<Modification<ITEM>>) {
if (closed) {
throw IllegalStateException("Cannot modify closed ObservableList")
}
synchronized(items) {
for (change in modifications) {
when (change) {
is Modification.Change -> {
items[change.position] = change.newItem
}
is Modification.Deletion -> {
items.removeAt(change.position)
}
is Modification.Insertion -> {
items.add(change.position, change.item)
}
is Modification.Move -> {
if (change.oldPosition == change.newPosition) {
continue
}
val item = items[change.oldPosition]
if (change.oldPosition >= change.newPosition) {
items.add(change.newPosition, item)
items.removeAt(change.oldPosition + 1)
println("Items size: ${items.size}")
} else { //change.newPosition > change.oldPosition
items.removeAt(change.oldPosition)
items.add(change.newPosition, item)
println(
"Items size: ${items.size}" +
"\nItems Contents: $items"
)
}
}
}
}
}
listeners.values.forEach { it(modifications) }
}
sealed class Modification<ITEM> {
class Insertion<ITEM>(val position: Int, val item: ITEM) : Modification<ITEM>()
class Change<ITEM>(val position: Int, val newItem: ITEM) : Modification<ITEM>()
class Move<ITEM>(val oldPosition: Int, val newPosition: Int) : Modification<ITEM>()
class Deletion<ITEM>(val position: Int) : Modification<ITEM>()
}
fun addListener(changes: (Iterable<Modification<ITEM>>) -> Unit): Long {
if (closed) {
throw IllegalStateException("Cannot add listener to closed ObservableList")
}
val handle = kweb.util.random.nextLong()
listeners[handle] = changes
return handle
}
fun removeListener(handle: Long) {
if (closed) {
throw IllegalStateException("Cannot remove listener from closed ObservableList")
}
listeners.remove(handle)
}
override fun contains(element: ITEM): Boolean {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items.contains(element)
}
override fun containsAll(elements: Collection<ITEM>): Boolean {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items.containsAll(elements)
}
override fun get(index: Int): ITEM {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items[index]
}
override fun indexOf(element: ITEM): Int {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items.indexOf(element)
}
override fun isEmpty(): Boolean {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items.isEmpty()
}
/**
* Returns an iterator over the elements in this list (in proper sequence).
*
* **IMPORTANT:** The returned iterator will throw an UnsupportedOperationException
* if you attempt to call [MutableIterator.remove].
*/
override fun iterator(): MutableIterator<ITEM> {
return listIterator()
}
override fun lastIndexOf(element: ITEM): Int {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
return items.lastIndexOf(element)
}
override fun add(element: ITEM): Boolean {
insert(items.size, element)
return true
}
override fun add(index: Int, element: ITEM) {
insert(index, element)
}
override fun addAll(index: Int, elements: Collection<ITEM>): Boolean {
for ((i, element) in elements.withIndex()) {
insert(index + i, element)
}
return true
}
override fun addAll(elements: Collection<ITEM>): Boolean {
elements.forEach { element ->
insert(items.size, element)
}
return true
}
override fun clear() {
while (items.size > 0) {
delete(0)
}
}
/**
* Returns a list iterator over the elements in this list (in proper sequence).
*
* **IMPORTANT:** The returned iterator will throw an UnsupportedOperationException
* if you attempt to call [MutableListIterator.add], [MutableListIterator.set], or
* [MutableListIterator.remove]. Implementing these in a threadsafe manner is
* difficult, and they are not needed for the intended use of this class.
*/
override fun listIterator(): MutableListIterator<ITEM> = listIterator(0)
/**
* Returns a list iterator over the elements in this list (in proper sequence),
* starting at the specified position.
*
* **IMPORTANT:** The returned iterator will throw an UnsupportedOperationException
* if you attempt to call [MutableListIterator.add], [MutableListIterator.set], or
* [MutableListIterator.remove]. Implementing these in a threadsafe manner is
* difficult, and they are not needed for the intended use of this class.
*/
override fun listIterator(index: Int): MutableListIterator<ITEM> {
if (closed) {
throw IllegalStateException("Cannot read closed ObservableList")
}
// Clone the list so that we can iterate over it without worrying about
// concurrent modification. Disallow modifications to the list because
// allowing this in a threadsafe way would be non-trivial for questionable
// benefit.
return object : MutableListIterator<ITEM> by ArrayList(items).listIterator(index) {
override fun set(element: ITEM) {
throw UnsupportedOperationException("Cannot set element via iterator in ObservableList")
}
override fun add(element: ITEM) {
throw UnsupportedOperationException("Cannot add element via iterator to ObservableList")
}
override fun remove() {
throw UnsupportedOperationException("Cannot remove element via iterator from ObservableList")
}
}
}
override fun remove(element: ITEM): Boolean {
val position = items.indexOf(element)
return if (position == -1) {
false
} else {
delete(position)
true
}
}
override fun removeAll(elements: Collection<ITEM>): Boolean {
var removed = false
for (element in elements) {
if (remove(element)) {
removed = true
}
}
return removed
}
override fun removeAt(index: Int): ITEM {
if (closed) {
throw IllegalStateException("Cannot modify closed ObservableList")
}
val itemToRemove = items[index]
delete(index)
return itemToRemove
}
override fun retainAll(elements: Collection<ITEM>): Boolean {
if (closed) {
throw IllegalStateException("Cannot modify closed ObservableList")
}
items.clear()
addAll(elements)
return true
}
override operator fun set(index: Int, element: ITEM): ITEM {
if (closed) {
throw IllegalStateException("Cannot set element of closed ObservableList")
}
val previousElement = items[index]
change(index, element)
return previousElement
}
//TODO: Documentation will need a note explaining that any modifications to the returned sublist will not propagate in the DOM
override fun subList(fromIndex: Int, toIndex: Int): MutableList<ITEM> {
return items.subList(fromIndex, toIndex)
}
private @Volatile
var closed = false
fun addCloseListener(listener: () -> Unit): Long {
val handle = kweb.util.random.nextLong()
synchronized(closeListeners) {
closeListeners[handle] = listener
}
return handle
}
fun removeCloseListener(handle: Long) {
synchronized(closeListeners) {
closeListeners.remove(handle)
}
}
override fun close() {
if (!closed) {
closed = true
synchronized(closeListeners) {
closeListeners.values.forEach { it() }
}
}
}
}