Split ArraySet into separate Jvm and Native implementations for toArray compatibility
It is required to provided a separate Java implementation of `.toArray` for
ArraySet for compatibility, due to:
1. Ignoring nullity of the generic type T to maintain the behavior of setting the n+1'th item to null
2. Allowing .toArray(array: Array<T>) to be called without making T a reified type
2. Delegate the array copy implementation to System.arraycopy which is implemented natively on JVM
Instead of providing a less-performance version for native / non-jvm platforms,
it was removed in this CL because it was always only ever needed for
compatibility. Users should instead prefer to follow the Kotlin stdlib standard
of using `.toTypedArray` or one of its primitive relatives, e.g., `.toIntArray`.
Relnote: "`ArraySet.toArray` is now only available on JVM as it was always only
included for compatibility with Java. Kotlin users should generaly use the
stdlib's `.toTypedArray` helpers instead, which provides the exact same
functionality with added type safety."
Test: :collection:collection:test
Change-Id: I2c59be76341e284936d6da16a3feb582d1f66c51
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ArraySet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ArraySet.kt
index 6d7a336..0c7ac8e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ArraySet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ArraySet.kt
@@ -59,132 +59,37 @@
* @constructor Creates a new empty ArraySet. The default capacity of an array map is 0, and
* will grow once items are added to it.
*/
-public class ArraySet<E> @JvmOverloads constructor(capacity: Int = 0) :
- MutableCollection<E>, MutableSet<E> {
+public expect class ArraySet<E> @JvmOverloads constructor(
+ capacity: Int = 0
+) : MutableCollection<E>, MutableSet<E> {
- private var hashes: IntArray = EMPTY_INTS
- private var array: Array<Any?> = EMPTY_OBJECTS
+ internal var hashes: IntArray
+ internal var array: Array<Any?>
- private var _size = 0
-
+ internal var _size: Int
override val size: Int
- get() = _size
/**
* Create a new ArraySet with the mappings from the given ArraySet.
*/
- public constructor(set: ArraySet<out E>?) : this(capacity = 0) {
- if (set != null) {
- addAll(set)
- }
- }
+ public constructor(set: ArraySet<out E>?)
/**
* Create a new ArraySet with the mappings from the given [Collection].
*/
- public constructor(set: Collection<E>?) : this(capacity = 0) {
- if (set != null) {
- addAll(set)
- }
- }
+ public constructor(set: Collection<E>?)
/**
* Create a new ArraySet with items from the given array.
*/
- public constructor(array: Array<out E>?) : this(capacity = 0) {
- if (array != null) {
- for (value in array) {
- add(value)
- }
- }
- }
-
- init {
- if (capacity > 0) {
- allocArrays(capacity)
- }
- }
-
- private fun binarySearchInternal(hash: Int): Int =
- try {
- binarySearch(hashes, _size, hash)
- } catch (e: IndexOutOfBoundsException) {
- throw ConcurrentModificationException()
- }
-
- private fun indexOf(key: Any?, hash: Int): Int {
- val n = _size
-
- // Important fast case: if nothing is in here, nothing to look for.
- if (n == 0) {
- return -1
- }
- val index = binarySearchInternal(hash)
-
- // If the hash code wasn't found, then we have no entry for this key.
- if (index < 0) {
- return index
- }
-
- // If the key at the returned index matches, that's what we want.
- if (key == array[index]) {
- return index
- }
-
- // Search for a matching key after the index.
- var end = index + 1
- while (end < n && hashes[end] == hash) {
- if (key == array[end]) {
- return end
- }
- end++
- }
-
- // Search for a matching key before the index.
- var i = index - 1
- while (i >= 0 && hashes[i] == hash) {
- if (key == array[i]) {
- return i
- }
- i--
- }
-
- // Key not found -- return negative value indicating where a
- // new entry for this key should go. We use the end of the
- // hash chain to reduce the number of array entries that will
- // need to be copied when inserting.
- return end.inv()
- }
-
- private fun indexOfNull(): Int = indexOf(key = null, hash = 0)
-
- private fun allocArrays(size: Int) {
- hashes = IntArray(size)
- array = arrayOfNulls(size)
- }
-
- private inline fun printlnIfDebug(message: () -> String) {
- if (DEBUG) {
- println(message())
- }
- }
+ public constructor(array: Array<out E>?)
/**
* Make the array map empty. All storage is released.
*
* @throws ConcurrentModificationException if concurrent modifications detected.
*/
- override fun clear() {
- if (_size != 0) {
- hashes = EMPTY_INTS
- array = EMPTY_OBJECTS
- _size = 0
- }
- @Suppress("KotlinConstantConditions")
- if (_size != 0) {
- throw ConcurrentModificationException()
- }
- }
+ override fun clear()
/**
* Ensure the array map can hold at least [minimumCapacity]
@@ -192,21 +97,7 @@
*
* @throws ConcurrentModificationException if concurrent modifications detected.
*/
- public fun ensureCapacity(minimumCapacity: Int) {
- val oSize: Int = _size
- if (hashes.size < minimumCapacity) {
- val ohashes = hashes
- val oarray = array
- allocArrays(minimumCapacity)
- if (_size > 0) {
- ohashes.copyInto(destination = hashes, endIndex = _size)
- oarray.copyInto(destination = array, endIndex = _size)
- }
- }
- if (_size != oSize) {
- throw ConcurrentModificationException()
- }
- }
+ public fun ensureCapacity(minimumCapacity: Int)
/**
* Check whether a value exists in the set.
@@ -214,8 +105,7 @@
* @param element The value to search for.
* @return Returns true if the value exists, else false.
*/
- override operator fun contains(element: E): Boolean =
- indexOf(element) >= 0
+ override operator fun contains(element: E): Boolean
/**
* Returns the index of a value in the set.
@@ -223,23 +113,20 @@
* @param key The value to search for.
* @return Returns the index of the value if it exists, else a negative integer.
*/
- public fun indexOf(key: Any?): Int =
- if (key == null) indexOfNull() else indexOf(key = key, hash = key.hashCode())
+ public fun indexOf(key: Any?): Int
/**
* Return the value at the given index in the array.
+ *
* @param index The desired index, must be between 0 and [size]-1.
* @return Returns the value stored at the given index.
*/
- @Suppress("UNCHECKED_CAST")
- public fun valueAt(index: Int): E =
- array[index] as E
+ public fun valueAt(index: Int): E
/**
- * Return true if the array map contains no items.
+ * Return `true` if the array map contains no items.
*/
- override fun isEmpty(): Boolean =
- _size <= 0
+ override fun isEmpty(): Boolean
/**
* Adds the specified object to this set. The set is not modified if it
@@ -249,98 +136,15 @@
* @return `true` if this set is modified, `false` otherwise.
* @throws ConcurrentModificationException if concurrent modifications detected.
*/
- override fun add(element: E): Boolean {
- val oSize = _size
- val hash: Int
- var index: Int
- if (element == null) {
- hash = 0
- index = indexOfNull()
- } else {
- hash = element.hashCode()
- index = indexOf(element, hash)
- }
-
- if (index >= 0) {
- return false
- }
-
- index = index.inv()
- if (oSize >= hashes.size) {
- val n =
- when {
- oSize >= BASE_SIZE * 2 -> oSize + (oSize shr 1)
- oSize >= BASE_SIZE -> BASE_SIZE * 2
- else -> BASE_SIZE
- }
-
- printlnIfDebug { "$TAG add: grow from ${hashes.size} to $n" }
-
- val ohashes = hashes
- val oarray = array
- allocArrays(n)
-
- if (oSize != _size) {
- throw ConcurrentModificationException()
- }
-
- if (hashes.isNotEmpty()) {
- printlnIfDebug { "$TAG add: copy 0-$oSize to 0" }
- ohashes.copyInto(destination = hashes, endIndex = ohashes.size)
- oarray.copyInto(destination = array, endIndex = oarray.size)
- }
- }
-
- if (index < oSize) {
- printlnIfDebug { "$TAG add: move $index-${oSize - index} to ${index + 1}" }
-
- hashes.copyInto(
- destination = hashes,
- destinationOffset = index + 1,
- startIndex = index,
- endIndex = oSize
- )
- array.copyInto(
- destination = array,
- destinationOffset = index + 1,
- startIndex = index,
- endIndex = oSize
- )
- }
-
- if (oSize != _size || index >= hashes.size) {
- throw ConcurrentModificationException()
- }
-
- hashes[index] = hash
- array[index] = element
- _size++
- return true
- }
+ override fun add(element: E): Boolean
/**
* Perform a [add] of all values in [array]
+ *
* @param array The array whose contents are to be retrieved.
* @throws ConcurrentModificationException if concurrent modifications detected.
*/
- public fun addAll(array: ArraySet<out E>) {
- val n = array._size
- ensureCapacity(_size + n)
- if (_size == 0) {
- if (n > 0) {
- array.hashes.copyInto(destination = hashes, endIndex = n)
- array.array.copyInto(destination = this.array, endIndex = n)
- if (0 != _size) {
- throw ConcurrentModificationException()
- }
- _size = n
- }
- } else {
- for (i in 0 until n) {
- add(array.valueAt(i))
- }
- }
- }
+ public fun addAll(array: ArraySet<out E>)
/**
* Removes the specified object from this set.
@@ -348,125 +152,23 @@
* @param element the object to remove.
* @return `true` if this set was modified, `false` otherwise.
*/
- override fun remove(element: E): Boolean {
- val index = indexOf(element)
- if (index >= 0) {
- removeAt(index)
- return true
- }
- return false
- }
+ override fun remove(element: E): Boolean
/**
* Remove the key/value mapping at the given index.
+ *
* @param index The desired index, must be between 0 and [size]-1.
* @return Returns the value that was stored at this index.
* @throws ConcurrentModificationException if concurrent modifications detected.
*/
- public fun removeAt(index: Int): E {
- val oSize = _size
- val old = array[index]
- if (oSize <= 1) {
- // Now empty.
- printlnIfDebug { "$TAG remove: shrink from ${hashes.size} to 0" }
- clear()
- } else {
- val nSize = oSize - 1
- if (hashes.size > (BASE_SIZE * 2) && (_size < hashes.size / 3)) {
- // Shrunk enough to reduce size of arrays. We don't allow it to
- // shrink smaller than (BASE_SIZE*2) to avoid flapping between
- // that and BASE_SIZE.
- val n = if (_size > BASE_SIZE * 2) _size + (_size shr 1) else BASE_SIZE * 2
- printlnIfDebug { "$TAG remove: shrink from ${hashes.size} to $n" }
- val ohashes = hashes
- val oarray = array
- allocArrays(n)
- if (index > 0) {
- printlnIfDebug { "$TAG remove: copy from 0-$index to 0" }
- ohashes.copyInto(destination = hashes, endIndex = index)
- oarray.copyInto(destination = array, endIndex = index)
- }
- if (index < nSize) {
- printlnIfDebug { "$TAG remove: copy from ${index + 1}-$nSize to $index" }
- ohashes.copyInto(
- destination = hashes,
- destinationOffset = index,
- startIndex = index + 1,
- endIndex = nSize + 1
- )
- oarray.copyInto(
- destination = array,
- destinationOffset = index,
- startIndex = index + 1,
- endIndex = nSize + 1
- )
- }
- } else {
- if (index < nSize) {
- printlnIfDebug { "$TAG remove: move ${index + 1}-$nSize to $index" }
- hashes.copyInto(
- destination = hashes,
- destinationOffset = index,
- startIndex = index + 1,
- endIndex = nSize + 1
- )
- array.copyInto(
- destination = array,
- destinationOffset = index,
- startIndex = index + 1,
- endIndex = nSize + 1
- )
- }
- array[nSize] = null
- }
- if (oSize != _size) {
- throw ConcurrentModificationException()
- }
- _size = nSize
- }
- @Suppress("UNCHECKED_CAST")
- return old as E
- }
+ public fun removeAt(index: Int): E
/**
* Perform a [remove] of all values in [array]
+ *
* @param array The array whose contents are to be removed.
*/
- public fun removeAll(array: ArraySet<out E>): Boolean {
- // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
- // pass, use the property that the sets are sorted by hash to make this linear passes
- // (except for hash collisions, which means worst case still n*m), then do one
- // collection pass into a new array. This avoids binary searches and excessive memcpy.
- val n = array._size
-
- // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
- // the single results, compare size before and after.
- val originalSize = _size
- for (i in 0 until n) {
- remove(array.valueAt(i))
- }
- return originalSize != _size
- }
-
- @Suppress("ArrayReturn")
- public fun toArray(): Array<Any?> {
- return array.copyOfRange(fromIndex = 0, toIndex = _size)
- }
-
- @Suppress("ArrayReturn")
- public fun <T> toArray(array: Array<T>): Array<T> {
- return if (array.size < _size) {
- @Suppress("UNCHECKED_CAST")
- this.array.copyOfRange(fromIndex = 0, toIndex = _size) as Array<T>
- } else {
- @Suppress("UNCHECKED_CAST")
- this.array.copyInto(array as Array<Any?>, 0, 0, _size)
- if (array.size > _size) {
- array[_size] = null
- }
- array
- }
- }
+ public fun removeAll(array: ArraySet<out E>): Boolean
/**
* This implementation returns false if the object is not a set, or
@@ -477,70 +179,19 @@
*
* @see Any.equals
*/
- override fun equals(other: Any?): Boolean {
- if (this === other) {
- return true
- }
- if (other is Set<*>) {
- if (size != other.size) {
- return false
- }
- try {
- for (i in 0 until _size) {
- val mine = valueAt(i)
- if (!other.contains(mine)) {
- return false
- }
- }
- } catch (ignored: NullPointerException) {
- return false
- } catch (ignored: ClassCastException) {
- return false
- }
- return true
- }
- return false
- }
+ override fun equals(other: Any?): Boolean
/**
* @see Any.hashCode
*/
- override fun hashCode(): Int {
- val hashes = hashes
- val s = _size
- var result = 0
- for (i in 0 until s) {
- result += hashes[i]
- }
- return result
- }
+ override fun hashCode(): Int
/**
* This implementation composes a string by iterating over its values. If
* this set contains itself as a value, the string "(this Set)"
* will appear in its place.
*/
- override fun toString(): String {
- if (isEmpty()) {
- return "{}"
- }
-
- return buildString(capacity = _size * 14) {
- append('{')
- for (i in 0 until _size) {
- if (i > 0) {
- append(", ")
- }
- val value = valueAt(i)
- if (value !== this@ArraySet) {
- append(value)
- } else {
- append("(this Set)")
- }
- }
- append('}')
- }
- }
+ override fun toString(): String
/**
* Return a [MutableIterator] over all values in the set.
@@ -548,84 +199,430 @@
* **Note:** this is a less efficient way to access the array contents compared to
* looping from 0 until [size] and calling [valueAt].
*/
- override fun iterator(): MutableIterator<E> =
- ElementIterator()
-
- private inner class ElementIterator : IndexBasedArrayIterator<E>(_size) {
- override fun elementAt(index: Int): E =
- valueAt(index)
-
- override fun removeAt(index: Int) {
- [email protected](index)
- }
- }
+ override fun iterator(): MutableIterator<E>
/**
* Determine if the array set contains all of the values in the given collection.
+ *
* @param elements The collection whose contents are to be checked against.
* @return Returns true if this array set contains a value for every entry
* in [elements] else returns false.
*/
- override fun containsAll(elements: Collection<E>): Boolean {
- for (item in elements) {
- if (!contains(item)) {
- return false
- }
- }
- return true
- }
+ override fun containsAll(elements: Collection<E>): Boolean
/**
* Perform an [add] of all values in [elements]
+ *
* @param elements The collection whose contents are to be retrieved.
*/
- override fun addAll(elements: Collection<E>): Boolean {
- ensureCapacity(_size + elements.size)
- var added = false
- for (value in elements) {
- added = add(value) or added
- }
- return added
- }
+ override fun addAll(elements: Collection<E>): Boolean
/**
* Remove all values in the array set that exist in the given collection.
+ *
* @param elements The collection whose contents are to be used to remove values.
* @return Returns true if any values were removed from the array set, else false.
*/
- override fun removeAll(elements: Collection<E>): Boolean {
- var removed = false
- for (value in elements) {
- removed = removed or remove(value)
- }
- return removed
- }
+ override fun removeAll(elements: Collection<E>): Boolean
/**
* Remove all values in the array set that do **not** exist in the given collection.
+ *
* @param elements The collection whose contents are to be used to determine which
* values to keep.
* @return Returns true if any values were removed from the array set, else false.
*/
- override fun retainAll(elements: Collection<E>): Boolean {
- var removed = false
- for (i in _size - 1 downTo 0) {
- if (array[i] !in elements) {
- removeAt(i)
- removed = true
+ override fun retainAll(elements: Collection<E>): Boolean
+}
+
+/**
+ * The minimum amount by which the capacity of a ArraySet will increase.
+ * This is tuned to be relatively space-efficient.
+ */
+internal const val ARRAY_SET_BASE_SIZE = 4
+
+internal fun <E> ArraySet<E>.binarySearchInternal(hash: Int): Int =
+ try {
+ binarySearch(hashes, _size, hash)
+ } catch (e: IndexOutOfBoundsException) {
+ throw ConcurrentModificationException()
+ }
+
+internal fun <E> ArraySet<E>.indexOf(key: Any?, hash: Int): Int {
+ val n = _size
+
+ // Important fast case: if nothing is in here, nothing to look for.
+ if (n == 0) {
+ return -1
+ }
+ val index = binarySearchInternal(hash)
+
+ // If the hash code wasn't found, then we have no entry for this key.
+ if (index < 0) {
+ return index
+ }
+
+ // If the key at the returned index matches, that's what we want.
+ if (key == array[index]) {
+ return index
+ }
+
+ // Search for a matching key after the index.
+ var end = index + 1
+ while (end < n && hashes[end] == hash) {
+ if (key == array[end]) {
+ return end
+ }
+ end++
+ }
+
+ // Search for a matching key before the index.
+ var i = index - 1
+ while (i >= 0 && hashes[i] == hash) {
+ if (key == array[i]) {
+ return i
+ }
+ i--
+ }
+
+ // Key not found -- return negative value indicating where a
+ // new entry for this key should go. We use the end of the
+ // hash chain to reduce the number of array entries that will
+ // need to be copied when inserting.
+ return end.inv()
+}
+
+internal fun <E> ArraySet<E>.indexOfNull(): Int = indexOf(key = null, hash = 0)
+
+internal fun <E> ArraySet<E>.allocArrays(size: Int) {
+ hashes = IntArray(size)
+ array = arrayOfNulls(size)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.clearInternal() {
+ if (_size != 0) {
+ hashes = EMPTY_INTS
+ array = EMPTY_OBJECTS
+ _size = 0
+ }
+ @Suppress("KotlinConstantConditions")
+ if (_size != 0) {
+ throw ConcurrentModificationException()
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.ensureCapacityInternal(minimumCapacity: Int) {
+ val oSize: Int = _size
+ if (hashes.size < minimumCapacity) {
+ val ohashes = hashes
+ val oarray = array
+ allocArrays(minimumCapacity)
+ if (_size > 0) {
+ ohashes.copyInto(destination = hashes, endIndex = _size)
+ oarray.copyInto(destination = array, endIndex = _size)
+ }
+ }
+ if (_size != oSize) {
+ throw ConcurrentModificationException()
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.containsInternal(element: E): Boolean {
+ return indexOf(element) >= 0
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.indexOfInternal(key: Any?): Int {
+ return if (key == null) indexOfNull() else indexOf(key = key, hash = key.hashCode())
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.valueAtInternal(index: Int): E {
+ @Suppress("UNCHECKED_CAST")
+ return array[index] as E
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.isEmptyInternal(): Boolean {
+ return _size <= 0
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.addInternal(element: E): Boolean {
+ val oSize = _size
+ val hash: Int
+ var index: Int
+ if (element == null) {
+ hash = 0
+ index = indexOfNull()
+ } else {
+ hash = element.hashCode()
+ index = indexOf(element, hash)
+ }
+
+ if (index >= 0) {
+ return false
+ }
+
+ index = index.inv()
+ if (oSize >= hashes.size) {
+ val n =
+ when {
+ oSize >= ARRAY_SET_BASE_SIZE * 2 -> oSize + (oSize shr 1)
+ oSize >= ARRAY_SET_BASE_SIZE -> ARRAY_SET_BASE_SIZE * 2
+ else -> ARRAY_SET_BASE_SIZE
+ }
+
+ val ohashes = hashes
+ val oarray = array
+ allocArrays(n)
+
+ if (oSize != _size) {
+ throw ConcurrentModificationException()
+ }
+
+ if (hashes.isNotEmpty()) {
+ ohashes.copyInto(destination = hashes, endIndex = ohashes.size)
+ oarray.copyInto(destination = array, endIndex = oarray.size)
+ }
+ }
+
+ if (index < oSize) {
+ hashes.copyInto(
+ destination = hashes,
+ destinationOffset = index + 1,
+ startIndex = index,
+ endIndex = oSize
+ )
+ array.copyInto(
+ destination = array,
+ destinationOffset = index + 1,
+ startIndex = index,
+ endIndex = oSize
+ )
+ }
+
+ if (oSize != _size || index >= hashes.size) {
+ throw ConcurrentModificationException()
+ }
+
+ hashes[index] = hash
+ array[index] = element
+ _size++
+ return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.addAllInternal(array: ArraySet<out E>) {
+ val n = array._size
+ ensureCapacity(_size + n)
+ if (_size == 0) {
+ if (n > 0) {
+ array.hashes.copyInto(destination = hashes, endIndex = n)
+ array.array.copyInto(destination = this.array, endIndex = n)
+ if (0 != _size) {
+ throw ConcurrentModificationException()
+ }
+ _size = n
+ }
+ } else {
+ for (i in 0 until n) {
+ add(array.valueAt(i))
+ }
+ }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.removeInternal(element: E): Boolean {
+ val index = indexOf(element)
+ if (index >= 0) {
+ removeAt(index)
+ return true
+ }
+ return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.removeAtInternal(index: Int): E {
+ val oSize = _size
+ val old = array[index]
+ if (oSize <= 1) {
+ // Now empty.
+ clear()
+ } else {
+ val nSize = oSize - 1
+ if (hashes.size > (ARRAY_SET_BASE_SIZE * 2) && (_size < hashes.size / 3)) {
+ // Shrunk enough to reduce size of arrays. We don't allow it to
+ // shrink smaller than (ARRAY_SET_BASE_SIZE*2) to avoid flapping between
+ // that and ARRAY_SET_BASE_SIZE.
+ val n = when {
+ _size > ARRAY_SET_BASE_SIZE * 2 -> _size + (_size shr 1)
+ else -> ARRAY_SET_BASE_SIZE * 2
+ }
+ val ohashes = hashes
+ val oarray = array
+ allocArrays(n)
+ if (index > 0) {
+ ohashes.copyInto(destination = hashes, endIndex = index)
+ oarray.copyInto(destination = array, endIndex = index)
+ }
+ if (index < nSize) {
+ ohashes.copyInto(
+ destination = hashes,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = nSize + 1
+ )
+ oarray.copyInto(
+ destination = array,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = nSize + 1
+ )
+ }
+ } else {
+ if (index < nSize) {
+ hashes.copyInto(
+ destination = hashes,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = nSize + 1
+ )
+ array.copyInto(
+ destination = array,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = nSize + 1
+ )
+ }
+ array[nSize] = null
+ }
+ if (oSize != _size) {
+ throw ConcurrentModificationException()
+ }
+ _size = nSize
+ }
+ @Suppress("UNCHECKED_CAST")
+ return old as E
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.removeAllInternal(array: ArraySet<out E>): Boolean {
+ // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
+ // pass, use the property that the sets are sorted by hash to make this linear passes
+ // (except for hash collisions, which means worst case still n*m), then do one
+ // collection pass into a new array. This avoids binary searches and excessive memcpy.
+ val n = array._size
+
+ // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
+ // the single results, compare size before and after.
+ val originalSize = _size
+ for (i in 0 until n) {
+ remove(array.valueAt(i))
+ }
+ return originalSize != _size
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.equalsInternal(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+ if (other is Set<*>) {
+ if (size != other.size) {
+ return false
+ }
+ try {
+ for (i in 0 until _size) {
+ val mine = valueAt(i)
+ if (!other.contains(mine)) {
+ return false
+ }
+ }
+ } catch (ignored: NullPointerException) {
+ return false
+ } catch (ignored: ClassCastException) {
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.hashCodeInternal(): Int {
+ val hashes = hashes
+ val s = _size
+ var result = 0
+ for (i in 0 until s) {
+ result += hashes[i]
+ }
+ return result
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.toStringInternal(): String {
+ if (isEmpty()) {
+ return "{}"
+ }
+
+ return buildString(capacity = _size * 14) {
+ append('{')
+ for (i in 0 until _size) {
+ if (i > 0) {
+ append(", ")
+ }
+ val value = valueAt(i)
+ if (value !== this@toStringInternal) {
+ append(value)
+ } else {
+ append("(this Set)")
}
}
- return removed
+ append('}')
}
+}
- private companion object {
- private const val DEBUG = false
- private const val TAG = "ArraySet"
-
- /**
- * The minimum amount by which the capacity of a ArraySet will increase.
- * This is tuned to be relatively space-efficient.
- */
- private const val BASE_SIZE = 4
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.containsAllInternal(elements: Collection<E>): Boolean {
+ for (item in elements) {
+ if (!contains(item)) {
+ return false
+ }
}
+ return true
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.addAllInternal(elements: Collection<E>): Boolean {
+ ensureCapacity(_size + elements.size)
+ var added = false
+ for (value in elements) {
+ added = add(value) or added
+ }
+ return added
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.removeAllInternal(elements: Collection<E>): Boolean {
+ var removed = false
+ for (value in elements) {
+ removed = removed or remove(value)
+ }
+ return removed
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <E> ArraySet<E>.retainAllInternal(elements: Collection<E>): Boolean {
+ var removed = false
+ for (i in _size - 1 downTo 0) {
+ if (array[i] !in elements) {
+ removeAt(i)
+ removed = true
+ }
+ }
+ return removed
}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ArraySetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ArraySetTest.kt
index 365694f..257a876 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ArraySetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ArraySetTest.kt
@@ -35,7 +35,6 @@
import kotlinx.coroutines.runBlocking
internal class ArraySetTest {
-
private val set = ArraySet<String>()
/**
diff --git a/collection/collection/src/jvmMain/java/androidx/collection/ArraySetJvmUtil.java b/collection/collection/src/jvmMain/java/androidx/collection/ArraySetJvmUtil.java
new file mode 100644
index 0000000..13642b6
--- /dev/null
+++ b/collection/collection/src/jvmMain/java/androidx/collection/ArraySetJvmUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection;
+
+import java.lang.reflect.Array;
+
+class ArraySetJvmUtil {
+ private ArraySetJvmUtil() {
+ }
+
+ // Necessary to implement in Java to allow allocating a typed array without a callback for
+ // initialization. We also need to ignore the nullity of the type in order to null out the
+ // (n+1)'th item for behavior compatibility.
+ @SuppressWarnings("unchecked")
+ static <T> T[] resizeForToArray(T[] destination, int size) {
+ if (destination.length < size) {
+ return (T[]) Array.newInstance(destination.getClass().getComponentType(), size);
+ } else {
+ if (destination.length > size) {
+ destination[size] = null;
+ }
+ return destination;
+ }
+ }
+}
diff --git a/collection/collection/src/jvmMain/kotlin/androidx/collection/ArraySet.jvm.kt b/collection/collection/src/jvmMain/kotlin/androidx/collection/ArraySet.jvm.kt
new file mode 100644
index 0000000..e804ff1
--- /dev/null
+++ b/collection/collection/src/jvmMain/kotlin/androidx/collection/ArraySet.jvm.kt
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_INTS
+import androidx.collection.internal.EMPTY_OBJECTS
+
+/**
+ * ArraySet is a generic set data structure that is designed to be more memory efficient than a
+ * traditional [HashSet]. The design is very similar to
+ * [ArrayMap], with all of the caveats described there. This implementation is
+ * separate from ArrayMap, however, so the Object array contains only one item for each
+ * entry in the set (instead of a pair for a mapping).
+ *
+ * Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashSet, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.
+ *
+ * Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it. Currently
+ * you have no control over this shrinking -- if you set a capacity and then remove an
+ * item, it may reduce the capacity to better match the current size. In the future an
+ * explicit call to set the capacity should turn off this aggressive shrinking behavior.
+ *
+ * This structure is **NOT** thread-safe.
+ *
+ * @constructor Creates a new empty ArraySet. The default capacity of an array map is 0, and
+ * will grow once items are added to it.
+ */
+public actual class ArraySet<E>
+// TODO(b/237405792): Default value for optional argument is required here to workaround Metalava's
+// lack of support for expect / actual.
+@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
+// TODO(b/237405286): @JvmOverloads is redundant in this actual, but is necessary here to workaround
+// Metalava's lack of support for expect / actual.
+@JvmOverloads actual constructor(capacity: Int = 0) : MutableCollection<E>, MutableSet<E> {
+ internal actual var hashes: IntArray = EMPTY_INTS
+ internal actual var array: Array<Any?> = EMPTY_OBJECTS
+
+ internal actual var _size = 0
+ actual override val size: Int
+ get() = _size
+
+ /**
+ * Create a new ArraySet with the mappings from the given ArraySet.
+ */
+ public actual constructor(set: ArraySet<out E>?) : this(capacity = 0) {
+ if (set != null) {
+ addAll(set)
+ }
+ }
+
+ /**
+ * Create a new ArraySet with the mappings from the given [Collection].
+ */
+ public actual constructor(set: Collection<E>?) : this(capacity = 0) {
+ if (set != null) {
+ addAll(set)
+ }
+ }
+
+ /**
+ * Create a new ArraySet with items from the given array.
+ */
+ public actual constructor(array: Array<out E>?) : this(capacity = 0) {
+ if (array != null) {
+ for (value in array) {
+ add(value)
+ }
+ }
+ }
+
+ init {
+ if (capacity > 0) {
+ allocArrays(capacity)
+ }
+ }
+
+ /**
+ * Make the array map empty. All storage is released.
+ *
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ actual override fun clear() {
+ clearInternal()
+ }
+
+ /**
+ * Ensure the array map can hold at least [minimumCapacity]
+ * items.
+ *
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun ensureCapacity(minimumCapacity: Int) {
+ ensureCapacityInternal(minimumCapacity)
+ }
+
+ /**
+ * Check whether a value exists in the set.
+ *
+ * @param element The value to search for.
+ * @return Returns true if the value exists, else false.
+ */
+ actual override operator fun contains(element: E): Boolean {
+ return containsInternal(element)
+ }
+
+ /**
+ * Returns the index of a value in the set.
+ *
+ * @param key The value to search for.
+ * @return Returns the index of the value if it exists, else a negative integer.
+ */
+ public actual fun indexOf(key: Any?): Int {
+ return indexOfInternal(key)
+ }
+
+ /**
+ * Return the value at the given index in the array.
+ *
+ * @param index The desired index, must be between 0 and [size]-1.
+ * @return Returns the value stored at the given index.
+ */
+ public actual fun valueAt(index: Int): E {
+ return valueAtInternal(index)
+ }
+
+ /**
+ * Return `true` if the array map contains no items.
+ */
+ actual override fun isEmpty(): Boolean {
+ return isEmptyInternal()
+ }
+
+ /**
+ * Adds the specified object to this set. The set is not modified if it
+ * already contains the object.
+ *
+ * @param element the object to add.
+ * @return `true` if this set is modified, `false` otherwise.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ actual override fun add(element: E): Boolean {
+ return addInternal(element)
+ }
+
+ /**
+ * Perform a [add] of all values in [array]
+ *
+ * @param array The array whose contents are to be retrieved.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun addAll(array: ArraySet<out E>) {
+ addAllInternal(array)
+ }
+
+ /**
+ * Removes the specified object from this set.
+ *
+ * @param element the object to remove.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ actual override fun remove(element: E): Boolean {
+ return removeInternal(element)
+ }
+
+ /**
+ * Remove the key/value mapping at the given index.
+ *
+ * @param index The desired index, must be between 0 and [size]-1.
+ * @return Returns the value that was stored at this index.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun removeAt(index: Int): E {
+ return removeAtInternal(index)
+ }
+
+ /**
+ * Perform a [remove] of all values in [array]
+ *
+ * @param array The array whose contents are to be removed.
+ */
+ public actual fun removeAll(array: ArraySet<out E>): Boolean {
+ return removeAllInternal(array)
+ }
+
+ @Suppress("ArrayReturn")
+ public fun toArray(): Array<Any?> {
+ return array.copyOfRange(fromIndex = 0, toIndex = _size)
+ }
+
+ @Suppress("ArrayReturn")
+ public fun <T> toArray(array: Array<T>): Array<T> {
+ val result = ArraySetJvmUtil.resizeForToArray(array, _size)
+
+ @Suppress("UNCHECKED_CAST")
+ this.array.copyInto(result as Array<Any?>, 0, 0, _size)
+ return result
+ }
+
+ /**
+ * This implementation returns false if the object is not a set, or
+ * if the sets have different sizes. Otherwise, for each value in this
+ * set, it checks to make sure the value also exists in the other set.
+ * If any value doesn't exist, the method returns false; otherwise, it
+ * returns true.
+ *
+ * @see Any.equals
+ */
+ actual override fun equals(other: Any?): Boolean {
+ return equalsInternal(other)
+ }
+
+ /**
+ * @see Any.hashCode
+ */
+ actual override fun hashCode(): Int {
+ return hashCodeInternal()
+ }
+
+ /**
+ * This implementation composes a string by iterating over its values. If
+ * this set contains itself as a value, the string "(this Set)"
+ * will appear in its place.
+ */
+ actual override fun toString(): String {
+ return toStringInternal()
+ }
+
+ /**
+ * Return a [MutableIterator] over all values in the set.
+ *
+ * **Note:** this is a less efficient way to access the array contents compared to
+ * looping from 0 until [size] and calling [valueAt].
+ */
+ actual override fun iterator(): MutableIterator<E> = ElementIterator()
+
+ private inner class ElementIterator : IndexBasedArrayIterator<E>(_size) {
+ override fun elementAt(index: Int): E = valueAt(index)
+
+ override fun removeAt(index: Int) {
+ [email protected](index)
+ }
+ }
+
+ /**
+ * Determine if the array set contains all of the values in the given collection.
+ *
+ * @param elements The collection whose contents are to be checked against.
+ * @return Returns true if this array set contains a value for every entry
+ * in [elements] else returns false.
+ */
+ actual override fun containsAll(elements: Collection<E>): Boolean {
+ return containsAllInternal(elements)
+ }
+
+ /**
+ * Perform an [add] of all values in [elements]
+ *
+ * @param elements The collection whose contents are to be retrieved.
+ */
+ actual override fun addAll(elements: Collection<E>): Boolean {
+ return addAllInternal(elements)
+ }
+
+ /**
+ * Remove all values in the array set that exist in the given collection.
+ *
+ * @param elements The collection whose contents are to be used to remove values.
+ * @return Returns true if any values were removed from the array set, else false.
+ */
+ actual override fun removeAll(elements: Collection<E>): Boolean {
+ return removeAllInternal(elements)
+ }
+
+ /**
+ * Remove all values in the array set that do **not** exist in the given collection.
+ *
+ * @param elements The collection whose contents are to be used to determine which
+ * values to keep.
+ * @return Returns true if any values were removed from the array set, else false.
+ */
+ actual override fun retainAll(elements: Collection<E>): Boolean {
+ return retainAllInternal(elements)
+ }
+}
diff --git a/collection/collection/src/jvmTest/kotlin/androidx/collection/ArraySetJvmTest.kt b/collection/collection/src/jvmTest/kotlin/androidx/collection/ArraySetJvmTest.kt
new file mode 100644
index 0000000..df77ccc
--- /dev/null
+++ b/collection/collection/src/jvmTest/kotlin/androidx/collection/ArraySetJvmTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.assertNull
+import org.junit.Test
+
+class ArraySetJvmTest {
+
+ @Test
+ fun toArray_emptyTypedDestination() {
+ val set = ArraySet<Int>()
+ for (i in 0..5) {
+ set.add(i)
+ }
+
+ // Forces casting, otherwise may not pick up certain failures. Typing just the destination
+ // array or return type is not sufficient to test on JVM.
+ @Suppress("UNUSED_VARIABLE")
+ val result: Array<Int> = set.toArray(emptyArray())
+ }
+
+ @Test
+ fun toArray_nullsLastElement() {
+ val set = ArraySet<Int>()
+ for (i in 0..4) {
+ set.add(i)
+ }
+
+ val result: Array<Int> = set.toArray(Array(10) { -1 })
+ assertNull(result[5])
+ }
+}
\ No newline at end of file
diff --git a/collection/collection/src/nativeMain/kotlin/androidx/collection/ArraySet.native.kt b/collection/collection/src/nativeMain/kotlin/androidx/collection/ArraySet.native.kt
new file mode 100644
index 0000000..9c7be56
--- /dev/null
+++ b/collection/collection/src/nativeMain/kotlin/androidx/collection/ArraySet.native.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_INTS
+import androidx.collection.internal.EMPTY_OBJECTS
+
+/**
+ * ArraySet is a generic set data structure that is designed to be more memory efficient than a
+ * traditional [HashSet]. The design is very similar to
+ * [ArrayMap], with all of the caveats described there. This implementation is
+ * separate from ArrayMap, however, so the Object array contains only one item for each
+ * entry in the set (instead of a pair for a mapping).
+ *
+ * Note that this implementation is not intended to be appropriate for data structures
+ * that may contain large numbers of items. It is generally slower than a traditional
+ * HashSet, since lookups require a binary search and adds and removes require inserting
+ * and deleting entries in the array. For containers holding up to hundreds of items,
+ * the performance difference is not significant, less than 50%.
+ *
+ * Because this container is intended to better balance memory use, unlike most other
+ * standard Java containers it will shrink its array as items are removed from it. Currently
+ * you have no control over this shrinking -- if you set a capacity and then remove an
+ * item, it may reduce the capacity to better match the current size. In the future an
+ * explicit call to set the capacity should turn off this aggressive shrinking behavior.
+ *
+ * This structure is **NOT** thread-safe.
+ *
+ * @constructor Creates a new empty ArraySet. The default capacity of an array map is 0, and
+ * will grow once items are added to it.
+ */
+public actual class ArraySet<E> actual constructor(
+ capacity: Int
+) : MutableCollection<E>, MutableSet<E> {
+
+ internal actual var hashes: IntArray = EMPTY_INTS
+ internal actual var array: Array<Any?> = EMPTY_OBJECTS
+
+ internal actual var _size = 0
+ actual override val size: Int
+ get() = _size
+
+ /**
+ * Create a new ArraySet with the mappings from the given ArraySet.
+ */
+ public actual constructor(set: ArraySet<out E>?) : this(capacity = 0) {
+ if (set != null) {
+ addAll(set)
+ }
+ }
+
+ /**
+ * Create a new ArraySet with the mappings from the given [Collection].
+ */
+ public actual constructor(set: Collection<E>?) : this(capacity = 0) {
+ if (set != null) {
+ addAll(set)
+ }
+ }
+
+ /**
+ * Create a new ArraySet with items from the given array.
+ */
+ public actual constructor(array: Array<out E>?) : this(capacity = 0) {
+ if (array != null) {
+ for (value in array) {
+ add(value)
+ }
+ }
+ }
+
+ init {
+ if (capacity > 0) {
+ allocArrays(capacity)
+ }
+ }
+
+ /**
+ * Make the array map empty. All storage is released.
+ *
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ actual override fun clear() {
+ clearInternal()
+ }
+
+ /**
+ * Ensure the array map can hold at least [minimumCapacity]
+ * items.
+ *
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun ensureCapacity(minimumCapacity: Int) {
+ ensureCapacityInternal(minimumCapacity)
+ }
+
+ /**
+ * Check whether a value exists in the set.
+ *
+ * @param element The value to search for.
+ * @return Returns true if the value exists, else false.
+ */
+ actual override operator fun contains(element: E): Boolean {
+ return containsInternal(element)
+ }
+
+ /**
+ * Returns the index of a value in the set.
+ *
+ * @param key The value to search for.
+ * @return Returns the index of the value if it exists, else a negative integer.
+ */
+ public actual fun indexOf(key: Any?): Int {
+ return indexOfInternal(key)
+ }
+
+ /**
+ * Return the value at the given index in the array.
+ *
+ * @param index The desired index, must be between 0 and [size]-1.
+ * @return Returns the value stored at the given index.
+ */
+ public actual fun valueAt(index: Int): E {
+ return valueAtInternal(index)
+ }
+
+ /**
+ * Return `true` if the array map contains no items.
+ */
+ actual override fun isEmpty(): Boolean {
+ return isEmptyInternal()
+ }
+
+ /**
+ * Adds the specified object to this set. The set is not modified if it
+ * already contains the object.
+ *
+ * @param element the object to add.
+ * @return `true` if this set is modified, `false` otherwise.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ actual override fun add(element: E): Boolean {
+ return addInternal(element)
+ }
+
+ /**
+ * Perform a [add] of all values in [array]
+ *
+ * @param array The array whose contents are to be retrieved.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun addAll(array: ArraySet<out E>) {
+ addAllInternal(array)
+ }
+
+ /**
+ * Removes the specified object from this set.
+ *
+ * @param element the object to remove.
+ * @return `true` if this set was modified, `false` otherwise.
+ */
+ actual override fun remove(element: E): Boolean {
+ return removeInternal(element)
+ }
+
+ /**
+ * Remove the key/value mapping at the given index.
+ *
+ * @param index The desired index, must be between 0 and [size]-1.
+ * @return Returns the value that was stored at this index.
+ * @throws ConcurrentModificationException if concurrent modifications detected.
+ */
+ public actual fun removeAt(index: Int): E {
+ return removeAtInternal(index)
+ }
+
+ /**
+ * Perform a [remove] of all values in [array]
+ *
+ * @param array The array whose contents are to be removed.
+ */
+ public actual fun removeAll(array: ArraySet<out E>): Boolean {
+ return removeAllInternal(array)
+ }
+
+ /**
+ * This implementation returns false if the object is not a set, or
+ * if the sets have different sizes. Otherwise, for each value in this
+ * set, it checks to make sure the value also exists in the other set.
+ * If any value doesn't exist, the method returns false; otherwise, it
+ * returns true.
+ *
+ * @see Any.equals
+ */
+ actual override fun equals(other: Any?): Boolean {
+ return equalsInternal(other)
+ }
+
+ /**
+ * @see Any.hashCode
+ */
+ actual override fun hashCode(): Int {
+ return hashCodeInternal()
+ }
+
+ /**
+ * This implementation composes a string by iterating over its values. If
+ * this set contains itself as a value, the string "(this Set)"
+ * will appear in its place.
+ */
+ actual override fun toString(): String {
+ return toStringInternal()
+ }
+
+ /**
+ * Return a [MutableIterator] over all values in the set.
+ *
+ * **Note:** this is a less efficient way to access the array contents compared to
+ * looping from 0 until [size] and calling [valueAt].
+ */
+ actual override fun iterator(): MutableIterator<E> = ElementIterator()
+
+ private inner class ElementIterator : IndexBasedArrayIterator<E>(_size) {
+ override fun elementAt(index: Int): E = valueAt(index)
+
+ override fun removeAt(index: Int) {
+ [email protected](index)
+ }
+ }
+
+ /**
+ * Determine if the array set contains all of the values in the given collection.
+ *
+ * @param elements The collection whose contents are to be checked against.
+ * @return Returns true if this array set contains a value for every entry
+ * in [elements] else returns false.
+ */
+ actual override fun containsAll(elements: Collection<E>): Boolean {
+ return containsAllInternal(elements)
+ }
+
+ /**
+ * Perform an [add] of all values in [elements]
+ *
+ * @param elements The collection whose contents are to be retrieved.
+ */
+ actual override fun addAll(elements: Collection<E>): Boolean {
+ return addAllInternal(elements)
+ }
+
+ /**
+ * Remove all values in the array set that exist in the given collection.
+ *
+ * @param elements The collection whose contents are to be used to remove values.
+ * @return Returns true if any values were removed from the array set, else false.
+ */
+ actual override fun removeAll(elements: Collection<E>): Boolean {
+ return removeAll(elements)
+ }
+
+ /**
+ * Remove all values in the array set that do **not** exist in the given collection.
+ *
+ * @param elements The collection whose contents are to be used to determine which
+ * values to keep.
+ * @return Returns true if any values were removed from the array set, else false.
+ */
+ actual override fun retainAll(elements: Collection<E>): Boolean {
+ return retainAllInternal(elements)
+ }
+}