[JS IR] Rework HashMapEntries, get rid of LinkedHashMap

^KT-59001
This commit is contained in:
Alexander Korepanov
2023-06-20 15:49:47 +02:00
committed by Space Team
parent f202865080
commit 2300facead
8 changed files with 195 additions and 527 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -20,82 +20,22 @@ package kotlin.collections
*/
public actual abstract class AbstractMutableMap<K, V> protected actual constructor() : AbstractMap<K, V>(), MutableMap<K, V> {
/**
* A mutable [Map.Entry] shared by several [Map] implementations.
*/
internal open class SimpleEntry<K, V>(override val key: K, value: V) : MutableMap.MutableEntry<K, V> {
constructor(entry: Map.Entry<K, V>) : this(entry.key, entry.value)
internal open fun createKeysView(): MutableSet<K> = HashMapKeysDefault(this)
internal open fun createValuesView(): MutableCollection<V> = HashMapValuesDefault(this)
private var _value = value
private var keysView: MutableSet<K>? = null
private var valuesView: MutableCollection<V>? = null
override val value: V get() = _value
actual override val keys: MutableSet<K>
get() = keysView ?: createKeysView().also { keysView = it }
override fun setValue(newValue: V): V {
// Should check if the map containing this entry is mutable.
// However, to not increase entry memory footprint it might be worthwhile not to check it here and
// force subclasses that implement `build()` (freezing) operation to implement their own `MutableEntry`.
// this@AbstractMutableMap.checkIsMutable()
val oldValue = this._value
this._value = newValue
return oldValue
}
override fun hashCode(): Int = entryHashCode(this)
override fun toString(): String = entryToString(this)
override fun equals(other: Any?): Boolean = entryEquals(this, other)
}
// intermediate abstract class to workaround KT-43321
internal abstract class AbstractEntrySet<E : Map.Entry<K, V>, K, V> : AbstractMutableSet<E>() {
final override fun contains(element: E): Boolean = containsEntry(element)
abstract fun containsEntry(element: Map.Entry<K, V>): Boolean
final override fun remove(element: E): Boolean = removeEntry(element)
abstract fun removeEntry(element: Map.Entry<K, V>): Boolean
}
actual override val values: MutableCollection<V>
get() = valuesView ?: createValuesView().also { valuesView = it }
actual override fun clear() {
entries.clear()
}
private var _keys: MutableSet<K>? = null
actual override val keys: MutableSet<K>
get() {
if (_keys == null) {
_keys = object : AbstractMutableSet<K>() {
override fun add(element: K): Boolean = throw UnsupportedOperationException("Add is not supported on keys")
override fun clear() {
this@AbstractMutableMap.clear()
}
override operator fun contains(element: K): Boolean = containsKey(element)
override operator fun iterator(): MutableIterator<K> {
val entryIterator = entries.iterator()
return object : MutableIterator<K> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): K = entryIterator.next().key
override fun remove() = entryIterator.remove()
}
}
override fun remove(element: K): Boolean {
checkIsMutable()
if (containsKey(element)) {
this@AbstractMutableMap.remove(element)
return true
}
return false
}
override val size: Int get() = this@AbstractMutableMap.size
override fun checkIsMutable(): Unit = this@AbstractMutableMap.checkIsMutable()
}
}
return _keys!!
}
actual abstract override fun put(key: K, value: V): V?
actual override fun putAll(from: Map<out K, V>) {
@@ -105,33 +45,6 @@ public actual abstract class AbstractMutableMap<K, V> protected actual construct
}
}
private var _values: MutableCollection<V>? = null
actual override val values: MutableCollection<V>
get() {
if (_values == null) {
_values = object : AbstractMutableCollection<V>() {
override fun add(element: V): Boolean = throw UnsupportedOperationException("Add is not supported on values")
override fun clear() = this@AbstractMutableMap.clear()
override operator fun contains(element: V): Boolean = containsValue(element)
override operator fun iterator(): MutableIterator<V> {
val entryIterator = entries.iterator()
return object : MutableIterator<V> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): V = entryIterator.next().value
override fun remove() = entryIterator.remove()
}
}
override val size: Int get() = this@AbstractMutableMap.size
override fun checkIsMutable(): Unit = this@AbstractMutableMap.checkIsMutable()
}
}
return _values!!
}
actual override fun remove(key: K): V? {
checkIsMutable()
val iter = entries.iterator()
@@ -152,5 +65,5 @@ public actual abstract class AbstractMutableMap<K, V> protected actual construct
* This method is called every time when a mutating method is called on this mutable map.
* Mutable maps that are built (frozen) must throw `UnsupportedOperationException`.
*/
internal open fun checkIsMutable(): Unit {}
internal open fun checkIsMutable() {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -20,34 +20,10 @@ import kotlin.collections.MutableMap.MutableEntry
// Classes that extend HashMap and implement `build()` (freezing) operation
// have to make sure mutating methods check `checkIsMutable`.
public actual open class HashMap<K, V> : AbstractMutableMap<K, V>, MutableMap<K, V> {
private inner class EntrySet : AbstractEntrySet<MutableEntry<K, V>, K, V>() {
override fun add(element: MutableEntry<K, V>): Boolean = throw UnsupportedOperationException("Add is not supported on entries")
override fun clear() {
this@HashMap.clear()
}
override fun containsEntry(element: Map.Entry<K, V>): Boolean = this@HashMap.containsEntry(element)
override operator fun iterator(): MutableIterator<MutableEntry<K, V>> = internalMap.entriesIterator()
override fun removeEntry(element: Map.Entry<K, V>): Boolean {
if (contains(element)) {
this@HashMap.remove(element.key)
return true
}
return false
}
override val size: Int get() = this@HashMap.size
}
/**
* Internal implementation of the map: either string-based or hashcode-based.
*/
private val internalMap: InternalMap<K, V>
internal val internalMap: InternalMap<K, V>
internal constructor(internalMap: InternalMap<K, V>) : super() {
this.internalMap = internalMap
@@ -92,7 +68,6 @@ public actual open class HashMap<K, V> : AbstractMutableMap<K, V>, MutableMap<K,
*/
actual constructor(initialCapacity: Int) : this(initialCapacity, 1.0f)
/**
* Creates a new [HashMap] filled with the contents of the specified [original] map.
*/
@@ -109,16 +84,12 @@ public actual open class HashMap<K, V> : AbstractMutableMap<K, V>, MutableMap<K,
actual override fun containsValue(value: V): Boolean = internalMap.containsValue(value)
private var _entries: MutableSet<MutableMap.MutableEntry<K, V>>? = null
actual override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() {
if (_entries == null) {
_entries = createEntrySet()
}
return _entries!!
}
override fun createKeysView(): MutableSet<K> = HashMapKeys(internalMap)
override fun createValuesView(): MutableCollection<V> = HashMapValues(internalMap)
internal open fun createEntrySet(): MutableSet<MutableMap.MutableEntry<K, V>> = EntrySet()
private var entriesView: HashMapEntrySet<K, V>? = null
actual override val entries: MutableSet<MutableEntry<K, V>>
get() = entriesView ?: HashMapEntrySet(internalMap).also { entriesView = it }
actual override operator fun get(key: K): V? = internalMap.get(key)
@@ -128,6 +99,7 @@ public actual open class HashMap<K, V> : AbstractMutableMap<K, V>, MutableMap<K,
actual override val size: Int get() = internalMap.size
actual override fun putAll(from: Map<out K, V>) = internalMap.putAll(from)
}
/**
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2023 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
internal class HashMapKeys<E> internal constructor(
private val backing: InternalMap<E, *>,
) : MutableSet<E>, AbstractMutableSet<E>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: E): Boolean = backing.contains(element)
override fun clear() = backing.clear()
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<E>): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.remove(element) != null
override fun iterator(): MutableIterator<E> = backing.keysIterator()
override fun checkIsMutable() = backing.checkIsMutable()
}
internal class HashMapValues<V> internal constructor(
private val backing: InternalMap<*, V>,
) : MutableCollection<V>, AbstractMutableCollection<V>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: V): Boolean = backing.containsValue(element)
override fun add(element: V): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<V>): Boolean = throw UnsupportedOperationException()
override fun clear() = backing.clear()
override fun iterator(): MutableIterator<V> = backing.valuesIterator()
override fun remove(element: V): Boolean = backing.removeValue(element)
override fun checkIsMutable() = backing.checkIsMutable()
}
/**
* Note: intermediate class with [E] `: Map.Entry<K, V>` is required to support
* [contains] for values that are [Map.Entry] but not [MutableMap.MutableEntry],
* and probably same for other functions.
* This is important because an instance of this class can be used as a result of [Map.entries],
* which should support [contains] for [Map.Entry].
* For example, this happens when upcasting [MutableMap] to [Map].
*
* The compiler enables special type-safe barriers to methods like [contains], which has [UnsafeVariance].
* Changing type from [MutableMap.MutableEntry] to [E] makes the compiler generate barriers checking that
* argument `is` [E] (so technically `is` [Map.Entry]) instead of `is` [MutableMap.MutableEntry].
*
* See also [KT-42248](https://youtrack.jetbrains.com/issue/KT-42428).
*/
internal abstract class HashMapEntrySetBase<K, V, E : Map.Entry<K, V>> internal constructor(
val backing: InternalMap<K, V>,
) : MutableSet<E>, AbstractMutableSet<E>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: E): Boolean = backing.containsEntry(element)
protected abstract fun getEntry(element: Map.Entry<K, V>): E?
override fun clear() = backing.clear()
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<E>): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.removeEntry(element)
override fun containsAll(elements: Collection<E>): Boolean = backing.containsAllEntries(elements)
override fun checkIsMutable() = backing.checkIsMutable()
}
internal class HashMapEntrySet<K, V> internal constructor(
backing: InternalMap<K, V>,
) : HashMapEntrySetBase<K, V, MutableMap.MutableEntry<K, V>>(backing) {
override fun getEntry(element: Map.Entry<K, V>): MutableMap.MutableEntry<K, V>? = backing.getEntry(element)
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> = backing.entriesIterator()
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2023 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
internal class HashMapKeysDefault<K, V>(private val backingMap: AbstractMutableMap<K, V>) : AbstractMutableSet<K>() {
override fun add(element: K): Boolean = throw UnsupportedOperationException("Add is not supported on keys")
override fun clear() = backingMap.clear()
override operator fun contains(element: K): Boolean = backingMap.containsKey(element)
override operator fun iterator(): MutableIterator<K> {
val entryIterator = backingMap.entries.iterator()
return object : MutableIterator<K> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): K = entryIterator.next().key
override fun remove() = entryIterator.remove()
}
}
override fun remove(element: K): Boolean {
checkIsMutable()
if (backingMap.containsKey(element)) {
backingMap.remove(element)
return true
}
return false
}
override val size: Int get() = backingMap.size
override fun checkIsMutable(): Unit = backingMap.checkIsMutable()
}
internal class HashMapValuesDefault<K, V>(private val backingMap: AbstractMutableMap<K, V>) : AbstractMutableCollection<V>() {
override fun add(element: V): Boolean = throw UnsupportedOperationException("Add is not supported on values")
override fun clear() = backingMap.clear()
override operator fun contains(element: V): Boolean = backingMap.containsValue(element)
override operator fun iterator(): MutableIterator<V> {
val entryIterator = backingMap.entries.iterator()
return object : MutableIterator<V> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): V = entryIterator.next().value
override fun remove() = entryIterator.remove()
}
}
override val size: Int get() = backingMap.size
override fun checkIsMutable(): Unit = backingMap.checkIsMutable()
}
@@ -19,10 +19,6 @@ internal class InternalHashMap<K, V> private constructor(
override val size: Int
get() = _size
private var keysView: HashMapKeys<K>? = null
private var valuesView: HashMapValues<V>? = null
private var entriesView: HashMapEntrySet<K, V>? = null
private var isReadOnly: Boolean = false
// ---------------------------- functions ----------------------------
@@ -78,11 +74,9 @@ internal class InternalHashMap<K, V> private constructor(
require(loadFactor > 0) { "Non-positive load factor: $loadFactor" }
}
@PublishedApi
internal fun build(): InternalHashMap<K, V> {
override fun build() {
checkIsMutable()
isReadOnly = true
return if (size > 0) this else EmptyHolder.value()
}
fun isEmpty(): Boolean = _size == 0
@@ -143,36 +137,6 @@ internal class InternalHashMap<K, V> private constructor(
length = 0
}
val keys: MutableSet<K>
get() {
val cur = keysView
return if (cur == null) {
val new = HashMapKeys(this)
keysView = new
new
} else cur
}
val values: MutableCollection<V>
get() {
val cur = valuesView
return if (cur == null) {
val new = HashMapValues(this)
valuesView = new
new
} else cur
}
val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() {
val cur = entriesView
return if (cur == null) {
val new = HashMapEntrySet(this)
entriesView = new
new
} else cur
}
override fun equals(other: Any?): Boolean {
return other === this ||
(other is Map<*, *>) &&
@@ -488,9 +452,9 @@ internal class InternalHashMap<K, V> private constructor(
return true
}
override fun removeValue(element: V): Boolean {
override fun removeValue(value: V): Boolean {
checkIsMutable()
val index = findValue(element)
val index = findValue(value)
if (index < 0) return false
removeKeyAt(index)
return true
@@ -511,15 +475,6 @@ internal class InternalHashMap<K, V> private constructor(
private fun computeShift(hashSize: Int): Int = hashSize.countLeadingZeroBits() + 1
}
internal object EmptyHolder {
val value_ = InternalHashMap<Nothing, Nothing>(0).also { it.isReadOnly = true }
fun <K, V> value(): InternalHashMap<K, V> {
@Suppress("UNCHECKED_CAST")
return value_ as InternalHashMap<K, V>
}
}
internal open class Itr<K, V>(
internal val map: InternalHashMap<K, V>,
) {
@@ -622,99 +577,3 @@ internal class InternalHashMap<K, V> private constructor(
override fun toString(): String = "$key=$value"
}
}
internal class HashMapKeys<E> internal constructor(
private val backing: InternalMap<E, *>,
) : MutableSet<E>, AbstractMutableSet<E>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: E): Boolean = backing.contains(element)
override fun clear() = backing.clear()
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<E>): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.remove(element) != null
override fun iterator(): MutableIterator<E> = backing.keysIterator()
override fun removeAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
internal class HashMapValues<V> internal constructor(
private val backing: InternalMap<*, V>,
) : MutableCollection<V>, AbstractMutableCollection<V>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: V): Boolean = backing.containsValue(element)
override fun add(element: V): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<V>): Boolean = throw UnsupportedOperationException()
override fun clear() = backing.clear()
override fun iterator(): MutableIterator<V> = backing.valuesIterator()
override fun remove(element: V): Boolean = backing.removeValue(element)
override fun removeAll(elements: Collection<V>): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection<V>): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
/**
* Note: intermediate class with [E] `: Map.Entry<K, V>` is required to support
* [contains] for values that are [Map.Entry] but not [MutableMap.MutableEntry],
* and probably same for other functions.
* This is important because an instance of this class can be used as a result of [Map.entries],
* which should support [contains] for [Map.Entry].
* For example, this happens when upcasting [MutableMap] to [Map].
*
* The compiler enables special type-safe barriers to methods like [contains], which has [UnsafeVariance].
* Changing type from [MutableMap.MutableEntry] to [E] makes the compiler generate barriers checking that
* argument `is` [E] (so technically `is` [Map.Entry]) instead of `is` [MutableMap.MutableEntry].
*
* See also [KT-42248](https://youtrack.jetbrains.com/issue/KT-42428).
*/
internal abstract class HashMapEntrySetBase<K, V, E : Map.Entry<K, V>> internal constructor(
val backing: InternalMap<K, V>,
) : MutableSet<E>, AbstractMutableSet<E>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.size == 0
override fun contains(element: E): Boolean = backing.containsEntry(element)
protected abstract fun getEntry(element: Map.Entry<K, V>): E?
override fun clear() = backing.clear()
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<E>): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.removeEntry(element)
override fun containsAll(elements: Collection<E>): Boolean = backing.containsAllEntries(elements)
override fun removeAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
internal class HashMapEntrySet<K, V> internal constructor(
backing: InternalMap<K, V>,
) : HashMapEntrySetBase<K, V, MutableMap.MutableEntry<K, V>>(backing) {
override fun getEntry(element: Map.Entry<K, V>): MutableMap.MutableEntry<K, V>? = backing.getEntry(element)
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> = backing.entriesIterator()
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -32,9 +32,14 @@ internal interface InternalMap<K, V> {
fun valuesIterator(): MutableIterator<V>
fun entriesIterator(): MutableIterator<MutableMap.MutableEntry<K, V>>
fun checkIsMutable() {}
fun checkIsMutable()
fun build()
fun containsAllEntries(m: Collection<Map.Entry<*, *>>): Boolean {
return m.all(this::containsOtherEntry)
return m.all {
// entry can be null due to variance.
val entry = it.unsafeCast<Any?>()
(entry is Map.Entry<*, *>) && containsOtherEntry(entry)
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
/*
@@ -163,6 +163,13 @@ internal open class InternalStringMap<K, V> : InternalMap<K, V> {
values = createJsArray()
}
override fun build() {
throw UnsupportedOperationException("build method is not implemented")
}
override fun checkIsMutable() {
}
override fun keysIterator(): MutableIterator<K> = KeysItr(this)
override fun valuesIterator(): MutableIterator<V> = ValuesItr(this)
override fun entriesIterator(): MutableIterator<MutableEntry<K, V>> = EntriesItr(this)
@@ -9,177 +9,31 @@
*/
package kotlin.collections
import kotlin.collections.MutableMap.MutableEntry
/**
* Hash table based implementation of the [MutableMap] interface, which additionally preserves the insertion order
* of entries during the iteration.
*
* The insertion order is preserved by maintaining a doubly-linked list of all of its entries.
* The insertion order is preserved natively by the HashMap implementation.
*/
public actual open class LinkedHashMap<K, V> : HashMap<K, V>, MutableMap<K, V> {
private companion object {
private val Empty = LinkedHashMap<Nothing, Nothing>(0).also { it.isReadOnly = true }
}
/**
* The entry we use includes next/prev pointers for a doubly-linked circular
* list with a head node. This reduces the special cases we have to deal with
* in the list operations.
* Note that we duplicate the key from the underlying hash map so we can find
* the eldest entry. The alternative would have been to modify HashMap so more
* of the code was directly usable here, but this would have added some
* overhead to HashMap, or to reimplement most of the HashMap code here with
* small modifications. Paying a small storage cost only if you use
* LinkedHashMap and minimizing code size seemed like a better tradeoff
*/
private inner class ChainEntry<K, V>(key: K, value: V) : AbstractMutableMap.SimpleEntry<K, V>(key, value) {
internal var next: ChainEntry<K, V>? = null
internal var prev: ChainEntry<K, V>? = null
override fun setValue(newValue: V): V {
this@LinkedHashMap.checkIsMutable()
return super.setValue(newValue)
}
}
private inner class EntrySet : AbstractEntrySet<MutableEntry<K, V>, K, V>() {
private inner class EntryIterator : MutableIterator<MutableEntry<K, V>> {
// The last entry that was returned from this iterator.
private var last: ChainEntry<K, V>? = null
// The next entry to return from this iterator.
private var next: ChainEntry<K, V>? = null
init {
next = head
// recordLastKnownStructure(map, this)
}
override fun hasNext(): Boolean {
return next !== null
}
override fun next(): MutableEntry<K, V> {
// checkStructuralChange(map, this)
if (!hasNext()) throw NoSuchElementException()
val current = next!!
last = current
next = current.next.takeIf { it !== head }
return current
}
override fun remove() {
check(last != null)
this@EntrySet.checkIsMutable()
// checkStructuralChange(map, this)
last!!.remove()
map.remove(last!!.key)
// recordLastKnownStructure(map, this)
last = null
}
}
override fun add(element: MutableEntry<K, V>): Boolean = throw UnsupportedOperationException("Add is not supported on entries")
override fun clear() {
this@LinkedHashMap.clear()
}
override fun containsEntry(element: Map.Entry<K, V>): Boolean = this@LinkedHashMap.containsEntry(element)
override operator fun iterator(): MutableIterator<MutableEntry<K, V>> = EntryIterator()
override fun removeEntry(element: Map.Entry<K, V>): Boolean {
checkIsMutable()
if (contains(element)) {
this@LinkedHashMap.remove(element.key)
return true
}
return false
}
override val size: Int get() = this@LinkedHashMap.size
override fun checkIsMutable(): Unit = this@LinkedHashMap.checkIsMutable()
}
/*
* The head of the insert order chain, which is a doubly-linked circular
* list.
*
* The most recently inserted node is at the end of the chain, ie.
* chain.prev.
*/
private var head: ChainEntry<K, V>? = null
/**
* Add this node to the end of the chain.
*/
private fun ChainEntry<K, V>.addToEnd() {
// This entry is not in the list.
check(next == null && prev == null)
val _head = head
if (_head == null) {
head = this
next = this
prev = this
} else {
// Chain is valid.
val _tail = checkNotNull(_head.prev)
// Update me.
prev = _tail
next = _head
// Update my new siblings: current head and old tail
_head.prev = this
_tail.next = this
}
}
/**
* Remove this node from the chain it is a part of.
*/
private fun ChainEntry<K, V>.remove() {
if (this.next === this) {
// if this is single element, remove head
head = null
} else {
if (head === this) {
// if this is first element, move head to next
head = next
}
next!!.prev = prev
prev!!.next = next
}
next = null
prev = null
}
/*
* The hashmap that keeps track of our entries and the chain. Note that we
* duplicate the key here to eliminate changes to HashMap and minimize the
* code here, at the expense of additional space.
*/
private val map: HashMap<K, ChainEntry<K, V>>
private var isReadOnly: Boolean = false
/**
* Creates a new empty [LinkedHashMap].
*/
actual constructor() : super() {
map = HashMap<K, ChainEntry<K, V>>()
}
actual constructor() : super()
internal constructor(backingMap: HashMap<K, Any>) : super() {
@Suppress("UNCHECKED_CAST") // expected to work due to erasure
map = backingMap as HashMap<K, ChainEntry<K, V>>
}
/**
* Creates a new empty [LinkedHashMap] with the specified initial capacity.
*
* Capacity is the maximum number of entries the map is able to store in current internal data structure.
* When the map gets full by a certain default load factor, its capacity is expanded,
* which usually leads to rebuild of the internal data structure.
*
* @param initialCapacity the initial capacity of the created map.
* Note that the argument is just a hint for the implementation and can be ignored.
*
* @throws IllegalArgumentException if [initialCapacity] is negative.
*/
actual constructor(initialCapacity: Int) : super(initialCapacity)
/**
* Creates a new empty [LinkedHashMap] with the specified initial capacity and load factor.
@@ -195,102 +49,28 @@ public actual open class LinkedHashMap<K, V> : HashMap<K, V>, MutableMap<K, V> {
*
* @throws IllegalArgumentException if [initialCapacity] is negative or [loadFactor] is non-positive.
*/
actual constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor) {
map = HashMap<K, ChainEntry<K, V>>()
}
/**
* Creates a new empty [LinkedHashMap] with the specified initial capacity.
*
* Capacity is the maximum number of entries the map is able to store in current internal data structure.
* When the map gets full by a certain default load factor, its capacity is expanded,
* which usually leads to rebuild of the internal data structure.
*
* @param initialCapacity the initial capacity of the created map.
* Note that the argument is just a hint for the implementation and can be ignored.
*
* @throws IllegalArgumentException if [initialCapacity] is negative.
*/
actual constructor(initialCapacity: Int) : this(initialCapacity, 1.0f)
actual constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor)
/**
* Creates a new [LinkedHashMap] filled with the contents of the specified [original] map.
*
* The iteration order of entries in the created map is the same as in the [original] map.
*/
actual constructor(original: Map<out K, V>) {
map = HashMap<K, ChainEntry<K, V>>()
this.putAll(original)
actual constructor(original: Map<out K, V>) : super(original)
internal constructor(internalMap: InternalMap<K, V>) : super(internalMap)
private object EmptyHolder {
val value = LinkedHashMap(InternalHashMap<Nothing, Nothing>(0).also { it.build() })
}
@PublishedApi
internal fun build(): Map<K, V> {
checkIsMutable()
isReadOnly = true
@Suppress("UNCHECKED_CAST")
return if (size > 0) this else (Empty as Map<K, V>)
internalMap.build()
return if (size > 0) this else EmptyHolder.value.unsafeCast<Map<K, V>>()
}
actual override fun clear() {
checkIsMutable()
map.clear()
head = null
}
// override fun clone(): Any {
// return LinkedHashMap(this)
// }
actual override fun containsKey(key: K): Boolean = map.containsKey(key)
actual override fun containsValue(value: V): Boolean {
var node: ChainEntry<K, V> = head ?: return false
do {
if (node.value == value) {
return true
}
node = node.next!!
} while (node !== head)
return false
}
internal override fun createEntrySet(): MutableSet<MutableMap.MutableEntry<K, V>> = EntrySet()
actual override operator fun get(key: K): V? = map.get(key)?.value
actual override fun put(key: K, value: V): V? {
checkIsMutable()
val old = map.get(key)
if (old == null) {
val newEntry = ChainEntry(key, value)
map.put(key, newEntry)
newEntry.addToEnd()
return null
} else {
return old.setValue(value)
}
}
actual override fun remove(key: K): V? {
checkIsMutable()
val entry = map.remove(key)
if (entry != null) {
entry.remove()
return entry.value
}
return null
}
actual override val size: Int get() = map.size
internal override fun checkIsMutable() {
if (isReadOnly) throw UnsupportedOperationException()
}
override fun checkIsMutable() = internalMap.checkIsMutable()
}
/**
@@ -298,5 +78,5 @@ public actual open class LinkedHashMap<K, V> : HashMap<K, V>, MutableMap<K, V> {
* JS object without hashing them.
*/
public fun <V> linkedStringMapOf(vararg pairs: Pair<String, V>): LinkedHashMap<String, V> {
return LinkedHashMap<String, V>(stringMapOf<Any>()).apply { putAll(pairs) }
return LinkedHashMap<String, V>(InternalStringMap()).apply { putAll(pairs) }
}