blob: bc600cc052401e2a97c70e976d346abda1d466de [file] [log] [blame]
/*
* Copyright 2021 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.compose.runtime.collection
import androidx.compose.runtime.identityHashCode
internal class IdentityArrayMap<Key : Any, Value : Any?>(capacity: Int = 16) {
var keys = arrayOfNulls<Any?>(capacity)
private set
var values = arrayOfNulls<Any?>(capacity)
private set
var size = 0
private set
fun isEmpty() = size == 0
fun isNotEmpty() = size > 0
operator fun contains(key: Key): Boolean = find(key) >= 0
operator fun get(key: Key): Value? {
val index = find(key)
@Suppress("UNCHECKED_CAST")
return if (index >= 0) values[index] as Value else null
}
operator fun set(key: Key, value: Value) {
val keys = keys
val values = values
val size = size
val index = find(key)
if (index >= 0) {
values[index] = value
} else {
val insertIndex = -(index + 1)
val resize = size == keys.size
val destKeys = if (resize) {
arrayOfNulls(size * 2)
} else keys
keys.copyInto(
destination = destKeys,
destinationOffset = insertIndex + 1,
startIndex = insertIndex,
endIndex = size
)
if (resize) {
keys.copyInto(
destination = destKeys,
endIndex = insertIndex
)
}
destKeys[insertIndex] = key
this.keys = destKeys
val destValues = if (resize) {
arrayOfNulls(size * 2)
} else values
values.copyInto(
destination = destValues,
destinationOffset = insertIndex + 1,
startIndex = insertIndex,
endIndex = size
)
if (resize) {
values.copyInto(
destination = destValues,
endIndex = insertIndex
)
}
destValues[insertIndex] = value
this.values = destValues
this.size++
}
}
fun remove(key: Key): Value? {
val index = find(key)
if (index >= 0) {
val value = values[index]
val size = size
val keys = keys
val values = values
keys.copyInto(
destination = keys,
destinationOffset = index,
startIndex = index + 1,
endIndex = size
)
values.copyInto(
destination = values,
destinationOffset = index,
startIndex = index + 1,
endIndex = size
)
val newSize = size - 1
keys[newSize] = null
values[newSize] = null
this.size = newSize
@Suppress("UNCHECKED_CAST")
return value as Value
}
return null
}
fun clear() {
size = 0
keys.fill(null)
values.fill(null)
}
inline fun removeIf(block: (key: Key, value: Value) -> Boolean) {
var current = 0
for (index in 0 until size) {
@Suppress("UNCHECKED_CAST")
val key = keys[index] as Key
@Suppress("UNCHECKED_CAST")
val value = values[index] as Value
if (!block(key, value)) {
if (current != index) {
keys[current] = key
values[current] = values[index]
}
current++
}
}
if (size > current) {
for (index in current until size) {
keys[index] = null
values[index] = null
}
size = current
}
}
inline fun removeValueIf(block: (value: Value) -> Boolean) {
removeIf { _, value -> block(value) }
}
inline fun forEach(block: (key: Key, value: Value) -> Unit) {
for (index in 0 until size) {
@Suppress("UNCHECKED_CAST")
block(keys[index] as Key, values[index] as Value)
}
}
/**
* Returns the index into [keys] of the found [key], or the negative index - 1 of the
* position in which it would be if it were found.
*/
private fun find(key: Any?): Int {
val keyIdentity = identityHashCode(key)
var low = 0
var high = size - 1
val keys = keys
while (low <= high) {
val mid = (low + high).ushr(1)
val midKey = keys[mid]
val midKeyHash = identityHashCode(midKey)
when {
midKeyHash < keyIdentity -> low = mid + 1
midKeyHash > keyIdentity -> high = mid - 1
key === midKey -> return mid
else -> return findExactIndex(mid, key, keyIdentity)
}
}
return -(low + 1)
}
/**
* When multiple keys share the same [identityHashCode], then we must find the specific
* index of the target item. This method assumes that [midIndex] has already been checked
* for an exact match for [key], but will look at nearby values to find the exact item index.
* If no match is found, the negative index - 1 of the position in which it would be will
* be returned, which is always after the last key with the same [identityHashCode].
*/
private fun findExactIndex(midIndex: Int, key: Any?, keyHash: Int): Int {
val keys = keys
val size = size
// hunt down first
for (i in midIndex - 1 downTo 0) {
val k = keys[i]
if (k === key) {
return i
}
if (identityHashCode(k) != keyHash) {
break // we've gone too far
}
}
for (i in midIndex + 1 until size) {
val k = keys[i]
if (k === key) {
return i
}
if (identityHashCode(k) != keyHash) {
// We've gone too far. We should insert here.
return -(i + 1)
}
}
// We should insert at the end
return -(size + 1)
}
}