commonMain.io.realm.kotlin.internal.RealmListInternal.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of library-base-jvm Show documentation
Show all versions of library-base-jvm Show documentation
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.
/*
* Copyright 2021 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_list_get
import io.realm.kotlin.internal.interop.RealmInterop.realm_list_set_embedded
import io.realm.kotlin.internal.interop.RealmKeyPathArrayPointer
import io.realm.kotlin.internal.interop.RealmListPointer
import io.realm.kotlin.internal.interop.RealmNotificationTokenPointer
import io.realm.kotlin.internal.interop.RealmObjectInterop
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.ListChange
import io.realm.kotlin.notifications.internal.DeletedListImpl
import io.realm.kotlin.notifications.internal.InitialListImpl
import io.realm.kotlin.notifications.internal.UpdatedListImpl
import io.realm.kotlin.query.RealmQuery
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmAny
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.Flow
import kotlin.reflect.KClass
internal const val INDEX_NOT_FOUND = io.realm.kotlin.internal.interop.INDEX_NOT_FOUND
/**
* Implementation for unmanaged lists, backed by a [MutableList].
*/
internal class UnmanagedRealmList(
private val backingList: MutableList = mutableListOf()
) : RealmList, InternalDeleteable, MutableList by backingList {
override fun asFlow(keyPaths: List?): Flow> =
throw UnsupportedOperationException("Unmanaged lists cannot be observed.")
override fun delete() {
throw UnsupportedOperationException("Unmanaged lists cannot be deleted.")
}
override fun toString(): String = "UnmanagedRealmList{${joinToString()}}"
override fun equals(other: Any?): Boolean = backingList == other
override fun hashCode(): Int = backingList.hashCode()
}
/**
* Implementation for managed lists, backed by Realm.
*/
internal class ManagedRealmList(
internal val parent: RealmObjectReference<*>?,
internal val nativePointer: RealmListPointer,
val operator: ListOperator,
) : AbstractMutableList(), RealmList, InternalDeleteable, CoreNotifiable, ListChange>, Versioned by operator.realmReference {
override val size: Int
get() {
operator.realmReference.checkClosed()
return RealmInterop.realm_list_size(nativePointer).toInt()
}
override fun get(index: Int): E {
operator.realmReference.checkClosed()
return operator.get(index)
}
override fun contains(element: E): Boolean {
return operator.contains(element)
}
override fun indexOf(element: E): Int {
return operator.indexOf(element)
}
override fun add(index: Int, element: E) {
operator.insert(index, element)
}
override fun remove(element: E): Boolean {
return operator.remove(element)
}
// We need explicit overrides of these to ensure that we capture duplicate references to the
// same unmanaged object in our internal import caching mechanism
override fun addAll(elements: Collection): Boolean = operator.insertAll(size, elements)
// We need explicit overrides of these to ensure that we capture duplicate references to the
// same unmanaged object in our internal import caching mechanism
override fun addAll(index: Int, elements: Collection): Boolean {
checkPositionIndex(index, size)
return operator.insertAll(index, elements)
}
override fun clear() {
operator.realmReference.checkClosed()
RealmInterop.realm_list_clear(nativePointer)
}
override fun removeAt(index: Int): E = get(index).also {
operator.realmReference.checkClosed()
RealmInterop.realm_list_erase(nativePointer, index.toLong())
}
override fun set(index: Int, element: E): E {
operator.realmReference.checkClosed()
return operator.set(index, element)
}
override fun asFlow(keyPaths: List?): Flow> {
operator.realmReference.checkClosed()
val keyPathInfo = keyPaths?.let {
Validation.isType>(operator, "Keypaths are only supported for lists of objects.")
Pair(operator.classKey, keyPaths)
}
return operator.realmReference.owner.registerObserver(this, keyPathInfo)
}
override fun freeze(frozenRealm: RealmReference): ManagedRealmList? {
return RealmInterop.realm_list_resolve_in(nativePointer, frozenRealm.dbPointer)?.let {
ManagedRealmList(parent, it, operator.copy(frozenRealm, it))
}
}
override fun thaw(liveRealm: RealmReference): ManagedRealmList? {
return RealmInterop.realm_list_resolve_in(nativePointer, liveRealm.dbPointer)?.let {
ManagedRealmList(parent, it, operator.copy(liveRealm, it))
}
}
override fun registerForNotification(
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback
): RealmNotificationTokenPointer {
return RealmInterop.realm_list_add_notification_callback(nativePointer, keyPaths, callback)
}
override fun changeFlow(scope: ProducerScope>): ChangeFlow, ListChange> =
RealmListChangeFlow(scope)
// TODO from LifeCycle interface
override fun isValid(): Boolean =
!nativePointer.isReleased() && RealmInterop.realm_list_is_valid(nativePointer)
override fun delete() = RealmInterop.realm_list_remove_all(nativePointer)
}
internal class RealmListChangeFlow(producerScope: ProducerScope>) :
ChangeFlow, ListChange>(producerScope) {
override fun initial(frozenRef: ManagedRealmList): ListChange =
InitialListImpl(frozenRef)
override fun update(
frozenRef: ManagedRealmList,
change: RealmChangesPointer
): ListChange {
val builder = ListChangeSetBuilderImpl(change)
return UpdatedListImpl(frozenRef, builder.build())
}
override fun delete(): ListChange = DeletedListImpl(UnmanagedRealmList())
}
internal fun ManagedRealmList.query(
query: String,
args: Array
): RealmQuery {
val operator: BaseRealmObjectListOperator = operator as BaseRealmObjectListOperator
val queryPointer = inputScope {
val queryArgs = convertToQueryArgs(args)
try {
RealmInterop.realm_query_parse_for_list(
[email protected],
query,
queryArgs
)
} catch (e: IndexOutOfBoundsException) {
throw IllegalArgumentException(e.message, e.cause)
}
}
// 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 lists")
return ObjectBoundQuery(
parent,
ObjectQuery(
operator.realmReference,
operator.classKey,
operator.clazz,
operator.mediator,
queryPointer,
)
)
}
// Cloned from https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/collections/AbstractList.kt
private fun checkPositionIndex(index: Int, size: Int) {
if (index < 0 || index > size) {
throw IndexOutOfBoundsException("index: $index, size: $size")
}
}
/**
* Operator interface abstracting the connection between the API and and the interop layer.
*/
internal interface ListOperator : CollectionOperator {
override val nativePointer: RealmListPointer
fun get(index: Int): E
fun contains(element: E): Boolean = indexOf(element) != -1
fun indexOf(element: E): Int
// TODO OPTIMIZE We technically don't need update policy and cache for primitive lists but right now RealmObjectHelper.assign doesn't know how to differentiate the calls to the operator
fun insert(
index: Int,
element: E,
updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
cache: UnmanagedToManagedObjectCache = mutableMapOf()
)
fun remove(element: E): Boolean = when (val index = indexOf(element)) {
-1 -> false
else -> {
RealmInterop.realm_list_erase(nativePointer, index.toLong())
true
}
}
fun insertAll(
index: Int,
elements: Collection,
updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
cache: UnmanagedToManagedObjectCache = mutableMapOf()
): Boolean {
@Suppress("VariableNaming")
var _index = index
var changed = false
for (e in elements) {
insert(_index++, e, updatePolicy, cache)
changed = true
}
return changed
}
fun set(
index: Int,
element: E,
updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
cache: UnmanagedToManagedObjectCache = mutableMapOf()
): E
// Creates a new operator from an existing one to be able to issue frozen/thawed instances of the list operating on the new version of the list
fun copy(realmReference: RealmReference, nativePointer: RealmListPointer): ListOperator
}
internal class PrimitiveListOperator(
override val mediator: Mediator,
override val realmReference: RealmReference,
val realmValueConverter: RealmValueConverter,
override val nativePointer: RealmListPointer
) : ListOperator {
@Suppress("UNCHECKED_CAST")
override fun get(index: Int): E {
return getterScope {
val transport = realm_list_get(nativePointer, index.toLong())
with(realmValueConverter) {
realmValueToPublic(transport) as E
}
}
}
override fun indexOf(element: E): Int {
inputScope {
with(realmValueConverter) {
return RealmInterop.realm_list_find(nativePointer, publicToRealmValue(element)).toInt()
}
}
}
override fun insert(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
) {
inputScope {
with(realmValueConverter) {
val transport = publicToRealmValue(element)
RealmInterop.realm_list_add(nativePointer, index.toLong(), transport)
}
}
}
override fun set(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
): E {
return get(index).also {
inputScope {
with(realmValueConverter) {
val transport = publicToRealmValue(element)
RealmInterop.realm_list_set(nativePointer, index.toLong(), transport)
}
}
}
}
override fun copy(
realmReference: RealmReference,
nativePointer: RealmListPointer
): ListOperator =
PrimitiveListOperator(mediator, realmReference, realmValueConverter, nativePointer)
}
internal fun realmAnyListOperator(
mediator: Mediator,
realm: RealmReference,
nativePointer: RealmListPointer,
issueDynamicObject: Boolean = false,
issueDynamicMutableObject: Boolean = false,
): RealmAnyListOperator = RealmAnyListOperator(
mediator,
realm,
nativePointer,
issueDynamicObject = issueDynamicObject,
issueDynamicMutableObject = issueDynamicMutableObject
)
@Suppress("LongParameterList")
internal class RealmAnyListOperator(
override val mediator: Mediator,
override val realmReference: RealmReference,
override val nativePointer: RealmListPointer,
val updatePolicy: UpdatePolicy = UpdatePolicy.ALL,
val cache: UnmanagedToManagedObjectCache = mutableMapOf(),
val issueDynamicObject: Boolean,
val issueDynamicMutableObject: Boolean
) : ListOperator {
override fun get(index: Int): RealmAny? = getterScope {
val transport = realm_list_get(nativePointer, index.toLong())
return realmValueToRealmAny(
transport, null, mediator, realmReference,
issueDynamicObject,
issueDynamicMutableObject,
{ RealmInterop.realm_list_get_list(nativePointer, index.toLong()) },
{ RealmInterop.realm_list_get_dictionary(nativePointer, index.toLong()) }
)
}
override fun indexOf(element: RealmAny?): Int {
// Unmanaged objects are never found in a managed collections
if (element?.type == RealmAny.Type.OBJECT) {
if (!element.asRealmObject().isManaged()) return -1
}
return inputScope {
val transport = realmAnyToRealmValueWithoutImport(element)
RealmInterop.realm_list_find(nativePointer, transport).toInt()
}
}
override fun insert(
index: Int,
element: RealmAny?,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
) {
inputScope {
realmAnyHandler(
value = element,
primitiveValueAsRealmValueHandler = { realmValue: RealmValue ->
RealmInterop.realm_list_add(nativePointer, index.toLong(), realmValue)
},
referenceAsRealmAnyHandler = { realmValue: RealmAny ->
val obj = when (issueDynamicObject) {
true -> realmValue.asRealmObject()
false -> realmValue.asRealmObject()
}
val objRef =
realmObjectToRealmReferenceWithImport(obj, mediator, realmReference, updatePolicy, cache)
RealmInterop.realm_list_add(nativePointer, index.toLong(), realmObjectTransport(objRef))
},
listAsRealmAnyHandler = { realmValue ->
val nativePointer = RealmInterop.realm_list_insert_list(nativePointer, index.toLong())
RealmInterop.realm_list_clear(nativePointer)
val operator = realmAnyListOperator(
mediator,
realmReference,
nativePointer,
issueDynamicObject, issueDynamicMutableObject
)
operator.insertAll(0, realmValue.asList(), updatePolicy, cache)
},
dictionaryAsRealmAnyHandler = { realmValue ->
val nativePointer = RealmInterop.realm_list_insert_dictionary(nativePointer, index.toLong())
RealmInterop.realm_dictionary_clear(nativePointer)
val operator =
realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject)
operator.putAll(realmValue.asDictionary(), updatePolicy, cache)
}
)
}
}
override fun set(
index: Int,
element: RealmAny?,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
): RealmAny? {
return get(index).also {
inputScope {
realmAnyHandler(
value = element,
primitiveValueAsRealmValueHandler = { realmValue: RealmValue ->
RealmInterop.realm_list_set(nativePointer, index.toLong(), realmValue)
},
referenceAsRealmAnyHandler = { realmValue ->
val objRef =
realmObjectToRealmReferenceWithImport(realmValue.asRealmObject(), mediator, realmReference, updatePolicy, cache)
RealmInterop.realm_list_set(nativePointer, index.toLong(), realmObjectTransport(objRef))
},
listAsRealmAnyHandler = { realmValue ->
val nativePointer = RealmInterop.realm_list_set_list(nativePointer, index.toLong())
RealmInterop.realm_list_clear(nativePointer)
val operator = realmAnyListOperator(
mediator,
realmReference,
nativePointer,
issueDynamicObject, issueDynamicMutableObject
)
operator.insertAll(0, realmValue.asList(), updatePolicy, cache)
},
dictionaryAsRealmAnyHandler = { realmValue ->
val nativePointer = RealmInterop.realm_list_set_dictionary(nativePointer, index.toLong())
RealmInterop.realm_dictionary_clear(nativePointer)
val operator =
realmAnyMapOperator(mediator, realmReference, nativePointer, issueDynamicObject, issueDynamicMutableObject)
operator.putAll(realmValue.asDictionary(), updatePolicy, cache)
}
)
}
}
}
override fun copy(
realmReference: RealmReference,
nativePointer: RealmListPointer
): ListOperator =
RealmAnyListOperator(mediator, realmReference, nativePointer, issueDynamicObject = issueDynamicObject, issueDynamicMutableObject = issueDynamicMutableObject)
}
internal abstract class BaseRealmObjectListOperator (
override val mediator: Mediator,
override val realmReference: RealmReference,
override val nativePointer: RealmListPointer,
val clazz: KClass,
val classKey: ClassKey,
) : ListOperator {
@Suppress("UNCHECKED_CAST")
override fun get(index: Int): E {
return getterScope {
val transport = realm_list_get(nativePointer, index.toLong())
realmValueToRealmObject(transport, clazz, mediator, realmReference) as E
}
}
override fun indexOf(element: E): Int {
// Unmanaged objects are never found in a managed collections
element?.also {
if (!(it as RealmObjectInternal).isManaged()) return -1
}
return inputScope {
val objRef = realmObjectToRealmReferenceOrError(element as BaseRealmObject?)
val transport = realmObjectTransport(objRef as RealmObjectInterop)
RealmInterop.realm_list_find(nativePointer, transport).toInt()
}
}
}
internal class RealmObjectListOperator(
mediator: Mediator,
realmReference: RealmReference,
nativePointer: RealmListPointer,
clazz: KClass,
classKey: ClassKey,
) : BaseRealmObjectListOperator(mediator, realmReference, nativePointer, clazz, classKey) {
override fun insert(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
) {
inputScope {
val objRef = realmObjectToRealmReferenceWithImport(
element as BaseRealmObject?,
mediator,
realmReference,
updatePolicy,
cache
)
val transport = realmObjectTransport(objRef as RealmObjectInterop)
RealmInterop.realm_list_add(nativePointer, index.toLong(), transport)
}
}
override fun set(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
): E {
return inputScope {
val objRef = realmObjectToRealmReferenceWithImport(
element as BaseRealmObject?,
mediator,
realmReference,
updatePolicy,
cache
)
val transport = realmObjectTransport(objRef as RealmObjectInterop)
val originalValue = get(index)
RealmInterop.realm_list_set(nativePointer, index.toLong(), transport)
originalValue
}
}
override fun copy(
realmReference: RealmReference,
nativePointer: RealmListPointer
): ListOperator {
return RealmObjectListOperator(
mediator,
realmReference,
nativePointer,
clazz,
classKey
)
}
}
internal class EmbeddedRealmObjectListOperator(
mediator: Mediator,
realmReference: RealmReference,
nativePointer: RealmListPointer,
clazz: KClass,
classKey: ClassKey,
) : BaseRealmObjectListOperator(mediator, realmReference, nativePointer, clazz, classKey) {
@Suppress("UNCHECKED_CAST")
override fun insert(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
) {
val embedded = RealmInterop.realm_list_insert_embedded(nativePointer, index.toLong())
val newObj = embedded.toRealmObject(
element::class as KClass,
mediator,
realmReference
)
RealmObjectHelper.assign(newObj, element, updatePolicy, cache)
}
override fun set(
index: Int,
element: E,
updatePolicy: UpdatePolicy,
cache: UnmanagedToManagedObjectCache
): E {
return inputScope {
// We cannot return the old object as it is deleted when losing its parent and cannot
// return null as this is not allowed for lists with non-nullable elements, so just return
// the newly created object even though it goes against the list API.
val embedded = realm_list_set_embedded(nativePointer, index.toLong())
val newEmbeddedRealmObject = realmValueToRealmObject(embedded, clazz, mediator, realmReference) as E
RealmObjectHelper.assign(newEmbeddedRealmObject, element, updatePolicy, cache)
newEmbeddedRealmObject
}
}
override fun copy(
realmReference: RealmReference,
nativePointer: RealmListPointer
): EmbeddedRealmObjectListOperator {
return EmbeddedRealmObjectListOperator(
mediator,
realmReference,
nativePointer,
clazz,
classKey
)
}
}
internal fun Array.asRealmList(): RealmList =
UnmanagedRealmList().apply { addAll(this@asRealmList) }
© 2015 - 2024 Weber Informatics LLC | Privacy Policy