jvmMain.kotlin.collections.builders.MapBuilder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-stdlib Show documentation
Show all versions of kotlin-stdlib Show documentation
Kotlin Standard Library for JVM
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.collections.builders
import java.io.Externalizable
import java.io.InvalidObjectException
import java.io.NotSerializableException
internal class MapBuilder private constructor(
// keys in insert order
private var keysArray: Array,
// values in insert order, allocated only when actually used, always null in pure HashSet
private var valuesArray: Array?,
// hash of a key by its index, -1 if a key at that index was removed
private var presenceArray: IntArray,
// (index + 1) of a key by its hash, 0 if there is no key with that hash, -1 if collision chain continues to the hash-1
private var hashArray: IntArray,
// max length of a collision chain
private var maxProbeDistance: Int,
// index of the next key to be inserted
private var length: Int
) : MutableMap, Serializable {
private var hashShift: Int = computeShift(hashSize)
/**
* The number of times this map is structurally modified.
*
* A modification is considered to be structural if it changes the map size,
* or otherwise changes it in a way that iterations in progress may return incorrect results.
*
* This value can be used by iterators of the [keys], [values] and [entries] views
* to provide fail-fast behavior when a concurrent modification is detected during iteration.
* [ConcurrentModificationException] will be thrown in this case.
*/
private var modCount: Int = 0
override var size: Int = 0
private set
private var keysView: MapBuilderKeys? = null
private var valuesView: MapBuilderValues? = null
private var entriesView: MapBuilderEntries? = null
internal var isReadOnly: Boolean = false
private set
// ---------------------------- functions ----------------------------
constructor() : this(INITIAL_CAPACITY)
constructor(initialCapacity: Int) : this(
arrayOfUninitializedElements(initialCapacity),
null,
IntArray(initialCapacity),
IntArray(computeHashSize(initialCapacity)),
INITIAL_MAX_PROBE_DISTANCE,
0)
fun build(): Map {
checkIsMutable()
isReadOnly = true
@Suppress("UNCHECKED_CAST")
return if (size > 0) this else (Empty as Map)
}
private fun writeReplace(): Any =
if (isReadOnly)
SerializedMap(this)
else
throw NotSerializableException("The map cannot be serialized while it is being built.")
override fun isEmpty(): Boolean = size == 0
override fun containsKey(key: K): Boolean = findKey(key) >= 0
override fun containsValue(value: V): Boolean = findValue(value) >= 0
override operator fun get(key: K): V? {
val index = findKey(key)
if (index < 0) return null
return valuesArray!![index]
}
override fun put(key: K, value: V): V? {
checkIsMutable()
val index = addKey(key)
val valuesArray = allocateValuesArray()
if (index < 0) {
val oldValue = valuesArray[-index - 1]
valuesArray[-index - 1] = value
return oldValue
} else {
valuesArray[index] = value
return null
}
}
override fun putAll(from: Map) {
checkIsMutable()
putAllEntries(from.entries)
}
override fun remove(key: K): V? {
checkIsMutable()
val index = findKey(key)
if (index < 0) return null
val oldValue = valuesArray!![index]
removeEntryAt(index)
return oldValue
}
override fun clear() {
checkIsMutable()
// O(length) implementation for hashArray cleanup
for (i in 0..length - 1) {
val hash = presenceArray[i]
if (hash >= 0) {
hashArray[hash] = 0
presenceArray[i] = TOMBSTONE
}
}
keysArray.resetRange(0, length)
valuesArray?.resetRange(0, length)
size = 0
length = 0
registerModification()
}
override val keys: MutableSet get() {
val cur = keysView
return if (cur == null) {
val new = MapBuilderKeys(this)
keysView = new
new
} else cur
}
override val values: MutableCollection get() {
val cur = valuesView
return if (cur == null) {
val new = MapBuilderValues(this)
valuesView = new
new
} else cur
}
override val entries: MutableSet> get() {
val cur = entriesView
return if (cur == null) {
val new = MapBuilderEntries(this)
entriesView = new
return new
} else cur
}
override fun equals(other: Any?): Boolean {
return other === this ||
(other is Map<*, *>) &&
contentEquals(other)
}
override fun hashCode(): Int {
var result = 0
val it = entriesIterator()
while (it.hasNext()) {
result += it.nextHashCode()
}
return result
}
override fun toString(): String {
val sb = StringBuilder(2 + size * 3)
sb.append("{")
var i = 0
val it = entriesIterator()
while (it.hasNext()) {
if (i > 0) sb.append(", ")
it.nextAppendString(sb)
i++
}
sb.append("}")
return sb.toString()
}
// ---------------------------- private ----------------------------
// Declared internal for testing
internal val capacity: Int get() = keysArray.size
private val hashSize: Int get() = hashArray.size
private fun registerModification() {
modCount += 1
}
internal fun checkIsMutable() {
if (isReadOnly) throw UnsupportedOperationException()
}
private fun ensureExtraCapacity(n: Int) {
if (shouldCompact(extraCapacity = n)) {
compact(updateHashArray = true)
} else {
ensureCapacity(length + n)
}
}
private fun shouldCompact(extraCapacity: Int): Boolean {
val spareCapacity = this.capacity - length
val gaps = length - size
return spareCapacity < extraCapacity // there is no room for extraCapacity entries
&& gaps + spareCapacity >= extraCapacity // removing gaps prevents capacity expansion
&& gaps >= this.capacity / 4 // at least 25% of current capacity is occupied by gaps
}
private fun ensureCapacity(minCapacity: Int) {
if (minCapacity < 0) throw OutOfMemoryError() // overflow
if (minCapacity > this.capacity) {
val newSize = AbstractList.newCapacity(this.capacity, minCapacity)
keysArray = keysArray.copyOfUninitializedElements(newSize)
valuesArray = valuesArray?.copyOfUninitializedElements(newSize)
presenceArray = presenceArray.copyOf(newSize)
val newHashSize = computeHashSize(newSize)
if (newHashSize > hashSize) rehash(newHashSize)
}
}
private fun allocateValuesArray(): Array {
val curValuesArray = valuesArray
if (curValuesArray != null) return curValuesArray
val newValuesArray = arrayOfUninitializedElements(capacity)
valuesArray = newValuesArray
return newValuesArray
}
private fun hash(key: K) = (key.hashCode() * MAGIC) ushr hashShift
private fun compact(updateHashArray: Boolean) {
var i = 0
var j = 0
val valuesArray = valuesArray
while (i < length) {
val hash = presenceArray[i]
if (hash >= 0) {
keysArray[j] = keysArray[i]
if (valuesArray != null) valuesArray[j] = valuesArray[i]
if (updateHashArray) {
presenceArray[j] = hash
hashArray[hash] = j + 1
}
j++
}
i++
}
keysArray.resetRange(j, length)
valuesArray?.resetRange(j, length)
length = j
//check(length == size) { "Internal invariant violated during compact: length=$length != size=$size" }
}
private fun rehash(newHashSize: Int) {
// require(newHashSize > hashSize) { "Rehash can only be executed with a grown hash array" }
registerModification()
if (length > size) compact(updateHashArray = false)
hashArray = IntArray(newHashSize)
hashShift = computeShift(newHashSize)
var i = 0
while (i < length) {
if (!putRehash(i++)) {
throw IllegalStateException(
"This cannot happen with fixed magic multiplier and grow-only hash array. Have object hashCodes changed?"
)
}
}
}
private fun putRehash(i: Int): Boolean {
var hash = hash(keysArray[i])
var probesLeft = maxProbeDistance
while (true) {
val index = hashArray[hash]
if (index == 0) {
hashArray[hash] = i + 1
presenceArray[i] = hash
return true
}
if (--probesLeft < 0) return false
if (hash-- == 0) hash = hashSize - 1
}
}
private fun findKey(key: K): Int {
var hash = hash(key)
var probesLeft = maxProbeDistance
while (true) {
val index = hashArray[hash]
if (index == 0) return TOMBSTONE
if (index > 0 && keysArray[index - 1] == key) return index - 1
if (--probesLeft < 0) return TOMBSTONE
if (hash-- == 0) hash = hashSize - 1
}
}
private fun findValue(value: V): Int {
var i = length
while (--i >= 0) {
if (presenceArray[i] >= 0 && valuesArray!![i] == value)
return i
}
return TOMBSTONE
}
internal fun addKey(key: K): Int {
checkIsMutable()
retry@ while (true) {
var hash = hash(key)
// put is allowed to grow maxProbeDistance with some limits (resize hash on reaching limits)
val tentativeMaxProbeDistance = (maxProbeDistance * 2).coerceAtMost(hashSize / 2)
var probeDistance = 0
while (true) {
val index = hashArray[hash]
if (index <= 0) { // claim or reuse hash slot
if (length >= capacity) {
ensureExtraCapacity(1)
continue@retry
}
val putIndex = length++
keysArray[putIndex] = key
presenceArray[putIndex] = hash
hashArray[hash] = putIndex + 1
size++
registerModification()
if (probeDistance > maxProbeDistance) maxProbeDistance = probeDistance
return putIndex
}
if (keysArray[index - 1] == key) {
return -index
}
if (++probeDistance > tentativeMaxProbeDistance) {
rehash(hashSize * 2) // cannot find room even with extra "tentativeMaxProbeDistance" -- grow hash
continue@retry
}
if (hash-- == 0) hash = hashSize - 1
}
}
}
internal fun removeKey(key: K): Boolean {
checkIsMutable()
val index = findKey(key)
if (index < 0) return false
removeEntryAt(index)
return true
}
private fun removeEntryAt(index: Int) {
keysArray.resetAt(index)
valuesArray?.resetAt(index)
removeHashAt(presenceArray[index])
presenceArray[index] = TOMBSTONE
size--
registerModification()
}
private fun removeHashAt(removedHash: Int) {
var hash = removedHash
var hole = removedHash // will try to patch the hole in hash array
var probeDistance = 0
var patchAttemptsLeft = (maxProbeDistance * 2).coerceAtMost(hashSize / 2) // don't spend too much effort
while (true) {
if (hash-- == 0) hash = hashSize - 1
if (++probeDistance > maxProbeDistance) {
// too far away -- can release the hole, bad case will not happen
hashArray[hole] = 0
return
}
val index = hashArray[hash]
if (index == 0) {
// end of chain -- can release the hole, bad case will not happen
hashArray[hole] = 0
return
}
if (index < 0) {
// TOMBSTONE FOUND
// - <--- [ TS ] ------ [hole] ---> +
// \------------/
// probeDistance
// move tombstone into the hole
hashArray[hole] = TOMBSTONE
hole = hash
probeDistance = 0
} else {
val otherHash = hash(keysArray[index - 1])
// Bad case:
// - <--- [hash] ------ [hole] ------ [otherHash] ---> +
// \------------/
// probeDistance
if ((otherHash - hash) and (hashSize - 1) >= probeDistance) {
// move otherHash into the hole, move the hole
hashArray[hole] = index
presenceArray[index - 1] = hole
hole = hash
probeDistance = 0
}
}
// check how long we're patching holes
if (--patchAttemptsLeft < 0) {
// just place tombstone into the hole
hashArray[hole] = TOMBSTONE
return
}
}
}
internal fun containsEntry(entry: Map.Entry): Boolean {
val index = findKey(entry.key)
if (index < 0) return false
return valuesArray!![index] == entry.value
}
private fun contentEquals(other: Map<*, *>): Boolean = size == other.size && containsAllEntries(other.entries)
internal fun containsAllEntries(m: Collection<*>): Boolean {
val it = m.iterator()
while (it.hasNext()) {
val entry = it.next()
try {
@Suppress("UNCHECKED_CAST") // todo: get rid of unchecked cast here somehow
if (entry == null || !containsEntry(entry as Map.Entry))
return false
} catch (e: ClassCastException) {
return false
}
}
return true
}
private fun putEntry(entry: Map.Entry): Boolean {
val index = addKey(entry.key)
val valuesArray = allocateValuesArray()
if (index >= 0) {
valuesArray[index] = entry.value
return true
}
val oldValue = valuesArray[-index - 1]
if (entry.value != oldValue) {
valuesArray[-index - 1] = entry.value
return true
}
return false
}
private fun putAllEntries(from: Collection>): Boolean {
if (from.isEmpty()) return false
ensureExtraCapacity(from.size)
val it = from.iterator()
var updated = false
while (it.hasNext()) {
if (putEntry(it.next()))
updated = true
}
return updated
}
internal fun removeEntry(entry: Map.Entry): Boolean {
checkIsMutable()
val index = findKey(entry.key)
if (index < 0) return false
if (valuesArray!![index] != entry.value) return false
removeEntryAt(index)
return true
}
internal fun removeValue(element: V): Boolean {
checkIsMutable()
val index = findValue(element)
if (index < 0) return false
removeEntryAt(index)
return true
}
internal fun keysIterator() = KeysItr(this)
internal fun valuesIterator() = ValuesItr(this)
internal fun entriesIterator() = EntriesItr(this)
internal companion object {
private const val MAGIC = -1640531527 // 2654435769L.toInt(), golden ratio
private const val INITIAL_CAPACITY = 8
private const val INITIAL_MAX_PROBE_DISTANCE = 2
private const val TOMBSTONE = -1
internal val Empty = MapBuilder(0).also { it.isReadOnly = true }
private fun computeHashSize(capacity: Int): Int = (capacity.coerceAtLeast(1) * 3).takeHighestOneBit()
private fun computeShift(hashSize: Int): Int = hashSize.countLeadingZeroBits() + 1
}
internal open class Itr(
internal val map: MapBuilder
) {
internal var index = 0
internal var lastIndex: Int = -1
private var expectedModCount: Int = map.modCount
init {
initNext()
}
internal fun initNext() {
while (index < map.length && map.presenceArray[index] < 0)
index++
}
fun hasNext(): Boolean = index < map.length
fun remove() {
checkForComodification()
check(lastIndex != -1) { "Call next() before removing element from the iterator." }
map.checkIsMutable()
map.removeEntryAt(lastIndex)
lastIndex = -1
expectedModCount = map.modCount
}
internal fun checkForComodification() {
if (map.modCount != expectedModCount)
throw ConcurrentModificationException()
}
}
internal class KeysItr(map: MapBuilder) : Itr(map), MutableIterator {
override fun next(): K {
checkForComodification()
if (index >= map.length) throw NoSuchElementException()
lastIndex = index++
val result = map.keysArray[lastIndex]
initNext()
return result
}
}
internal class ValuesItr(map: MapBuilder) : Itr(map), MutableIterator {
override fun next(): V {
checkForComodification()
if (index >= map.length) throw NoSuchElementException()
lastIndex = index++
val result = map.valuesArray!![lastIndex]
initNext()
return result
}
}
internal class EntriesItr(map: MapBuilder) : Itr(map),
MutableIterator> {
override fun next(): EntryRef {
checkForComodification()
if (index >= map.length) throw NoSuchElementException()
lastIndex = index++
val result = EntryRef(map, lastIndex)
initNext()
return result
}
internal fun nextHashCode(): Int {
if (index >= map.length) throw NoSuchElementException()
lastIndex = index++
val result = map.keysArray[lastIndex].hashCode() xor map.valuesArray!![lastIndex].hashCode()
initNext()
return result
}
fun nextAppendString(sb: StringBuilder) {
if (index >= map.length) throw NoSuchElementException()
lastIndex = index++
val key = map.keysArray[lastIndex]
if (key === map) sb.append("(this Map)") else sb.append(key)
sb.append('=')
val value = map.valuesArray!![lastIndex]
if (value === map) sb.append("(this Map)") else sb.append(value)
initNext()
}
}
internal class EntryRef(
private val map: MapBuilder,
private val index: Int
) : MutableMap.MutableEntry {
private val expectedModCount = map.modCount
override val key: K
get() {
checkForComodification()
return map.keysArray[index]
}
override val value: V
get() {
checkForComodification()
return map.valuesArray!![index]
}
override fun setValue(newValue: V): V {
checkForComodification()
map.checkIsMutable()
val valuesArray = map.allocateValuesArray()
val oldValue = valuesArray[index]
valuesArray[index] = newValue
return oldValue
}
override fun equals(other: Any?): Boolean =
other is Map.Entry<*, *> &&
other.key == key &&
other.value == value
override fun hashCode(): Int = key.hashCode() xor value.hashCode()
override fun toString(): String = "$key=$value"
private fun checkForComodification() {
if (map.modCount != expectedModCount)
throw ConcurrentModificationException("The backing map has been modified after this entry was obtained.")
}
}
}
internal class MapBuilderKeys internal constructor(
private val backing: MapBuilder
) : MutableSet, AbstractMutableSet() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.isEmpty()
override fun contains(element: E): Boolean = backing.containsKey(element)
override fun clear() = backing.clear()
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.removeKey(element)
override fun iterator(): MutableIterator = backing.keysIterator()
override fun removeAll(elements: Collection): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
internal class MapBuilderValues internal constructor(
val backing: MapBuilder<*, V>
) : MutableCollection, AbstractMutableCollection() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.isEmpty()
override fun contains(element: V): Boolean = backing.containsValue(element)
override fun add(element: V): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection): Boolean = throw UnsupportedOperationException()
override fun clear() = backing.clear()
override fun iterator(): MutableIterator = backing.valuesIterator()
override fun remove(element: V): Boolean = backing.removeValue(element)
override fun removeAll(elements: Collection): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
// intermediate abstract class to workaround KT-43321
internal abstract class AbstractMapBuilderEntrySet, K, V> : AbstractMutableSet() {
final override fun contains(element: E): Boolean = containsEntry(element)
abstract fun containsEntry(element: Map.Entry): Boolean
}
internal class MapBuilderEntries internal constructor(
val backing: MapBuilder
) : AbstractMapBuilderEntrySet, K, V>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.isEmpty()
override fun containsEntry(element: Map.Entry): Boolean = backing.containsEntry(element)
override fun clear() = backing.clear()
override fun add(element: MutableMap.MutableEntry): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection>): Boolean = throw UnsupportedOperationException()
override fun remove(element: MutableMap.MutableEntry): Boolean = backing.removeEntry(element)
override fun iterator(): MutableIterator> = backing.entriesIterator()
override fun containsAll(elements: Collection>): Boolean = backing.containsAllEntries(elements)
override fun removeAll(elements: Collection>): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection>): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
private class SerializedMap(
private var map: Map<*, *>
) : Externalizable {
constructor() : this(emptyMap()) // for deserialization
override fun writeExternal(output: java.io.ObjectOutput) {
output.writeByte(0) // flags
output.writeInt(map.size)
for (entry in map) {
output.writeObject(entry.key)
output.writeObject(entry.value)
}
}
override fun readExternal(input: java.io.ObjectInput) {
val flags = input.readByte().toInt()
if (flags != 0) {
throw InvalidObjectException("Unsupported flags value: $flags")
}
val size = input.readInt()
if (size < 0) throw InvalidObjectException("Illegal size value: $size.")
map = buildMap(size) {
repeat(size) {
val key = input.readObject()
val value = input.readObject()
put(key, value)
}
}
}
private fun readResolve(): Any = map
companion object {
private const val serialVersionUID: Long = 0L
}
}