From ec166db5061f9eedaad034f926bbacd48ea5318c Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Thu, 12 Mar 2020 05:22:48 +0300 Subject: [PATCH] Implement collection builders --- .../src/kotlin/collections/ArrayDeque.kt | 34 +- .../src/kotlin/collections/Collections.kt | 6 +- .../stdlib/src/kotlin/collections/Maps.kt | 6 +- .../stdlib/src/kotlin/collections/Sets.kt | 6 +- .../collections/builders/ListBuilder.kt | 351 +++++++++++ .../kotlin/collections/builders/MapBuilder.kt | 593 ++++++++++++++++++ .../kotlin/collections/builders/SetBuilder.kt | 29 + .../stdlib/test/collections/ArrayDequeTest.kt | 10 +- .../test/collections/ContainerBuilderTest.kt | 98 ++- .../kotlin-stdlib-runtime-merged.txt | 67 ++ 10 files changed, 1164 insertions(+), 36 deletions(-) create mode 100644 libraries/stdlib/src/kotlin/collections/builders/ListBuilder.kt create mode 100644 libraries/stdlib/src/kotlin/collections/builders/MapBuilder.kt create mode 100644 libraries/stdlib/src/kotlin/collections/builders/SetBuilder.kt diff --git a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt index 2c9c4790085..8613f7f0417 100644 --- a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt +++ b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt @@ -5,13 +5,6 @@ package kotlin.collections -import kotlin.native.concurrent.SharedImmutable - -@SharedImmutable -private val emptyElementData = emptyArray() -private const val maxArraySize = Int.MAX_VALUE - 8 -private const val defaultMinCapacity = 10 - /** * Resizable-array implementation of the deque data structure. * @@ -74,17 +67,6 @@ public class ArrayDeque : AbstractMutableList { copyElements(newCapacity) } - // made internal for testing - internal fun newCapacity(oldCapacity: Int, minCapacity: Int): Int { - // overflow-conscious - var newCapacity = oldCapacity + (oldCapacity shr 1) - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity - if (newCapacity - maxArraySize > 0) - newCapacity = if (minCapacity > maxArraySize) Int.MAX_VALUE else maxArraySize - return newCapacity - } - /** * Creates a new array with the specified [newCapacity] size and copies elements in the [elementData] array to it. */ @@ -547,6 +529,22 @@ public class ArrayDeque : AbstractMutableList { size = 0 } + internal companion object { + private val emptyElementData = emptyArray() + private const val maxArraySize = Int.MAX_VALUE - 8 + private const val defaultMinCapacity = 10 + + internal fun newCapacity(oldCapacity: Int, minCapacity: Int): Int { + // overflow-conscious + var newCapacity = oldCapacity + (oldCapacity shr 1) + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity + if (newCapacity - maxArraySize > 0) + newCapacity = if (minCapacity > maxArraySize) Int.MAX_VALUE else maxArraySize + return newCapacity + } + } + // For testing only internal fun internalStructure(structure: (head: Int, elements: Array) -> Unit) { val tail = internalIndex(size) diff --git a/libraries/stdlib/src/kotlin/collections/Collections.kt b/libraries/stdlib/src/kotlin/collections/Collections.kt index 3980e3ad08a..aefaf821ae3 100644 --- a/libraries/stdlib/src/kotlin/collections/Collections.kt +++ b/libraries/stdlib/src/kotlin/collections/Collections.kt @@ -11,6 +11,7 @@ package kotlin.collections import kotlin.contracts.* import kotlin.random.Random +import kotlin.collections.builders.* internal object EmptyIterator : ListIterator { override fun hasNext(): Boolean = false @@ -169,7 +170,7 @@ public inline fun MutableList(size: Int, init: (index: Int) -> T): MutableLi @kotlin.internal.InlineOnly public inline fun buildList(@BuilderInference builderAction: MutableList.() -> Unit): List { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - return ArrayList().apply(builderAction) + return ListBuilder().apply(builderAction).build() } /** @@ -190,8 +191,7 @@ public inline fun buildList(@BuilderInference builderAction: MutableList. @kotlin.internal.InlineOnly public inline fun buildList(capacity: Int, @BuilderInference builderAction: MutableList.() -> Unit): List { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - checkBuilderCapacity(capacity) - return ArrayList(capacity).apply(builderAction) + return ListBuilder(capacity).apply(builderAction).build() } diff --git a/libraries/stdlib/src/kotlin/collections/Maps.kt b/libraries/stdlib/src/kotlin/collections/Maps.kt index 360629ef21f..e872fb0df0f 100644 --- a/libraries/stdlib/src/kotlin/collections/Maps.kt +++ b/libraries/stdlib/src/kotlin/collections/Maps.kt @@ -9,6 +9,7 @@ package kotlin.collections +import kotlin.collections.builders.MapBuilder import kotlin.contracts.* private object EmptyMap : Map, Serializable { @@ -139,7 +140,7 @@ public fun linkedMapOf(vararg pairs: Pair): LinkedHashMap = p @kotlin.internal.InlineOnly public inline fun buildMap(@BuilderInference builderAction: MutableMap.() -> Unit): Map { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - return LinkedHashMap().apply(builderAction) + return MapBuilder().apply(builderAction).build() } /** @@ -162,8 +163,7 @@ public inline fun buildMap(@BuilderInference builderAction: MutableMap buildMap(capacity: Int, @BuilderInference builderAction: MutableMap.() -> Unit): Map { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - checkBuilderCapacity(capacity) - return LinkedHashMap(mapCapacity(capacity)).apply(builderAction) + return MapBuilder(capacity).apply(builderAction).build() } /** diff --git a/libraries/stdlib/src/kotlin/collections/Sets.kt b/libraries/stdlib/src/kotlin/collections/Sets.kt index eab0e097b48..875c4a7da8c 100644 --- a/libraries/stdlib/src/kotlin/collections/Sets.kt +++ b/libraries/stdlib/src/kotlin/collections/Sets.kt @@ -10,6 +10,7 @@ package kotlin.collections import kotlin.contracts.* +import kotlin.collections.builders.* internal object EmptySet : Set, Serializable { private const val serialVersionUID: Long = 3406603774387020532 @@ -125,7 +126,7 @@ public fun setOfNotNull(vararg elements: T?): Set { @kotlin.internal.InlineOnly public inline fun buildSet(@BuilderInference builderAction: MutableSet.() -> Unit): Set { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - return LinkedHashSet().apply(builderAction) + return SetBuilder().apply(builderAction).build() } /** @@ -148,8 +149,7 @@ public inline fun buildSet(@BuilderInference builderAction: MutableSet.() @kotlin.internal.InlineOnly public inline fun buildSet(capacity: Int, @BuilderInference builderAction: MutableSet.() -> Unit): Set { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - checkBuilderCapacity(capacity) - return LinkedHashSet(mapCapacity(capacity)).apply(builderAction) + return SetBuilder(capacity).apply(builderAction).build() } diff --git a/libraries/stdlib/src/kotlin/collections/builders/ListBuilder.kt b/libraries/stdlib/src/kotlin/collections/builders/ListBuilder.kt new file mode 100644 index 00000000000..42719a6dec2 --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/builders/ListBuilder.kt @@ -0,0 +1,351 @@ +/* + * Copyright 2010-2020 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 + +@PublishedApi +internal class ListBuilder private constructor( + private var array: Array, + private var offset: Int, + private var length: Int, + private var isReadOnly: Boolean, + private val backing: ListBuilder? +) : MutableList, RandomAccess, AbstractMutableList() { + + constructor() : this(10) + + constructor(initialCapacity: Int) : this( + arrayOfUninitializedElements(initialCapacity), 0, 0, false, null) + + fun build(): List { + if (backing != null) throw IllegalStateException() // just in case somebody casts subList to ListBuilder + checkIsMutable() + isReadOnly = true + return this + } + + override val size: Int + get() = length + + override fun isEmpty(): Boolean = length == 0 + + override fun get(index: Int): E { + AbstractList.checkElementIndex(index, length) + return array[offset + index] + } + + override operator fun set(index: Int, element: E): E { + checkIsMutable() + AbstractList.checkElementIndex(index, length) + val old = array[offset + index] + array[offset + index] = element + return old + } + + override fun indexOf(element: E): Int { + var i = 0 + while (i < length) { + if (array[offset + i] == element) return i + i++ + } + return -1 + } + + override fun lastIndexOf(element: E): Int { + var i = length - 1 + while (i >= 0) { + if (array[offset + i] == element) return i + i-- + } + return -1 + } + + override fun iterator(): MutableIterator = Itr(this, 0) + override fun listIterator(): MutableListIterator = Itr(this, 0) + + override fun listIterator(index: Int): MutableListIterator { + AbstractList.checkPositionIndex(index, length) + return Itr(this, index) + } + + override fun add(element: E): Boolean { + checkIsMutable() + addAtInternal(offset + length, element) + return true + } + + override fun add(index: Int, element: E) { + checkIsMutable() + AbstractList.checkPositionIndex(index, length) + addAtInternal(offset + index, element) + } + + override fun addAll(elements: Collection): Boolean { + checkIsMutable() + val n = elements.size + addAllInternal(offset + length, elements, n) + return n > 0 + } + + override fun addAll(index: Int, elements: Collection): Boolean { + checkIsMutable() + AbstractList.checkPositionIndex(index, length) + val n = elements.size + addAllInternal(offset + index, elements, n) + return n > 0 + } + + override fun clear() { + checkIsMutable() + removeRangeInternal(offset, length) + } + + override fun removeAt(index: Int): E { + checkIsMutable() + AbstractList.checkElementIndex(index, length) + return removeAtInternal(offset + index) + } + + override fun remove(element: E): Boolean { + checkIsMutable() + val i = indexOf(element) + if (i >= 0) removeAt(i) + return i >= 0 + } + + override fun removeAll(elements: Collection): Boolean { + checkIsMutable() + return retainOrRemoveAllInternal(offset, length, elements, false) > 0 + } + + override fun retainAll(elements: Collection): Boolean { + checkIsMutable() + return retainOrRemoveAllInternal(offset, length, elements, true) > 0 + } + + override fun subList(fromIndex: Int, toIndex: Int): MutableList { + AbstractList.checkRangeIndexes(fromIndex, toIndex, length) + return ListBuilder(array, offset + fromIndex, toIndex - fromIndex, isReadOnly, this) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun ensureCapacity(minCapacity: Int) { + if (backing != null) throw IllegalStateException() // just in case somebody casts subList to ListBuilder + if (minCapacity > array.size) { + val newSize = ArrayDeque.newCapacity(array.size, minCapacity) + array = array.copyOfUninitializedElements(newSize) + } + } + + override fun equals(other: Any?): Boolean { + return other === this || + (other is List<*>) && contentEquals(other) + } + + override fun hashCode(): Int { + return array.subarrayContentHashCode(offset, length) + } + + override fun toString(): String { + return array.subarrayContentToString(offset, length) + } + + // ---------------------------- private ---------------------------- + + private fun checkIsMutable() { + if (isReadOnly) throw UnsupportedOperationException() + } + + private fun ensureExtraCapacity(n: Int) { + ensureCapacity(length + n) + } + + private fun contentEquals(other: List<*>): Boolean { + return array.subarrayContentEquals(offset, length, other) + } + + private fun insertAtInternal(i: Int, n: Int) { + ensureExtraCapacity(n) + array.copyInto(array, startIndex = i, endIndex = offset + length, destinationOffset = i + n) + length += n + } + + private fun addAtInternal(i: Int, element: E) { + if (backing != null) { + backing.addAtInternal(i, element) + array = backing.array + length++ + } else { + insertAtInternal(i, 1) + array[i] = element + } + } + + private fun addAllInternal(i: Int, elements: Collection, n: Int) { + if (backing != null) { + backing.addAllInternal(i, elements, n) + array = backing.array + length += n + } else { + insertAtInternal(i, n) + var j = 0 + val it = elements.iterator() + while (j < n) { + array[i + j] = it.next() + j++ + } + } + } + + private fun removeAtInternal(i: Int): E { + if (backing != null) { + val old = backing.removeAtInternal(i) + length-- + return old + } else { + val old = array[i] + array.copyInto(array, startIndex = i + 1, endIndex = offset + length, destinationOffset = i) + array.resetAt(offset + length - 1) + length-- + return old + } + } + + private fun removeRangeInternal(rangeOffset: Int, rangeLength: Int) { + if (backing != null) { + backing.removeRangeInternal(rangeOffset, rangeLength) + } else { + array.copyInto(array, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset) + array.resetRange(fromIndex = length - rangeLength, toIndex = length) + } + length -= rangeLength + } + + /** Retains elements if [retain] == true and removes them it [retain] == false. */ + private fun retainOrRemoveAllInternal(rangeOffset: Int, rangeLength: Int, elements: Collection, retain: Boolean): Int { + if (backing != null) { + val removed = backing.retainOrRemoveAllInternal(rangeOffset, rangeLength, elements, retain) + length -= removed + return removed + } else { + var i = 0 + var j = 0 + while (i < rangeLength) { + if (elements.contains(array[rangeOffset + i]) == retain) { + array[rangeOffset + j++] = array[rangeOffset + i++] + } else { + i++ + } + } + val removed = rangeLength - j + array.copyInto(array, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset + j) + array.resetRange(fromIndex = length - removed, toIndex = length) + length -= removed + return removed + } + } + + private class Itr : MutableListIterator { + private val list: ListBuilder + private var index: Int + private var lastIndex: Int + + constructor(list: ListBuilder, index: Int) { + this.list = list + this.index = index + this.lastIndex = -1 + } + + override fun hasPrevious(): Boolean = index > 0 + override fun hasNext(): Boolean = index < list.length + + override fun previousIndex(): Int = index - 1 + override fun nextIndex(): Int = index + + override fun previous(): E { + if (index <= 0) throw NoSuchElementException() + lastIndex = --index + return list.array[list.offset + lastIndex] + } + + override fun next(): E { + if (index >= list.length) throw NoSuchElementException() + lastIndex = index++ + return list.array[list.offset + lastIndex] + } + + override fun set(element: E) { + list.checkIsMutable() + check(lastIndex != -1) { "Call next() or previous() before replacing element from the iterator." } + list.array[list.offset + lastIndex] = element + } + + override fun add(element: E) { + list.add(index++, element) + lastIndex = -1 + } + + override fun remove() { + check(lastIndex != -1) { "Call next() or previous() before removing element from the iterator." } + list.removeAt(lastIndex) + index = lastIndex + lastIndex = -1 + } + } +} + +internal fun arrayOfUninitializedElements(size: Int): Array { + require(size >= 0) { "capacity must be non-negative." } + @Suppress("UNCHECKED_CAST") + return arrayOfNulls(size) as Array +} + +private fun Array.subarrayContentToString(offset: Int, length: Int): String { + val sb = StringBuilder(2 + length * 3) + sb.append("[") + var i = 0 + while (i < length) { + if (i > 0) sb.append(", ") + sb.append(this[offset + i]) + i++ + } + sb.append("]") + return sb.toString() +} + +private fun Array.subarrayContentHashCode(offset: Int, length: Int): Int { + var result = 1 + var i = 0 + while (i < length) { + val nextElement = this[offset + i] + result = result * 31 + nextElement.hashCode() + i++ + } + return result +} + +private fun Array.subarrayContentEquals(offset: Int, length: Int, other: List<*>): Boolean { + if (length != other.size) return false + var i = 0 + while (i < length) { + if (this[offset + i] != other[i]) return false + i++ + } + return true +} + +internal fun Array.copyOfUninitializedElements(newSize: Int): Array { + @Suppress("UNCHECKED_CAST") + return copyOf(newSize) as Array +} + +internal fun Array.resetAt(index: Int) { + @Suppress("UNCHECKED_CAST") + (this as Array)[index] = null +} + +internal fun Array.resetRange(fromIndex: Int, toIndex: Int) { + for (index in fromIndex until toIndex) resetAt(index) +} \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/collections/builders/MapBuilder.kt b/libraries/stdlib/src/kotlin/collections/builders/MapBuilder.kt new file mode 100644 index 00000000000..03487672d19 --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/builders/MapBuilder.kt @@ -0,0 +1,593 @@ +/* + * Copyright 2010-2020 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 + +@PublishedApi +internal class MapBuilder private constructor( + private var keysArray: Array, + private var valuesArray: Array?, // allocated only when actually used, always null in pure HashSet + private var presenceArray: IntArray, + private var hashArray: IntArray, + private var maxProbeDistance: Int, + private var length: Int +) : MutableMap { + private var hashShift: Int = computeShift(hashSize) + + override var size: Int = 0 + private set + + private var keysView: HashMapKeys? = null + private var valuesView: HashMapValues? = null + private var entriesView: HashMapEntrySet? = null + + private var isReadOnly: Boolean = false + + // ---------------------------- 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 + return this + } + + 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) { + putAllEntries(from.entries) // mutability gets checked here + } + + override fun remove(key: K): V? { + val index = removeKey(key) // mutability gets checked here + if (index < 0) return null + val valuesArray = valuesArray!! + val oldValue = valuesArray[index] + valuesArray.resetAt(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 + } + + override val keys: MutableSet get() { + val cur = keysView + return if (cur == null) { + val new = HashMapKeys(this) + keysView = new + new + } else cur + } + + override val values: MutableCollection get() { + val cur = valuesView + return if (cur == null) { + val new = HashMapValues(this) + valuesView = new + new + } else cur + } + + override val entries: MutableSet> get() { + val cur = entriesView + return if (cur == null) { + val new = HashMapEntrySet(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 ---------------------------- + + private val capacity: Int get() = keysArray.size + private val hashSize: Int get() = hashArray.size + + private fun checkIsMutable() { + if (isReadOnly) throw UnsupportedOperationException() + } + + private fun ensureExtraCapacity(n: Int) { + ensureCapacity(length + n) + } + + private fun ensureCapacity(capacity: Int) { + if (capacity > this.capacity) { + var newSize = this.capacity * 3 / 2 + if (capacity > newSize) newSize = capacity + keysArray = keysArray.copyOfUninitializedElements(newSize) + valuesArray = valuesArray?.copyOfUninitializedElements(newSize) + presenceArray = presenceArray.copyOf(newSize) + val newHashSize = computeHashSize(newSize) + if (newHashSize > hashSize) rehash(newHashSize) + } else if (length + capacity - size > this.capacity) { + rehash(hashSize) + } + } + + 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() { + var i = 0 + var j = 0 + val valuesArray = valuesArray + while (i < length) { + if (presenceArray[i] >= 0) { + keysArray[j] = keysArray[i] + if (valuesArray != null) valuesArray[j] = valuesArray[i] + 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) { + if (length > size) compact() + if (newHashSize != hashSize) { + hashArray = IntArray(newHashSize) + hashShift = computeShift(newHashSize) + } else { + hashArray.fill(0, 0, hashSize) + } + 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++ + 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): Int { + checkIsMutable() + val index = findKey(key) + if (index < 0) return TOMBSTONE + removeKeyAt(index) + return index + } + + private fun removeKeyAt(index: Int) { + keysArray.resetAt(index) + removeHashAt(presenceArray[index]) + presenceArray[index] = TOMBSTONE + size-- + } + + 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 + } + + internal fun putEntry(entry: Map.Entry): Boolean { + checkIsMutable() + 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 + } + + internal fun putAllEntries(from: Collection>): Boolean { + checkIsMutable() + 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 + removeKeyAt(index) + return true + } + + internal fun removeValue(element: V): Boolean { + checkIsMutable() + val index = findValue(element) + if (index < 0) return false + removeKeyAt(index) + return true + } + + internal fun keysIterator() = KeysItr(this) + internal fun valuesIterator() = ValuesItr(this) + internal fun entriesIterator() = EntriesItr(this) + + private 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 + + @UseExperimental(ExperimentalStdlibApi::class) + private fun computeHashSize(capacity: Int): Int = (capacity.coerceAtLeast(1) * 3).takeHighestOneBit() + + @UseExperimental(ExperimentalStdlibApi::class) + 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 + + init { + initNext() + } + + internal fun initNext() { + while (index < map.length && map.presenceArray[index] < 0) + index++ + } + + fun hasNext(): Boolean = index < map.length + + fun remove() { + map.checkIsMutable() + map.removeKeyAt(lastIndex) + lastIndex = -1 + } + } + + internal class KeysItr(map: MapBuilder) : Itr(map), MutableIterator { + override fun next(): K { + 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 { + 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 { + 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 { + override val key: K + get() = map.keysArray[index] + + override val value: V + get() = map.valuesArray!![index] + + override fun setValue(newValue: V): V { + 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" + } +} + +internal class HashMapKeys 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) >= 0 + override fun iterator(): MutableIterator = backing.keysIterator() +} + +internal class HashMapValues 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) +} + +internal class HashMapEntrySet internal constructor( + val backing: MapBuilder +) : MutableSet>, AbstractMutableSet>() { + + override val size: Int get() = backing.size + override fun isEmpty(): Boolean = backing.isEmpty() + override fun contains(element: MutableMap.MutableEntry): Boolean = backing.containsEntry(element) + override fun clear() = backing.clear() + override fun add(element: MutableMap.MutableEntry): Boolean = backing.putEntry(element) + 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 addAll(elements: Collection>): Boolean = backing.putAllEntries(elements) +} diff --git a/libraries/stdlib/src/kotlin/collections/builders/SetBuilder.kt b/libraries/stdlib/src/kotlin/collections/builders/SetBuilder.kt new file mode 100644 index 00000000000..e603666d4d4 --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/builders/SetBuilder.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2010-2020 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 + +@PublishedApi +internal class SetBuilder internal constructor( + private val backing: MapBuilder +) : MutableSet, AbstractMutableSet() { + + constructor() : this(MapBuilder()) + + constructor(initialCapacity: Int) : this(MapBuilder(initialCapacity)) + + fun build(): Set { + backing.build() + return this + } + + 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 = backing.addKey(element) >= 0 + override fun remove(element: E): Boolean = backing.removeKey(element) >= 0 + override fun iterator(): MutableIterator = backing.keysIterator() +} \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArrayDequeTest.kt b/libraries/stdlib/test/collections/ArrayDequeTest.kt index 67750a80f1c..31344701352 100644 --- a/libraries/stdlib/test/collections/ArrayDequeTest.kt +++ b/libraries/stdlib/test/collections/ArrayDequeTest.kt @@ -612,15 +612,13 @@ class ArrayDequeTest { @Suppress("INVISIBLE_MEMBER") @Test fun newCapacity() { - val deque = ArrayDeque() - // oldCapacity < minCapacity < newCapacity repeat(100) { val oldCapacity = Random.nextInt(1 shl 30) val newCapacity = oldCapacity + (oldCapacity shr 1) val minCapacity = Random.nextInt(oldCapacity + 1 until newCapacity) - assertEquals(newCapacity, deque.newCapacity(oldCapacity, minCapacity)) + assertEquals(newCapacity, ArrayDeque.newCapacity(oldCapacity, minCapacity)) } // oldCapacity < newCapacity < minCapacity @@ -629,7 +627,7 @@ class ArrayDequeTest { val newCapacity = oldCapacity + (oldCapacity shr 1) val minCapacity = Random.nextInt(newCapacity..Int.MAX_VALUE) - assertEquals(minCapacity, deque.newCapacity(oldCapacity, minCapacity)) + assertEquals(minCapacity, ArrayDeque.newCapacity(oldCapacity, minCapacity)) } // newCapacity overflow, oldCapacity < minCapacity <= maxArraySize @@ -638,7 +636,7 @@ class ArrayDequeTest { val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29) until maxArraySize) val minCapacity = Random.nextInt(oldCapacity..maxArraySize) - assertEquals(maxArraySize, deque.newCapacity(oldCapacity, minCapacity)) + assertEquals(maxArraySize, ArrayDeque.newCapacity(oldCapacity, minCapacity)) } // newCapacity overflow, minCapacity > maxArraySize @@ -646,7 +644,7 @@ class ArrayDequeTest { val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29)..maxArraySize) val minCapacity = Random.nextInt(maxArraySize + 1..Int.MAX_VALUE) - assertEquals(Int.MAX_VALUE, deque.newCapacity(oldCapacity, minCapacity)) + assertEquals(Int.MAX_VALUE, ArrayDeque.newCapacity(oldCapacity, minCapacity)) } } } \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ContainerBuilderTest.kt b/libraries/stdlib/test/collections/ContainerBuilderTest.kt index f2c85ac6f69..2c6d2179675 100644 --- a/libraries/stdlib/test/collections/ContainerBuilderTest.kt +++ b/libraries/stdlib/test/collections/ContainerBuilderTest.kt @@ -1,8 +1,42 @@ package test.collections +import test.collections.behaviors.listBehavior +import test.collections.behaviors.mapBehavior +import test.collections.behaviors.setBehavior import kotlin.test.* class ContainerBuilderTest { + private fun mutableCollectionOperations(e: E) = listOf.() -> Unit>( + { add(e) }, + { addAll(listOf(e)) }, + { remove(e) }, + { removeAll(listOf(e)) }, + { retainAll(listOf(e)) }, + { clear() }, + { iterator().apply { next() }.remove() } + ) + + private fun mutableListOperations(e: E) = mutableCollectionOperations(e) + listOf.() -> Unit>( + { add(0, e) }, + { addAll(0, listOf(e)) }, + { removeAt(0) }, + { set(0, e) }, + { listIterator().apply { next() }.remove() }, + { listIterator(0).apply { next() }.remove() }, + { listIterator().add(e) } + ) + + private fun mutableSetOperations(e: E): List.() -> Unit> = mutableCollectionOperations(e) + + private fun mutableMapOperations(k: K, v: V) = listOf.() -> Unit>( + { put(k, v) }, + { remove(k) }, + { putAll(mapOf(k to v)) }, + { clear() }, + { entries.first().setValue(v) }, + { entries.iterator().next().setValue(v) } + ) + @Test fun buildList() { val x = buildList { @@ -16,12 +50,51 @@ class ContainerBuilderTest { add('d') } - assertEquals(listOf('a', 'b', 'c', 'd'), y) + compare(listOf('a', 'b', 'c', 'd'), y) { listBehavior() } + compare(listOf('a', 'b', 'c', 'd'), y.subList(0, 4)) { listBehavior() } + compare(listOf('b', 'c'), y.subList(1, 4).subList(0, 2)) { listBehavior() } assertEquals(listOf(1), buildList(0) { add(1) }) assertFailsWith { buildList(-1) { add(0) } } + + assertTrue(y is MutableList) + for (operation in mutableListOperations('a')) { + assertFailsWith { y.operation() } + assertFailsWith { y.subList(1, 3).operation() } + } + } + + @Test + fun listBuilderSubList() { + buildList { + addAll(listOf('a', 'b', 'c', 'd', 'e')) + + val subList = subList(1, 4) + compare(listOf('a', 'b', 'c', 'd', 'e'), this) { listBehavior() } + compare(listOf('b', 'c', 'd'), subList) { listBehavior() } + + set(2, '1') + compare(listOf('a', 'b', '1', 'd', 'e'), this) { listBehavior() } + compare(listOf('b', '1', 'd'), subList) { listBehavior() } + + subList[2] = '2' + compare(listOf('a', 'b', '1', '2', 'e'), this) { listBehavior() } + compare(listOf('b', '1', '2'), subList) { listBehavior() } + + subList.add('3') + compare(listOf('a', 'b', '1', '2', '3', 'e'), this) { listBehavior() } + compare(listOf('b', '1', '2', '3'), subList) { listBehavior() } + + val subSubList = subList.subList(2, 4) + // buffer reallocation should happen + repeat(20) { subSubList.add('x') } + + compare("ab123${"x".repeat(20)}e".toList(), this) { listBehavior() } + compare("b123${"x".repeat(20)}".toList(), subList) { listBehavior() } + compare("23${"x".repeat(20)}".toList(), subSubList) { listBehavior() } + } } @Test @@ -37,12 +110,17 @@ class ContainerBuilderTest { add('d') } - assertEquals(setOf('c', 'b', 'd'), y) + compare(setOf('c', 'b', 'd'), y) { setBehavior() } assertEquals(setOf(1), buildSet(0) { add(1) }) assertFailsWith { buildSet(-1) { add(0) } } + + assertTrue(y is MutableSet) + for (operation in mutableSetOperations('b')) { + assertFailsWith { y.operation() } + } } @Test @@ -59,11 +137,25 @@ class ContainerBuilderTest { put('d', 4) } - assertEquals(mapOf('a' to 1, 'c' to 3, 'b' to 2, 'd' to 4), y) + compare(mapOf('a' to 1, 'c' to 3, 'b' to 2, 'd' to 4), y) { mapBehavior() } assertEquals(mapOf("a" to 1), buildMap(0) { put("a", 1) }) assertFailsWith { buildMap(-1) { put("x", 1) } } + + assertTrue(y is MutableMap) + for (operation in mutableMapOperations('a', 0)) { + assertFailsWith { y.operation() } + } + for (operation in mutableSetOperations('a')) { + assertFailsWith { y.keys.operation() } + } + for (operation in mutableCollectionOperations(1)) { + assertFailsWith { y.values.operation() } + } + for (operation in mutableSetOperations(y.entries.first())) { + assertFailsWith { y.entries.operation() } + } } } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 33890ec8097..39f4bac1f57 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -674,6 +674,7 @@ public abstract class kotlin/collections/AbstractSet : kotlin/collections/Abstra } public final class kotlin/collections/ArrayDeque : kotlin/collections/AbstractMutableList { + public static final field Companion Lkotlin/collections/ArrayDeque$Companion; public fun ()V public fun (I)V public fun (Ljava/util/Collection;)V @@ -2504,6 +2505,72 @@ public abstract class kotlin/collections/UShortIterator : java/util/Iterator, ko public fun remove ()V } +public final class kotlin/collections/builders/ListBuilder : kotlin/collections/AbstractMutableList, java/util/List, java/util/RandomAccess, kotlin/jvm/internal/markers/KMutableList { + public fun ()V + public fun (I)V + public fun add (ILjava/lang/Object;)V + public fun add (Ljava/lang/Object;)Z + public fun addAll (ILjava/util/Collection;)Z + public fun addAll (Ljava/util/Collection;)Z + public final fun build ()Ljava/util/List; + public fun clear ()V + public fun equals (Ljava/lang/Object;)Z + public fun get (I)Ljava/lang/Object; + public fun getSize ()I + public fun hashCode ()I + public fun indexOf (Ljava/lang/Object;)I + public fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public fun lastIndexOf (Ljava/lang/Object;)I + public fun listIterator ()Ljava/util/ListIterator; + public fun listIterator (I)Ljava/util/ListIterator; + public fun remove (Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public fun removeAt (I)Ljava/lang/Object; + public fun retainAll (Ljava/util/Collection;)Z + public fun set (ILjava/lang/Object;)Ljava/lang/Object; + public fun subList (II)Ljava/util/List; + public fun toString ()Ljava/lang/String; +} + +public final class kotlin/collections/builders/MapBuilder : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { + public fun ()V + public fun (I)V + public final fun build ()Ljava/util/Map; + public fun clear ()V + public fun containsKey (Ljava/lang/Object;)Z + public fun containsValue (Ljava/lang/Object;)Z + public final fun entrySet ()Ljava/util/Set; + public fun equals (Ljava/lang/Object;)Z + public fun get (Ljava/lang/Object;)Ljava/lang/Object; + public fun getEntries ()Ljava/util/Set; + public fun getKeys ()Ljava/util/Set; + public fun getSize ()I + public fun getValues ()Ljava/util/Collection; + public fun hashCode ()I + public fun isEmpty ()Z + public final fun keySet ()Ljava/util/Set; + public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + public fun putAll (Ljava/util/Map;)V + public fun remove (Ljava/lang/Object;)Ljava/lang/Object; + public final fun size ()I + public fun toString ()Ljava/lang/String; + public final fun values ()Ljava/util/Collection; +} + +public final class kotlin/collections/builders/SetBuilder : kotlin/collections/AbstractMutableSet, java/util/Set, kotlin/jvm/internal/markers/KMutableSet { + public fun ()V + public fun (I)V + public fun add (Ljava/lang/Object;)Z + public final fun build ()Ljava/util/Set; + public fun clear ()V + public fun contains (Ljava/lang/Object;)Z + public fun getSize ()I + public fun isEmpty ()Z + public fun iterator ()Ljava/util/Iterator; + public fun remove (Ljava/lang/Object;)Z +} + public final class kotlin/collections/unsigned/UArraysKt { public static final fun asList--ajY-9A ([I)Ljava/util/List; public static final fun asList-GBYM_sE ([B)Ljava/util/List;