diff --git a/libraries/stdlib/api/js-v1/kotlin.collections.kt b/libraries/stdlib/api/js-v1/kotlin.collections.kt index d6dabf51c6b..a8c35f86847 100644 --- a/libraries/stdlib/api/js-v1/kotlin.collections.kt +++ b/libraries/stdlib/api/js-v1/kotlin.collections.kt @@ -10519,6 +10519,10 @@ public final class ArrayDeque : kotlin.collections.AbstractMutableList { public open override fun retainAll(elements: kotlin.collections.Collection): kotlin.Boolean public open override operator fun set(index: kotlin.Int, element: E): E + + protected open override fun toArray(): kotlin.Array + + protected open override fun toArray(array: kotlin.Array): kotlin.Array } public open class ArrayList : kotlin.collections.AbstractMutableList, kotlin.collections.MutableList, kotlin.collections.RandomAccess { @@ -10558,6 +10562,8 @@ public open class ArrayList : kotlin.collections.AbstractMutableList, kotl protected open override fun toArray(): kotlin.Array + protected open override fun toArray(array: kotlin.Array): kotlin.Array + public open override fun toString(): kotlin.String public final fun trimToSize(): kotlin.Unit diff --git a/libraries/stdlib/api/js/kotlin.collections.kt b/libraries/stdlib/api/js/kotlin.collections.kt index 40691210af3..a2e33caba22 100644 --- a/libraries/stdlib/api/js/kotlin.collections.kt +++ b/libraries/stdlib/api/js/kotlin.collections.kt @@ -10483,6 +10483,10 @@ public final class ArrayDeque : kotlin.collections.AbstractMutableList { public open override fun retainAll(elements: kotlin.collections.Collection): kotlin.Boolean public open override operator fun set(index: kotlin.Int, element: E): E + + protected open override fun toArray(): kotlin.Array + + protected open override fun toArray(array: kotlin.Array): kotlin.Array } public open class ArrayList : kotlin.collections.AbstractMutableList, kotlin.collections.MutableList, kotlin.collections.RandomAccess { @@ -10522,6 +10526,8 @@ public open class ArrayList : kotlin.collections.AbstractMutableList, kotl protected open override fun toArray(): kotlin.Array + protected open override fun toArray(array: kotlin.Array): kotlin.Array + public open override fun toString(): kotlin.String public final fun trimToSize(): kotlin.Unit diff --git a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt index 885d46ca700..22fb104b3ac 100644 --- a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt @@ -133,7 +133,25 @@ public actual open class ArrayList internal constructor(private var array: Ar actual override fun lastIndexOf(element: E): Int = array.lastIndexOf(element) override fun toString() = arrayToString(array) - override fun toArray(): Array = js("[]").slice.call(array) + + @Suppress("UNCHECKED_CAST") + override fun toArray(array: Array): Array { + if (array.size < size) { + return toArray() as Array + } + + (this.array as Array).copyInto(array) + + if (array.size > size) { + array[size] = null as T // null-terminate + } + + return array + } + + override fun toArray(): Array { + return js("[]").slice.call(array) + } internal override fun checkIsMutable() { diff --git a/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt b/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt index cf4a27cf413..ffa1fe6e62e 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/builders/ListBuilder.kt @@ -130,13 +130,25 @@ internal class ListBuilder private constructor( return ListBuilder(array, offset + fromIndex, toIndex - fromIndex, isReadOnly, this, root ?: 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 toArray(destination: Array): Array { + if (destination.size < length) { + return java.util.Arrays.copyOfRange(array, offset, offset + length, destination.javaClass) } + + @Suppress("UNCHECKED_CAST") + (array as Array).copyInto(destination, 0, startIndex = offset, endIndex = offset + length) + + if (destination.size > length) { + @Suppress("UNCHECKED_CAST") + destination[length] = null as T // null-terminate + } + + return destination + } + + override fun toArray(): Array { + @Suppress("UNCHECKED_CAST") + return array.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array } override fun equals(other: Any?): Boolean { @@ -154,6 +166,14 @@ internal class ListBuilder private constructor( // ---------------------------- private ---------------------------- + 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) + } + } + private fun checkIsMutable() { if (isReadOnly || root != null && root.isReadOnly) throw UnsupportedOperationException() } diff --git a/libraries/stdlib/jvm/test/collections/ListBuilderTest.kt b/libraries/stdlib/jvm/test/collections/ListBuilderTest.kt new file mode 100644 index 00000000000..8c3b024fa5d --- /dev/null +++ b/libraries/stdlib/jvm/test/collections/ListBuilderTest.kt @@ -0,0 +1,60 @@ +/* + * 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 test.collections + +import kotlin.collections.builders.* +import kotlin.test.Test +import kotlin.test.assertSame +import kotlin.test.assertTrue + +@Suppress("INVISIBLE_MEMBER") +class ListBuilderTest { + @Test + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") + fun toArray() { + val numberOfElements = 5 + val expected = ArrayList().apply { addAll(0 until numberOfElements) } + val builder = ListBuilder().apply { addAll(0 until numberOfElements) } + + fun testToArray(getter: List.() -> Array) { + assertTrue(expected.getter() contentEquals builder.getter()) + assertTrue(expected.subList(2, 4).getter() contentEquals builder.subList(2, 4).getter()) + } + + testToArray { (this as java.util.Collection).toArray() } + } + + @Test + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") + fun toArrayDestination() { + val numberOfElements = 5 + val expected = ArrayList().apply { addAll(0 until numberOfElements) } + val builder = ListBuilder().apply { addAll(0 until numberOfElements) } + + fun testToArray(destSize: Int, getter: List.(Array) -> Array) { + repeat(2) { index -> + val expectedDest = Array(destSize) { -it - 1 } + val builderDest = Array(destSize) { -it - 1 } + + val takeSubList = index == 1 + val expectedResult = (if (!takeSubList) expected else expected.subList(2, 4)).getter(expectedDest) + val builderResult = (if (!takeSubList) builder else builder.subList(2, 4)).getter(builderDest) + + if (expectedResult.size <= expectedDest.size) { + assertSame(expectedDest, expectedResult) + assertSame(builderDest, builderResult) + } + assertTrue(expectedResult contentEquals builderResult) + } + } + + testToArray(0) { (this as java.util.Collection).toArray(it) } + testToArray(numberOfElements - 1) { (this as java.util.Collection).toArray(it) } + testToArray(numberOfElements) { (this as java.util.Collection).toArray(it) } + testToArray(numberOfElements + 1) { (this as java.util.Collection).toArray(it) } + testToArray(numberOfElements + 2) { (this as java.util.Collection).toArray(it) } + } +} \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt index 08583d751d3..d41e585b923 100644 --- a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt +++ b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt @@ -529,6 +529,35 @@ public class ArrayDeque : AbstractMutableList { size = 0 } + @Suppress("NOTHING_TO_OVERRIDE") + override fun toArray(array: Array): Array { + @Suppress("UNCHECKED_CAST") + val dest = (if (array.size >= size) array else arrayOfNulls(array, size)) as Array + + val tail = internalIndex(size) + if (head < tail) { + elementData.copyInto(dest, startIndex = head, endIndex = tail) + } else if (isNotEmpty()) { + elementData.copyInto(dest, destinationOffset = 0, startIndex = head, endIndex = elementData.size) + elementData.copyInto(dest, destinationOffset = elementData.size - head, startIndex = 0, endIndex = tail) + } + if (dest.size > size) { + dest[size] = null // null-terminate + } + + @Suppress("UNCHECKED_CAST") + return dest as Array + } + + @Suppress("NOTHING_TO_OVERRIDE") + override fun toArray(): Array { + return toArray(arrayOfNulls(size)) + } + + // for testing + internal fun testToArray(array: Array): Array = toArray(array) + internal fun testToArray(): Array = toArray() + internal companion object { private val emptyElementData = emptyArray() private const val maxArraySize = Int.MAX_VALUE - 8 @@ -548,20 +577,7 @@ public class ArrayDeque : AbstractMutableList { // For testing only internal fun internalStructure(structure: (head: Int, elements: Array) -> Unit) { val tail = internalIndex(size) - - if (isEmpty()) { - structure(head, emptyArray()) - return - } - - val elements = arrayOfNulls(size) - if (head < tail) { - elementData.copyInto(elements, startIndex = head, endIndex = tail) - structure(head, elements) - } else { - elementData.copyInto(elements, startIndex = head) - elementData.copyInto(elements, elementData.size - head, startIndex = 0, endIndex = tail) - structure(head - elementData.size, elements) - } + val head = if (isEmpty() || head < tail) head else head - elementData.size + structure(head, toArray()) } } \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArrayDequeTest.kt b/libraries/stdlib/test/collections/ArrayDequeTest.kt index 8e7053966c6..665a2852254 100644 --- a/libraries/stdlib/test/collections/ArrayDequeTest.kt +++ b/libraries/stdlib/test/collections/ArrayDequeTest.kt @@ -8,7 +8,6 @@ package test.collections import test.collections.behaviors.iteratorBehavior import test.collections.behaviors.listIteratorBehavior import test.collections.behaviors.listIteratorProperties -import test.collections.compare import kotlin.random.Random import kotlin.random.nextInt import kotlin.test.* @@ -647,4 +646,48 @@ class ArrayDequeTest { assertEquals(Int.MAX_VALUE, ArrayDeque.newCapacity(oldCapacity, minCapacity)) } } + + @Suppress("INVISIBLE_MEMBER") + @Test + fun toArray() { + val deque = ArrayDeque() + + // empty + assertTrue(deque.testToArray().isEmpty()) + + fun testContentEquals(expected: Array) { + assertTrue(expected contentEquals deque.testToArray()) + assertTrue(expected contentEquals deque.testToArray(emptyArray())) + + val dest = Array(expected.size + 2) { it + 100 } + + @Suppress("UNCHECKED_CAST") + val nullTerminatedExpected = (expected as Array) + null + (expected.size + 101) + val actual = deque.testToArray(dest) + assertTrue( + nullTerminatedExpected contentEquals actual, + message = "Expected: ${nullTerminatedExpected.contentToString()}, Actual: ${actual.contentToString()}" + ) + } + + // head < tail + deque.addAll(listOf(0, 1, 2, 3)) + deque.internalStructure { head, _ -> assertEquals(0, head) } + testContentEquals(arrayOf(0, 1, 2, 3)) + deque.removeFirst() + deque.internalStructure { head, _ -> assertEquals(1, head) } + testContentEquals(arrayOf(1, 2, 3)) + + // head > tail + deque.addFirst(-1) + deque.addFirst(-2) + deque.addFirst(-3) + deque.internalStructure { head, _ -> assertEquals(-2, head) } // deque min capacity is 10 + testContentEquals(arrayOf(-3, -2, -1, 1, 2, 3)) + + // head == tail + deque.addAll(listOf(4, 5, 6, 7)) + deque.internalStructure { head, _ -> assertEquals(-2, head) } // deque min capacity is 10 + testContentEquals(arrayOf(-3, -2, -1, 1, 2, 3, 4, 5, 6, 7)) + } } \ No newline at end of file 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 4dfefc54ef1..36eeea72649 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 @@ -727,6 +727,8 @@ public final class kotlin/collections/ArrayDeque : kotlin/collections/AbstractMu public final fun removeLastOrNull ()Ljava/lang/Object; public fun retainAll (Ljava/util/Collection;)Z public fun set (ILjava/lang/Object;)Ljava/lang/Object; + public fun toArray ()[Ljava/lang/Object; + public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; } public final class kotlin/collections/ArraysKt {