All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.realm.kotlin.internal.RealmMapInternal.kt Maven / Gradle / Ivy

Go to download

Library code for Realm Kotlin. This artifact is not supposed to be consumed directly, but through 'io.realm.kotlin:gradle-plugin:1.11.1' instead.

There is a newer version: 3.0.0
Show newest version
/*
 * Copyright 2023 Realm Inc.
 *
 * 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 io.realm.kotlin.internal

import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.Versioned
import io.realm.kotlin.dynamic.DynamicRealmObject
import io.realm.kotlin.ext.asRealmObject
import io.realm.kotlin.ext.isManaged
import io.realm.kotlin.internal.RealmValueArgumentConverter.convertToQueryArgs
import io.realm.kotlin.internal.interop.Callback
import io.realm.kotlin.internal.interop.ClassKey
import io.realm.kotlin.internal.interop.RealmChangesPointer
import io.realm.kotlin.internal.interop.RealmInterop
import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_erase
import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_find
import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_get
import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert
import io.realm.kotlin.internal.interop.RealmInterop.realm_dictionary_insert_embedded
import io.realm.kotlin.internal.interop.RealmInterop.realm_results_get
import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer
import io.realm.kotlin.internal.interop.RealmMapPointer
import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer
import io.realm.kotlin.internal.interop.RealmObjectInterop
import io.realm.kotlin.internal.interop.RealmResultsPointer
import io.realm.kotlin.internal.interop.RealmValue
import io.realm.kotlin.internal.interop.getterScope
import io.realm.kotlin.internal.interop.inputScope
import io.realm.kotlin.internal.query.ObjectBoundQuery
import io.realm.kotlin.internal.query.ObjectQuery
import io.realm.kotlin.internal.util.Validation
import io.realm.kotlin.notifications.MapChange
import io.realm.kotlin.notifications.MapChangeSet
import io.realm.kotlin.notifications.internal.DeletedDictionaryImpl
import io.realm.kotlin.notifications.internal.InitialDictionaryImpl
import io.realm.kotlin.notifications.internal.UpdatedMapImpl
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmAny
import io.realm.kotlin.types.RealmDictionary
import io.realm.kotlin.types.RealmMap
import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.Flow
import kotlin.reflect.KClass

// ----------------------------------------------------------------------
// Map
// ----------------------------------------------------------------------

internal abstract class ManagedRealmMap constructor(
    internal val parent: RealmObjectReference<*>?,
    internal val nativePointer: RealmMapPointer,
    val operator: MapOperator
) : AbstractMutableMap(), RealmMap, CoreNotifiable, MapChange> {

    private val keysPointer by lazy { RealmInterop.realm_dictionary_get_keys(nativePointer) }
    private val valuesPointer by lazy { RealmInterop.realm_dictionary_to_results(nativePointer) }

    // Make it lazy since the entry set is a live set pointing to the actual map
    override val entries: MutableSet> by lazy {
        operator.realmReference.checkClosed()
        RealmMapEntrySetImpl(nativePointer, operator, parent)
    }

    // Make it lazy since the key set is a live set pointing to the actual map
    override val keys: MutableSet by lazy {
        operator.realmReference.checkClosed()
        KeySet(keysPointer, operator, parent)
    }

    override val size: Int
        get() = operator.size

    // Make it lazy since the values collection is a live collection pointing to the actual map
    override val values: MutableCollection by lazy {
        operator.realmReference.checkClosed()
        RealmMapValues(valuesPointer, operator, parent)
    }

    override fun clear() = operator.clear()

    override fun containsKey(key: K): Boolean = operator.containsKey(key)

    override fun containsValue(value: V): Boolean = operator.containsValue(value)

    override fun get(key: K): V? = operator.get(key)

    override fun put(key: K, value: V): V? = operator.put(key, value)

    override fun remove(key: K): V? = operator.remove(key)

    override fun asFlow(keyPaths: List?): Flow> {
        operator.realmReference.checkClosed()
        val keyPathInfo = keyPaths?.let {
            Validation.isType>(operator, "Keypaths are only supported for maps of objects.")
            Pair(operator.classKey, keyPaths)
        }
        return operator.realmReference.owner.registerObserver(this, keyPathInfo)
    }

    override fun registerForNotification(
        keyPaths: RealmKeyPathArrayPointer?,
        callback: Callback
    ): RealmNotificationTokenPointer =
        RealmInterop.realm_dictionary_add_notification_callback(nativePointer, keyPaths, callback)

    override fun isValid(): Boolean =
        !nativePointer.isReleased() && RealmInterop.realm_dictionary_is_valid(nativePointer)

    // TODO add equals and hashCode and tests for those. Observe this constrain
    //  https://github.com/realm/realm-kotlin/pull/1188
}

internal fun  ManagedRealmMap.query(
    query: String,
    args: Array
): RealmQuery {
    @Suppress("UNCHECKED_CAST")
    val operator: BaseRealmObjectMapOperator = operator as BaseRealmObjectMapOperator
    val queryPointer = inputScope {
        val queryArgs = convertToQueryArgs(args)
        val mapValues = values as RealmMapValues<*, *>
        RealmInterop.realm_query_parse_for_results(mapValues.resultsPointer, query, queryArgs)
    }
    // parent is only available for lists with an object as an immediate parent (contrary to nested
    // collections).
    // Nested collections are only supported for RealmAny-values and are therefore
    // outside of the BaseRealmObject bound for the generic type parameters, so we should never be
    // able to reach here for nested collections of RealmAny.
    if (parent == null) error("Cannot perform subqueries on non-object dictionaries")
    return ObjectBoundQuery(
        parent,
        ObjectQuery(
            operator.realmReference,
            operator.classKey,
            operator.clazz,
            operator.mediator,
            queryPointer
        )
    )
}

/**
 * Operator interface abstracting the connection between the API and and the interop layer. It is
 * used internally by [ManagedRealmMap], [RealmMapEntrySetImpl] and [ManagedRealmMapEntry].
 */
internal interface MapOperator : CollectionOperator {

    // Modification counter used to detect concurrent writes from the iterator, taken from Java's
    // AbstractList implementation
    var modCount: Int
    val keyConverter: RealmValueConverter
    override val nativePointer: RealmMapPointer

    val size: Int
        get() {
            realmReference.checkClosed()
            return RealmInterop.realm_dictionary_size(nativePointer).toInt()
        }

    fun insertInternal(
        key: K,
        value: V?,
        updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
        cache: UnmanagedToManagedObjectCache = mutableMapOf()
    ): Pair

    fun eraseInternal(key: K): Pair
    fun getEntryInternal(position: Int): Pair
    fun getInternal(key: K): V?
    fun containsValueInternal(value: V): Boolean

    // Compares two values. Byte arrays are compared structurally. Objects are only equal if the
    // memory address is the same.
    fun areValuesEqual(expected: V?, actual: V?): Boolean

    // This function returns a Pair because it is used by both the Map and the entry Set. Having
    // both different semantics, Map returns the previous value for the key whereas the entry Set
    // returns whether the element was inserted successfully.
    fun insert(
        key: K,
        value: V?,
        updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
        cache: UnmanagedToManagedObjectCache = mutableMapOf()
    ): Pair {
        realmReference.checkClosed()
        return insertInternal(key, value, updatePolicy, cache)
            .also { modCount++ }
    }

    // Similarly to insert, Map returns the erased value whereas the entry Set returns whether the
    // element was erased successfully.
    fun erase(key: K): Pair {
        realmReference.checkClosed()
        return eraseInternal(key)
            .also { modCount++ }
    }

    fun getEntry(position: Int): Pair {
        realmReference.checkClosed()
        return getEntryInternal(position)
    }

    fun get(key: K): V? {
        realmReference.checkClosed()
        return getInternal(key)
    }

    fun containsValue(value: V): Boolean {
        realmReference.checkClosed()
        return containsValueInternal(value)
    }

    @Suppress("UNCHECKED_CAST")
    fun getValue(resultsPointer: RealmResultsPointer, index: Int): V?

    @Suppress("UNCHECKED_CAST")
    fun getKey(resultsPointer: RealmResultsPointer, index: Int): K {
        return getterScope {
            with(keyConverter) {
                val transport = realm_results_get(resultsPointer, index.toLong())
                realmValueToPublic(transport)
            } as K
        }
    }

    fun put(
        key: K,
        value: V,
        updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
        cache: UnmanagedToManagedObjectCache = mutableMapOf()
    ): V? {
        realmReference.checkClosed()
        return insertInternal(key, value, updatePolicy, cache).first
            .also { modCount++ }
    }

    fun putAll(
        from: Map,
        updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
        cache: UnmanagedToManagedObjectCache = mutableMapOf()
    ) {
        realmReference.checkClosed()
        for (entry in from) {
            put(entry.key, entry.value, updatePolicy, cache)
        }
    }

    fun remove(key: K): V? {
        realmReference.checkClosed()
        return eraseInternal(key).first
            .also { modCount++ }
    }

    fun clear() {
        realmReference.checkClosed()
        RealmInterop.realm_dictionary_clear(nativePointer)
        modCount++
    }

    fun containsKey(key: K): Boolean {
        realmReference.checkClosed()

        // Even though we are getting a value we need to free the data buffers of the string we
        // send down to Core, so we need to use an inputScope.
        return inputScope {
            with(keyConverter) {
                RealmInterop.realm_dictionary_contains_key(nativePointer, publicToRealmValue(key))
            }
        }
    }

    fun copy(realmReference: RealmReference, nativePointer: RealmMapPointer): MapOperator
}

internal open class PrimitiveMapOperator constructor(
    override val mediator: Mediator,
    override val realmReference: RealmReference,
    val realmValueConverter: RealmValueConverter,
    override val keyConverter: RealmValueConverter,
    override val nativePointer: RealmMapPointer
) : MapOperator {

    override var modCount: Int = 0

    override fun insertInternal(
        key: K,
        value: V?,
        updatePolicy: UpdatePolicy,
        cache: UnmanagedToManagedObjectCache
    ): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            with(realmValueConverter) {
                val valueTransport = publicToRealmValue(value)
                realm_dictionary_insert(
                    nativePointer,
                    keyTransport,
                    valueTransport
                ).let {
                    Pair(realmValueToPublic(it.first), it.second)
                }
            }
        }
    }

    override fun eraseInternal(key: K): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            with(realmValueConverter) {
                realm_dictionary_erase(nativePointer, keyTransport).let {
                    Pair(realmValueToPublic(it.first), it.second)
                }
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun getEntryInternal(position: Int): Pair {
        return getterScope {
            realm_dictionary_get(nativePointer, position)
                .let {
                    val key = with(keyConverter) { realmValueToPublic(it.first) }
                    val value = with(realmValueConverter) { realmValueToPublic(it.second) }
                    Pair(key, value)
                } as Pair
        }
    }

    override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? {
        return getterScope {
            @Suppress("UNCHECKED_CAST")
            with(realmValueConverter) {
                val transport = realm_results_get(resultsPointer, index.toLong())
                realmValueToPublic(transport)
            } as V
        }
    }

    override fun getInternal(key: K): V? {
        // Even though we are getting a value we need to free the data buffers of the string we
        // send down to Core, so we need to use an inputScope.
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            val valueTransport = realm_dictionary_find(nativePointer, keyTransport)
            with(realmValueConverter) { realmValueToPublic(valueTransport) }
        }
    }

    override fun containsValueInternal(value: V): Boolean {
        // Even though we are getting a value we need to free the data buffers of the string values
        // we send down to Core, so we need to use an inputScope.
        return inputScope {
            // FIXME This could potentially import an object?
            with(realmValueConverter) {
                RealmInterop.realm_dictionary_contains_value(
                    nativePointer,
                    publicToRealmValue(value)
                )
            }
        }
    }

    override fun areValuesEqual(expected: V?, actual: V?): Boolean =
        when (expected) {
            is ByteArray -> expected.contentEquals(actual?.let { it as ByteArray })
            else -> expected == actual
        }

    override fun copy(
        realmReference: RealmReference,
        nativePointer: RealmMapPointer
    ): MapOperator =
        PrimitiveMapOperator(mediator, realmReference, realmValueConverter, keyConverter, nativePointer)
}

internal fun realmAnyMapOperator(
    mediator: Mediator,
    realm: RealmReference,
    nativePointer: RealmMapPointer,
    issueDynamicObject: Boolean = false,
    issueDynamicMutableObject: Boolean = false,
): RealmAnyMapOperator = RealmAnyMapOperator(
    mediator,
    realm,
    converter(String::class),
    nativePointer,
    issueDynamicObject,
    issueDynamicMutableObject
)
@Suppress("LongParameterList")
internal class RealmAnyMapOperator constructor(
    override val mediator: Mediator,
    override val realmReference: RealmReference,
    override val keyConverter: RealmValueConverter,
    override val nativePointer: RealmMapPointer,
    private val issueDynamicObject: Boolean,
    private val issueDynamicMutableObject: Boolean
) : MapOperator {

    override var modCount: Int = 0

    override fun eraseInternal(key: K): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            realm_dictionary_erase(nativePointer, keyTransport).let {
                Pair(realmAny(it.first, keyTransport), it.second)
            }
        }
    }

    override fun containsValueInternal(value: RealmAny?): Boolean {
        // Unmanaged objects are never found in a managed dictionary
        if (value?.type == RealmAny.Type.OBJECT) {
            if (!value.asRealmObject().isManaged()) return false
        }

        // Even though we are getting a value we need to free the data buffers of the string values
        // we send down to Core, so we need to use an inputScope.
        return inputScope {
            RealmInterop.realm_dictionary_contains_value(
                nativePointer,
                realmAnyToRealmValueWithoutImport(value)
            )
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun getEntryInternal(position: Int): Pair {
        return getterScope {
            realm_dictionary_get(nativePointer, position)
                .let {
                    val keyTransport: K = with(keyConverter) { realmValueToPublic(it.first) as K }
                    return keyTransport to getInternal(keyTransport)
                }
        }
    }

    override fun getValue(resultsPointer: RealmResultsPointer, index: Int): RealmAny? {
        return getterScope {
            val transport = realm_results_get(resultsPointer, index.toLong())
            realmValueToRealmAny(
                realmValue = transport,
                parent = null,
                mediator = mediator,
                owner = realmReference,
                issueDynamicObject = issueDynamicObject,
                issueDynamicMutableObject = issueDynamicMutableObject,
                getListFunction = { RealmInterop.realm_results_get_list(resultsPointer, index.toLong()) },
                getDictionaryFunction = { RealmInterop.realm_results_get_dictionary(resultsPointer, index.toLong()) },
            )
        }
    }

    override fun copy(
        realmReference: RealmReference,
        nativePointer: RealmMapPointer
    ): MapOperator =
        RealmAnyMapOperator(mediator, realmReference, keyConverter, nativePointer, issueDynamicObject, issueDynamicMutableObject)

    override fun areValuesEqual(expected: RealmAny?, actual: RealmAny?): Boolean {
        return expected == actual
    }

    override fun getInternal(key: K): RealmAny? {
        return inputScope {
            val keyTransport: RealmValue = with(keyConverter) { publicToRealmValue(key) }
            val valueTransport: RealmValue = realm_dictionary_find(nativePointer, keyTransport)
            realmAny(valueTransport, keyTransport)
        }
    }

    private fun realmAny(
        valueTransport: RealmValue,
        keyTransport: RealmValue
    ) = realmValueToRealmAny(
        valueTransport, null, mediator, realmReference,
        issueDynamicObject,
        issueDynamicMutableObject,
        { RealmInterop.realm_dictionary_find_list(nativePointer, keyTransport) }
    ) { RealmInterop.realm_dictionary_find_dictionary(nativePointer, keyTransport) }

    override fun insertInternal(
        key: K,
        value: RealmAny?,
        updatePolicy: UpdatePolicy,
        cache: UnmanagedToManagedObjectCache
    ): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            return realmAnyHandler(
                value,
                primitiveValueAsRealmValueHandler = {
                    realm_dictionary_insert(nativePointer, keyTransport, it).let { result ->
                        realmAny(result.first, keyTransport) to result.second
                    }
                },
                referenceAsRealmAnyHandler = {
                    val obj = when (issueDynamicObject) {
                        true -> it.asRealmObject()
                        false -> it.asRealmObject()
                    }
                    val objRef = realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache)
                    val transport = realmObjectTransport(objRef as RealmObjectInterop)
                    realm_dictionary_insert(nativePointer, keyTransport, transport).let { result ->
                        realmAny(result.first, keyTransport) to result.second
                    }
                },
                listAsRealmAnyHandler = { realmValue ->
                    val previous = getInternal(key)
                    val nativePointer = RealmInterop.realm_dictionary_insert_list(nativePointer, keyTransport)
                    RealmInterop.realm_list_clear(nativePointer)
                    val operator = realmAnyListOperator(
                        mediator,
                        realmReference,
                        nativePointer,
                        issueDynamicObject, issueDynamicMutableObject
                    )
                    operator.insertAll(0, realmValue.asList(), updatePolicy, cache)
                    previous to true
                },
                dictionaryAsRealmAnyHandler = { realmValue ->
                    val previous = getInternal(key)
                    val nativePointer = RealmInterop.realm_dictionary_insert_dictionary(nativePointer, keyTransport)
                    RealmInterop.realm_dictionary_clear(nativePointer)
                    val operator =
                        realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject)
                    operator.putAll(realmValue.asDictionary(), updatePolicy, cache)
                    previous to true
                }
            )
        }
    }
}

@Suppress("LongParameterList")
internal abstract class BaseRealmObjectMapOperator constructor(
    override val mediator: Mediator,
    override val realmReference: RealmReference,
    override val keyConverter: RealmValueConverter,
    override val nativePointer: RealmMapPointer,
    val clazz: KClass,
    val classKey: ClassKey
) : MapOperator {

    override var modCount: Int = 0

    @Suppress("UNCHECKED_CAST")
    override fun eraseInternal(key: K): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            realm_dictionary_erase(nativePointer, keyTransport).let {
                val previousObject = realmValueToRealmObject(
                    it.first,
                    clazz as KClass,
                    mediator,
                    realmReference
                )
                Pair(previousObject, it.second)
            } as Pair
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun getEntryInternal(position: Int): Pair {
        return getterScope {
            realm_dictionary_get(nativePointer, position)
                .let {
                    val key = with(keyConverter) { realmValueToPublic(it.first) }
                    val value = realmValueToRealmObject(
                        it.second,
                        clazz as KClass,
                        mediator,
                        realmReference
                    )
                    Pair(key, value)
                } as Pair
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun getInternal(key: K): V? {
        // Even though we are getting a value we need to free the data buffers of the string we
        // send down to Core, so we need to use an inputScope.
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            realmValueToRealmObject(
                realm_dictionary_find(nativePointer, keyTransport),
                clazz as KClass,
                mediator,
                realmReference
            )
        } as V?
    }

    override fun getValue(resultsPointer: RealmResultsPointer, index: Int): V? {
        return getterScope {
            val transport = realm_results_get(resultsPointer, index.toLong())
            realmValueToRealmObject(transport, clazz, mediator, realmReference)
        }
    }

    override fun containsValueInternal(value: V): Boolean {
        value?.also {
            // Unmanaged objects are never found in a managed dictionary
            if (!(it as RealmObjectInternal).isManaged()) return false
        }

        // Even though we are getting a value we need to free the data buffers of the string we
        // send down to Core, so we need to use an inputScope.
        return inputScope {
            RealmInterop.realm_dictionary_contains_value(
                nativePointer,
                realmObjectToRealmValue(value)
            )
        }
    }

    override fun areValuesEqual(expected: V?, actual: V?): Boolean {
        // Two objects are only the same if they point to the same memory address
        if (expected === actual) return true

        // TODO take this into consideration when it's ready
        //  https://github.com/realm/realm-kotlin/issues/1097
        return false
    }

    @Suppress("UNCHECKED_CAST")
    override fun copy(
        realmReference: RealmReference,
        nativePointer: RealmMapPointer
    ): MapOperator = RealmObjectMapOperator(
        mediator,
        realmReference,
        converter(String::class) as RealmValueConverter,
        nativePointer,
        clazz,
        classKey
    )
}

@Suppress("LongParameterList")
internal class RealmObjectMapOperator constructor(
    mediator: Mediator,
    realmReference: RealmReference,
    keyConverter: RealmValueConverter,
    nativePointer: RealmMapPointer,
    clazz: KClass,
    classKey: ClassKey
) : BaseRealmObjectMapOperator(
    mediator,
    realmReference,
    keyConverter,
    nativePointer,
    clazz,
    classKey
) {
    @Suppress("UNCHECKED_CAST")
    override fun insertInternal(
        key: K,
        value: V?,
        updatePolicy: UpdatePolicy,
        cache: UnmanagedToManagedObjectCache
    ): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            val objTransport = realmObjectToRealmReferenceWithImport(
                value as BaseRealmObject?,
                mediator,
                realmReference,
                updatePolicy,
                cache
            ).let {
                realmObjectTransport(it as RealmObjectInterop?)
            }
            realm_dictionary_insert(
                nativePointer,
                keyTransport,
                objTransport
            ).let {
                val previousObject = realmValueToRealmObject(
                    it.first,
                    clazz as KClass,
                    mediator,
                    realmReference
                )
                Pair(previousObject, it.second)
            } as Pair
        }
    }
}

@Suppress("LongParameterList")
internal class EmbeddedRealmObjectMapOperator constructor(
    mediator: Mediator,
    realmReference: RealmReference,
    keyConverter: RealmValueConverter,
    nativePointer: RealmMapPointer,
    clazz: KClass,
    classKey: ClassKey
) : BaseRealmObjectMapOperator(
    mediator,
    realmReference,
    keyConverter,
    nativePointer,
    clazz,
    classKey
) {
    @Suppress("UNCHECKED_CAST")
    override fun insertInternal(
        key: K,
        value: V?,
        updatePolicy: UpdatePolicy,
        cache: UnmanagedToManagedObjectCache
    ): Pair {
        return inputScope {
            val keyTransport = with(keyConverter) { publicToRealmValue(key) }
            if (value == null) {
                val (previousValue, modified) = realm_dictionary_insert(
                    nativePointer,
                    keyTransport,
                    realmObjectTransport(null)
                )
                val previousObject = realmValueToRealmObject(
                    previousValue,
                    clazz as KClass,
                    mediator,
                    realmReference
                )
                Pair(previousObject, modified)
            } else {
                // We cannot return the old object as it is deleted when losing its parent so just
                // return the newly created object even though it goes against the API
                val embedded = realm_dictionary_insert_embedded(nativePointer, keyTransport)
                val newEmbeddedRealmObject = realmValueToRealmObject(embedded, clazz, mediator, realmReference) as V
                RealmObjectHelper.assign(newEmbeddedRealmObject, value, updatePolicy, cache)
                Pair(newEmbeddedRealmObject, true)
            } as Pair
        }
    }
}

// ----------------------------------------------------------------------
// Dictionary
// ----------------------------------------------------------------------

internal class UnmanagedRealmDictionary(
    dictionary: Map = mutableMapOf()
) : RealmDictionary, MutableMap by dictionary.toMutableMap() {
    override fun asFlow(keyPaths: List?): Flow> =
        throw UnsupportedOperationException("Unmanaged dictionaries cannot be observed.")

    override fun toString(): String = entries.joinToString { (key, value) -> "[$key,$value]" }
        .let { "UnmanagedRealmDictionary{$it}" }

    override fun equals(other: Any?): Boolean {
        if (other !is RealmDictionary<*>) return false
        if (this === other) return true
        if (this.size == other.size && this.entries.containsAll(other.entries)) return true
        return false
    }

    override fun hashCode(): Int {
        var result = size.hashCode()
        result = 31 * result + entries.hashCode()
        return result
    }
}

internal class ManagedRealmDictionary constructor(
    parent: RealmObjectReference<*>?,
    nativePointer: RealmMapPointer,
    operator: MapOperator
) : ManagedRealmMap(parent, nativePointer, operator),
    RealmDictionary,
    Versioned by operator.realmReference {

    override fun freeze(frozenRealm: RealmReference): ManagedRealmDictionary? {
        return RealmInterop.realm_dictionary_resolve_in(nativePointer, frozenRealm.dbPointer)
            ?.let {
                ManagedRealmDictionary(parent, it, operator.copy(frozenRealm, it))
            }
    }

    override fun changeFlow(scope: ProducerScope>): ChangeFlow, MapChange> =
        RealmDictionaryChangeFlow(scope)

    override fun thaw(liveRealm: RealmReference): ManagedRealmDictionary? {
        return RealmInterop.realm_dictionary_resolve_in(nativePointer, liveRealm.dbPointer)
            ?.let {
                ManagedRealmDictionary(parent, it, operator.copy(liveRealm, it))
            }
    }

    override fun toString(): String {
        val (owner, version, objKey) = parent?.run {
            Triple(
                className,
                owner.version().version,
                RealmInterop.realm_object_get_key(objectPointer).key
            )
        } ?: Triple("null", operator.realmReference.version().version, "null")
        return "RealmDictionary{size=$size,owner=$owner,objKey=$objKey,version=$version}"
    }

    // TODO add equals and hashCode when https://github.com/realm/realm-kotlin/issues/1097 is fixed
}

internal class RealmDictionaryChangeFlow(scope: ProducerScope>) :
    ChangeFlow, MapChange>(scope) {
    override fun initial(frozenRef: ManagedRealmMap): MapChange =
        InitialDictionaryImpl(frozenRef)

    override fun update(
        frozenRef: ManagedRealmMap,
        change: RealmChangesPointer
    ): MapChange? {
        val builder: MapChangeSet = DictionaryChangeSetBuilderImpl(change).build()
        return UpdatedMapImpl(frozenRef, builder)
    }

    override fun delete(): MapChange =
        DeletedDictionaryImpl(UnmanagedRealmDictionary())
}

// ----------------------------------------------------------------------
// Keys
// ----------------------------------------------------------------------

/**
 * [MutableSet] containing all the keys present in a dictionary. Core returns keys as results.
 */
internal class KeySet constructor(
    private val keysPointer: RealmResultsPointer,
    private val operator: MapOperator,
    private val parent: RealmObjectReference<*>?
) : AbstractMutableSet() {

    override val size: Int
        get() = RealmInterop.realm_results_count(keysPointer).toInt()

    override fun add(element: K): Boolean =
        throw UnsupportedOperationException("Adding keys to a dictionary through 'dictionary.keys' is not allowed.")

    override fun iterator(): MutableIterator =
        object : RealmMapGenericIterator(operator) {
            @Suppress("UNCHECKED_CAST")
            override fun getNext(position: Int): K = operator.getKey(keysPointer, position)
        }

    override fun toString(): String {
        val (owner, version, objKey) = parent?.run {
            Triple(
                className,
                owner.version().version,
                RealmInterop.realm_object_get_key(parent.objectPointer).key
            )
        } ?: Triple("null", operator.realmReference.version().version, "null")
        return "RealmDictionary.keys{size=$size,owner=$owner,objKey=$objKey,version=$version}"
    }

    // TODO add equals and hashCode when https://github.com/realm/realm-kotlin/issues/1097 is fixed
}

// ----------------------------------------------------------------------
// Values
// ----------------------------------------------------------------------

/**
 * The semantics of [MutableMap.values] establish a connection between these values and the map
 * itself. This collection represents the map's values as a [MutableCollection] of [V] values.
 *
 * The default implementation of `MutableMap.values` in Kotlin allows removals but no additions -
 * which makes sense since keys are nowhere to be found in this data structure.
 *
 * The implementation uses `realm_dictionary_to_results` internally, which (surprisingly) returns a
 * `realm_results_t` struct. Since the current `RealmResults` implementation is bound by
 * `RealmObject` we cannot use them to contain a map's values since maps of primitive values are
 * also supported. A separate implementation these `realm_results_t` was chosen over adapting the
 * current results infrastructure since the collection must be mutable too, and the current results
 * implementation is not.
 */
internal class RealmMapValues constructor(
    internal val resultsPointer: RealmResultsPointer,
    private val operator: MapOperator,
    private val parent: RealmObjectReference<*>?
) : AbstractMutableCollection() {

    override val size: Int
        get() = operator.size

    override fun add(element: V): Boolean =
        throw UnsupportedOperationException("Adding values to a dictionary through 'dictionary.values' is not allowed.")

    override fun addAll(elements: Collection): Boolean =
        throw UnsupportedOperationException("Adding values to a dictionary through 'dictionary.values' is not allowed.")

    override fun clear() = operator.clear()

    override fun iterator(): MutableIterator =
        object : RealmMapGenericIterator(operator) {
            @Suppress("UNCHECKED_CAST")
            override fun getNext(position: Int): V =
                operator.getValue(resultsPointer, position) as V
        }

    // Custom implementation to allow removal of byte arrays based on structural equality
    @Suppress("ReturnCount")
    override fun remove(element: V): Boolean {
        val it = iterator()
        if (element == null) {
            while (it.hasNext()) {
                if (it.next() == null) {
                    it.remove()
                    return true
                }
            }
        } else {
            while (it.hasNext()) {
                if (operator.areValuesEqual(element, it.next())) {
                    it.remove()
                    return true
                }
            }
        }
        return false
    }

    // Custom implementation to allow removal of byte arrays based on structural equality
    override fun removeAll(elements: Collection): Boolean =
        elements.fold(false) { accumulator, value ->
            remove(value) or accumulator
        }

    // Custom implementation to allow removal of byte arrays based on structural equality
    @Suppress("NestedBlockDepth")
    override fun retainAll(elements: Collection): Boolean {
        var modified = false
        val it = iterator()
        while (it.hasNext()) {
            val next = it.next()
            if (next is ByteArray) {
                val otherIterator = elements.iterator()
                while (otherIterator.hasNext()) {
                    val otherNext = otherIterator.next()
                    if (!next.contentEquals(otherNext as ByteArray?)) {
                        it.remove()
                        modified = true
                        continue // Avoid looping on an already deleted element
                    }
                }
            } else {
                if (!elements.contains(next)) {
                    it.remove()
                    modified = true
                }
            }
        }
        return modified
    }

    override fun toString(): String {
        val (owner, version, objKey) = parent?.run {
            Triple(
                className,
                owner.version().version,
                RealmInterop.realm_object_get_key(parent.objectPointer).key
            )
        } ?: Triple("null", operator.realmReference.owner.version(), "null")
        return "RealmDictionary.values{size=$size,owner=$owner,objKey=$objKey,version=$version}"
    }

    // TODO add equals and hashCode when https://github.com/realm/realm-kotlin/issues/1097 is fixed
}

// ----------------------------------------------------------------------
// Iterator
// ----------------------------------------------------------------------

/**
 * Base iterator used by [RealmDictionary.keys], [RealmDictionary.values] and
 * [RealmDictionary.entries]. Upon calling [next] the iterator used by `keys` returns a [K],
 * `entries` returns a [MutableMap.MutableEntry] whereas the one used by `values` returns a [T].
 */
internal abstract class RealmMapGenericIterator(
    protected val operator: MapOperator
) : MutableIterator {

    private var expectedModCount = operator.modCount // Current modifications in the map
    private var cursor = 0 // The position returned by next()
    private var lastReturned = -1 // The last known returned position

    abstract fun getNext(position: Int): T

    override fun hasNext(): Boolean {
        checkConcurrentModification()

        return cursor < operator.size
    }

    override fun remove() {
        checkConcurrentModification()

        if (operator.size == 0) {
            throw NoSuchElementException("Could not remove last element returned by the iterator: dictionary is empty.")
        }
        if (lastReturned < 0) {
            throw IllegalStateException("Could not remove last element returned by the iterator: iterator never returned an element.")
        }

        val erased = getterScope {
            val keyValuePair = operator.getEntry(lastReturned)
            operator.erase(keyValuePair.first)
                .second
                .also {
                    if (lastReturned < cursor) {
                        cursor -= 1
                    }
                    lastReturned = -1
                }
        }
        expectedModCount = operator.modCount
        if (!erased) {
            throw NoSuchElementException("Could not remove last element returned by the iterator: was there an element to remove?")
        }
    }

    override fun next(): T {
        checkConcurrentModification()

        val position = cursor
        if (position >= operator.size) {
            throw IndexOutOfBoundsException("Cannot access index $position when size is ${operator.size}. Remember to check hasNext() before using next().")
        }
        val next = getNext(position)
        lastReturned = position
        cursor = position + 1
        return next
    }

    private fun checkConcurrentModification() {
        if (operator.modCount != expectedModCount) {
            throw ConcurrentModificationException("The underlying RealmDictionary was modified while iterating over its entry set.")
        }
    }
}

// ----------------------------------------------------------------------
// Entry set
// ----------------------------------------------------------------------

/**
 * This class implements the typealias [RealmMapEntrySet] which matches
 * `MutableSet>`. This class allows operating on a [ManagedRealmMap]
 * in the form of a [Set] of [MutableMap.MutableEntry] values.
 *
 * Deletions are supported by the default semantics in JVM and K/N. These two operations are
 * equivalent:
 * ```
 * dictionary.remove(myKey)
 * dictionary.entries.remove(realmDictionaryEntryOf(myKey, myValue)) // implies knowing myValue
 * ```
 *
 * Default semantics forbid addition operations though. This is due to `AbstractCollection` not
 * having implemented this functionality both in JVM and K/N:
 * ```
 * dictionary.entries.add(SimpleEntry(myKey, myValue)) // throws UnsupportedOperationException
 * ```
 *
 * However, these semantics don't pose a problem for `RealmMap`s. The [add] function behaves in the
 * same way [RealmDictionary.put] does:
 * ```
 * // these two operations are equivalent and result in [myKey, myValue] being added to dictionary
 * dictionary[myKey] = myValue
 * dictionary.entries.add(realmMapEntryOf(myKey, myValue))
 * ```
 *
 * All other [Map] operations are funneled through the corresponding [MapOperator] and are available
 * from this class. Some of these operations leverage default implementations in
 * [AbstractMutableSet].
 */
internal class RealmMapEntrySetImpl constructor(
    private val nativePointer: RealmMapPointer,
    private val operator: MapOperator,
    private val parent: RealmObjectReference<*>?
) : AbstractMutableSet>(), RealmMapEntrySet {

    override val size: Int
        get() = RealmInterop.realm_dictionary_size(nativePointer).toInt()

    override fun add(element: MutableMap.MutableEntry): Boolean =
        operator.insert(element.key, element.value).second

    override fun addAll(elements: Collection>): Boolean =
        elements.fold(false) { accumulator, entry ->
            (operator.insert(entry.key, entry.value).second) or accumulator
        }

    override fun clear() = operator.clear()

    override fun iterator(): MutableIterator> =
        object : RealmMapGenericIterator>(operator) {
            @Suppress("UNCHECKED_CAST")
            override fun getNext(position: Int): MutableMap.MutableEntry {
                val pair = operator.getEntry(position)
                return ManagedRealmMapEntry(
                    pair.first,
                    operator
                ) as MutableMap.MutableEntry
            }
        }

    override fun remove(element: MutableMap.MutableEntry): Boolean =
        operator.get(element.key).let { value ->
            when (operator.areValuesEqual(value, element.value)) {
                true -> operator.erase(element.key).second
                false -> false
            }
        }

    override fun removeAll(elements: Collection>): Boolean =
        elements.fold(false) { accumulator, entry ->
            remove(entry) or accumulator
        }

    override fun toString(): String {
        val (owner, version, objKey) = parent?.run {
            Triple(
                className,
                owner.version().version,
                RealmInterop.realm_object_get_key(parent.objectPointer).key
            )
        } ?: Triple("null", operator.realmReference.owner.version(), "null")
        return "RealmDictionary.entries{size=$size,owner=$owner,objKey=$objKey,version=$version}"
    }

    // TODO add equals and hashCode when https://github.com/realm/realm-kotlin/issues/1097 is fixed
}

/**
 * Naive implementation of [MutableMap.MutableEntry] for adding new elements to a [RealmMap] via the
 * [RealmMapEntrySet] produced by `RealmMap.entries`.
 */
internal class UnmanagedRealmMapEntry constructor(
    override val key: K,
    value: V
) : MutableMap.MutableEntry {

    private var _value = value

    override val value: V
        get() = _value

    override fun setValue(newValue: V): V {
        val oldValue = this._value
        this._value = newValue
        return oldValue
    }

    override fun toString(): String = "UnmanagedRealmMapEntry{$key,$value}"
    override fun hashCode(): Int = key.hashCode() xor value.hashCode()
    override fun equals(other: Any?): Boolean {
        if (other !is Map.Entry<*, *>) return false

        // Byte arrays are compared at a structural level
        if (this.value is ByteArray && other.value is ByteArray) {
            val thisByteArray = this.value as ByteArray
            val otherByteArray = other.value as ByteArray
            if (this.key == other.key && thisByteArray.contentEquals(otherByteArray)) {
                return true
            }
            return false
        }

        return (this.key == other.key) && (this.value == other.value)
    }
}

/**
 * Implementation of a managed [MutableMap.MutableEntry] returned by the [Iterator] from a
 * [ManagedRealmMap] [RealmMapEntrySet]. It is possible to modify the [value] of the entry. Doing so
 * results in the managed `RealmMap` being updated as well.
 */
internal class ManagedRealmMapEntry constructor(
    override val key: K,
    private val operator: MapOperator
) : MutableMap.MutableEntry {

    override val value: V?
        get() = operator.get(key)

    override fun setValue(newValue: V?): V? {
        val previousValue = operator.get(key)
        operator.insert(key, newValue)
        return previousValue
    }

    override fun toString(): String = "ManagedRealmMapEntry{$key,$value}"

    override fun hashCode(): Int = key.hashCode() xor value.hashCode()

    // TODO Compare by key and value with a special case for byte arrays until equality is reworked
    //  properly in https://github.com/realm/realm-kotlin/issues/1097.
    override fun equals(other: Any?): Boolean {
        if (other !is Map.Entry<*, *>) return false

        // Byte arrays are compared at a structural level
        if (this.value is ByteArray && other.value is ByteArray) {
            val thisByteArray = this.value as ByteArray
            val otherByteArray = other.value as ByteArray
            if (this.key == other.key && thisByteArray.contentEquals(otherByteArray)) {
                return true
            }
            return false
        }

        return (this.key == other.key) && (this.value == other.value)
    }
}

// ----------------------------------------------------------------------
// Internal type alias and helpers for factory functions
// ----------------------------------------------------------------------

internal typealias RealmMapEntrySet = MutableSet>

internal typealias RealmMapMutableEntry = MutableMap.MutableEntry

internal fun  realmMapEntryOf(pair: Pair): RealmMapMutableEntry =
    UnmanagedRealmMapEntry(pair.first, pair.second)

internal fun  realmMapEntryOf(key: K, value: V): RealmMapMutableEntry =
    UnmanagedRealmMapEntry(key, value)

internal fun  realmMapEntryOf(entry: Map.Entry): RealmMapMutableEntry =
    UnmanagedRealmMapEntry(entry.key, entry.value)

internal fun  Array>.asRealmDictionary(): RealmDictionary =
    UnmanagedRealmDictionary().apply { putAll(this@asRealmDictionary) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy