commonMain.co.touchlab.stately.collections.SharedLinkedList.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stately-collections-jvm Show documentation
Show all versions of stately-collections-jvm Show documentation
Multithreaded Kotlin Multiplatform Utilities
The newest version!
/*
* Copyright (C) 2018 Touchlab, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package co.touchlab.stately.collections
import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.concurrency.AtomicInt
import co.touchlab.stately.concurrency.AtomicReference
import co.touchlab.stately.concurrency.Lock
import co.touchlab.stately.concurrency.value
import co.touchlab.stately.freeze
/**
* Thread safe linked list implementation for Kotlin Multiplatform. This is intended for situations where there may
* be fairly regular updates and/or relatively large sizes. List iterators are mutable, and if the list changes
* while iterating, you won't have concurrent modification exceptions, but obviously the state of the list from
* when you started iterating is not guaranteed.
*/
class SharedLinkedList(objectPoolSize: Int = 0) : AbstractSharedLinkedList(objectPoolSize) {
internal val version = AtomicInt(0)
init {
freeze()
}
override fun updated() {
val newVal = version.incrementAndGet()
if (newVal == Int.MAX_VALUE) {
version.set(0)
}
}
override fun listIterator(): MutableListIterator {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun listIterator(index: Int): MutableListIterator {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun iterator(): MutableIterator = LLIterator(this, version.value)
fun nodeIterator(): MutableIterator> = NodeIterator(this, version.value)
class NodeIterator(private val ll: SharedLinkedList, private val version: Int) : MutableIterator> {
override fun remove() {
throw UnsupportedOperationException()
}
var currentNode = ll.head.value
private fun checkVersion() {
if (version != ll.version.get()) {
throw ConcurrentModificationException()
}
}
override fun hasNext(): Boolean {
checkVersion()
return currentNode != null
}
override fun next(): Node {
checkVersion()
val retval: Node = currentNode!!
currentNode = currentNode?.next?.value
return retval
}
}
class LLIterator(private val ll: SharedLinkedList, private val version: Int) : MutableIterator {
override fun remove() {
throw UnsupportedOperationException()
}
var currentNode = ll.head.value
private fun checkVersion() {
if (version != ll.version.get()) {
throw ConcurrentModificationException()
}
}
override fun hasNext(): Boolean {
checkVersion()
return currentNode?.nodeValue != null
}
override fun next(): T {
checkVersion()
val retval: T
retval = currentNode?.nodeValue!!
currentNode = currentNode?.next?.value
return retval
}
}
}
/**
* Fairly heavy handed implementation if list sizes are relatively significant. When stable copies are read,
* if the list has been updated, the list will lock while a copy is made. If the list is updated often,
* this is better than the simpler CopyOnWriteList, because EVERY update will lock and save, but something
* to keep in mind. If updating the list while you're iterating isn't a huge problem, use SharedLinkedList
*/
class CopyOnIterateLinkedList(objectPoolSize: Int = 0) : AbstractSharedLinkedList(objectPoolSize) {
private val updated: AtomicInt
private val lastList: AtomicReference>
init {
updated = AtomicInt(0)
lastList = AtomicReference(mutableListOf().freeze())
freeze()
}
override fun updated() {
updated.value = 1
}
override fun iterator(): MutableIterator = LocalIterator(withLock(false) { checkUpdate() }.iterator())
override fun listIterator(): MutableListIterator =
LocalListIterator(withLock(false) { checkUpdate() }.listIterator())
override fun listIterator(index: Int): MutableListIterator =
LocalListIterator(withLock(false) { checkUpdate() }.listIterator(index))
private fun checkUpdate(): MutableList {
if (updated.value != 0) {
val newList = ArrayList(sizeCount.value)
var node = head.value
var nodeCount = 0
while (node != null) {
newList.add(node.nodeValue)
node = node.next.value
nodeCount++
}
updated.value = 0
lastList.value = newList.freeze()
}
return lastList.value
}
}
private open class LocalIterator(private val delegate: MutableIterator) : MutableIterator {
override fun hasNext(): Boolean = delegate.hasNext()
override fun next(): T = delegate.next()
override fun remove() {
throw UnsupportedOperationException("Can't mutate list from iterator")
}
}
private class LocalListIterator(private val delegate: MutableListIterator) : MutableListIterator {
override fun hasNext(): Boolean = delegate.hasNext()
override fun next(): T = delegate.next()
override fun remove() {
throw UnsupportedOperationException("Can't mutate list from iterator")
}
override fun hasPrevious(): Boolean = delegate.hasPrevious()
override fun nextIndex(): Int = delegate.nextIndex()
override fun previous(): T = delegate.previous()
override fun previousIndex(): Int = delegate.previousIndex()
override fun add(element: T) {
throw UnsupportedOperationException()
}
override fun set(element: T) {
throw UnsupportedOperationException()
}
}
abstract class AbstractSharedLinkedList(objectPoolSize: Int) : MutableList {
override fun lastIndexOf(element: T): Int = withLock(false) {
var lastIndex = -1
var indexCounter = 0
var currentNode = head.value
while (currentNode != null) {
if (currentNode.nodeValue == element) {
lastIndex = indexCounter
}
indexCounter++
currentNode = currentNode.next.value
}
lastIndex
}
override fun retainAll(elements: Collection): Boolean = withLock(true) {
val result: ArrayList = ArrayList(sizeCount.value)
var currentNode = head.value
while (currentNode != null) {
val entry = currentNode.nodeValue
if (elements.contains(entry)) {
result.add(entry)
}
currentNode = currentNode.next.value
}
internalClear()
result.forEach {
internalAdd(makeNode(it))
}
true
}
override fun subList(fromIndex: Int, toIndex: Int): MutableList {
throw UnsupportedOperationException()
}
override val size: Int
get() = withLock(false) { sizeCount.value }
override fun add(element: T): Boolean = withLock(true) {
return internalAdd(makeNode(element))
}
override fun add(index: Int, element: T) {
withLock(true) {
if (index == sizeCount.value) {
internalAdd(makeNode(element))
} else {
internalNodeAt(index).internalAdd(element)
}
}
}
fun addNode(element: T): Node = withLock(true) {
val node = makeNode(element)
internalAdd(node)
return node
}
override fun addAll(index: Int, elements: Collection): Boolean = withLock(true) {
return when {
index == sizeCount.value -> internalAddAll(elements)
index > sizeCount.value -> throw IndexOutOfBoundsException("Index $index > ${sizeCount.value}")
else -> {
val node = internalNodeAt(index)
elements.forEach {
node.internalAdd(it)
}
true
}
}
}
override fun addAll(elements: Collection): Boolean = withLock(true) { internalAddAll(elements) }
override fun clear() = withLock(true) {
internalClear()
}
private fun internalClear() {
while (sizeCount.value != 0) {
internalRemoveAt(0)
}
head.value = null
tail.value = null
sizeCount.value = 0
nodePool.clear()
}
override fun contains(element: T): Boolean = withLock(false) { internalFindFirst(element) != null }
override fun containsAll(elements: Collection): Boolean =
withLock(false) { elements.all { internalFindFirst(it) != null } }
override fun get(index: Int) = withLock(false) { internalNodeAt(index).nodeValue }
override fun indexOf(element: T): Int = withLock(false) { internalFindFirstIndex(element).index }
override fun isEmpty() = withLock(false) { sizeCount.value == 0 }
override fun remove(value: T): Boolean = withLock(true) { internalRemove(value) }
override fun removeAll(elements: Collection): Boolean = withLock(true) { elements.all { internalRemove(it) } }
override fun removeAt(index: Int): T = withLock(true) {
internalRemoveAt(index)
}
override fun set(index: Int, element: T): T = withLock(true) {
val node = internalNodeAt(index)
val old = node.nodeValue
node.internalSet(element)
return old
}
class Node(val list: AbstractSharedLinkedList) {
private val atomicValue: AtomicReference = AtomicReference(null)
val nodeValue: T
get() = atomicValue.value!!
val prev = AtomicReference?>(null)
val next = AtomicReference?>(null)
private val removed = AtomicBoolean(false)
internal fun recycle() {
prev.value = null
next.value = null
removed.value = false
}
internal fun clearValue() {
atomicValue.value = null
}
fun set(t: T) = list.withLock(true) {
internalSet(t)
}
/**
* Set value
*
* Mutates list
*/
internal fun internalSet(t: T) {
checkNotRemoved()
atomicValue.value = t.freeze()
}
/**
* For iterators, make sure 'next' is always updated last so we don't need to lock nav.
*/
fun add(t: T): Boolean = list.withLock(true) {
internalAdd(t)
}
/**
* Adds new value before me.
*
* Mutates list
*/
internal fun internalAdd(t: T): Boolean {
checkNotRemoved()
val ins = list.makeNode(t)
ins.freeze()
val prevNode = prev.value
ins.prev.value = prevNode
ins.next.value = this
list.sizeCount.incrementAndGet()
// If replacing 0
if (prevNode == null) {
list.head.value = ins
} else {
prevNode.next.value = ins
}
prev.value = ins
return true
}
internal fun checkNotRemoved() {
if (removed.value) {
throw IllegalStateException("Node is removed $this")
}
}
/**
* Add same node to end of list.
*/
fun readd() = list.withLock(true) {
internalReadd()
}
/**
* Re-add myself back to list.
*
* Mutates list
*/
internal fun internalReadd() {
checkNotRemoved()
internalRemove(false)
prev.value = null
next.value = null
list.internalAdd(this)
}
/**
* For iterators, make sure 'next' is always updated last so we don't need to lock nav.
*/
fun remove(permanent: Boolean = true) = list.withLock(true) {
internalRemove(permanent)
}
val isRemoved: Boolean
get() = removed.value
/**
* Removes self from list.
*
* Mutates list
*/
internal fun internalRemove(permanent: Boolean = true) {
checkNotRemoved()
if (permanent) {
removed.value = true
clearValue()
list.nodePool.push(this)
}
val prevNode = prev.value
val nextNode = next.value
list.sizeCount.decrementAndGet()
if (nextNode == null) {
list.tail.value = prevNode
} else {
nextNode.prev.value = prevNode
}
if (prevNode == null) {
list.head.value = nextNode
} else {
prevNode.next.value = nextNode
}
}
}
private var lock: Lock = Lock()
internal val sizeCount = AtomicInt(0)
internal val head = AtomicReference?>(null)
internal val tail = AtomicReference?>(null)
internal val nodePool = ObjectPool(objectPoolSize, { Node(this) })
internal fun makeNode(t: T): Node {
val node = nodePool.pop()
node.recycle()
node.internalSet(t)
return node
}
internal fun internalAddAll(elements: Collection): Boolean {
elements.forEach {
internalAdd(makeNode(it))
}
return true
}
internal fun internalNodeAt(i: Int): Node {
if (i >= sizeCount.value) {
throw IllegalArgumentException("index $i ge ${sizeCount.value}")
}
var node = head.value
var nodeCount = 0
while (node != null) {
if (i == nodeCount++) {
return node
}
node = node.next.value
}
throw IllegalStateException("Bad math")
}
internal fun internalRemoveAt(i: Int): T {
val node = internalNodeAt(i)
val nodeValue = node.nodeValue
node.internalRemove()
return nodeValue
}
/**
* Add node to list.
*
* Mutates list
*
* @param node
* to add to list.
*/
internal fun internalAdd(node: Node): Boolean {
node.checkNotRemoved()
node.freeze()
if (sizeCount.value == 0) {
head.value = node
tail.value = node
} else {
val prev = tail.value
prev!!.next.value = node
node.prev.value = prev
tail.value = node
}
sizeCount.incrementAndGet()
return true
}
internal fun internalFindFirstIndex(value: T): NodeResult {
var node = head.value
var nodeCount = 0
while (node != null) {
if (node.nodeValue == value) {
return NodeResult(nodeCount, node)
}
node = node.next.value
nodeCount++
}
return NodeResult(-1, null)
}
internal fun internalFindFirst(value: T): Node? = internalFindFirstIndex(value).node
internal fun internalRemove(value: T): Boolean {
val node = internalFindFirst(value)
return if (node == null) {
false
} else {
node.internalRemove()
true
}
}
data class NodeResult(val index: Int, val node: Node?)
fun debugPrint(): String {
val sb = StringBuilder()
var node = head.value
while (node != null) {
sb.append("val: ${node.nodeValue}, pref: ${node.prev.value}, next: ${node.next.value}\n")
node = node.next.value
}
return sb.toString()
}
internal fun lock() {
lock.lock()
}
internal fun unlock() {
lock.unlock()
}
internal abstract fun updated()
internal inline fun withLock(updated: Boolean, proc: () -> T): T {
lock()
try {
return proc.invoke()
} finally {
if (updated) {
updated()
}
unlock()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy