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

commonMain.io.realm.kotlin.internal.Converters.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 2022 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.
 */
@file:Suppress("NOTHING_TO_INLINE", "OVERRIDE_BY_INLINE")

package io.realm.kotlin.internal

import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.annotations.ExperimentalGeoSpatialApi
import io.realm.kotlin.dynamic.DynamicMutableRealmObject
import io.realm.kotlin.dynamic.DynamicRealmObject
import io.realm.kotlin.ext.asRealmObject
import io.realm.kotlin.internal.interop.MemTrackingAllocator
import io.realm.kotlin.internal.interop.RealmListPointer
import io.realm.kotlin.internal.interop.RealmMapPointer
import io.realm.kotlin.internal.interop.RealmObjectInterop
import io.realm.kotlin.internal.interop.RealmQueryArgument
import io.realm.kotlin.internal.interop.RealmQueryArgumentList
import io.realm.kotlin.internal.interop.RealmQueryListArgument
import io.realm.kotlin.internal.interop.RealmQuerySingleArgument
import io.realm.kotlin.internal.interop.RealmValue
import io.realm.kotlin.internal.interop.Timestamp
import io.realm.kotlin.internal.interop.ValueType
import io.realm.kotlin.types.BaseRealmObject
import io.realm.kotlin.types.RealmAny
import io.realm.kotlin.types.RealmInstant
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.RealmUUID
import io.realm.kotlin.types.geo.GeoBox
import io.realm.kotlin.types.geo.GeoCircle
import io.realm.kotlin.types.geo.GeoPolygon
import org.mongodb.kbson.BsonObjectId
import org.mongodb.kbson.Decimal128
import kotlin.reflect.KClass

// This file contains all code for converting public API values into values passed to the C-API.
// This conversion is split into a two-step operation to:
// - Maximize code reuse of individual conversion steps to ensure consistency throughout the
//   compiler plugin injected code and the library
// - Accommodate future public (or internal default) type converters
// The two steps are:
// 1. Converting public user facing types to internal "storage types" which are library specific
//    Kotlin types mimicing the various underlying core types.
// 2. Converting from the "library storage types" into the C-API intepretable corresponding value
// The "C-API values" are passed in and out of the C-API as RealmValue that is just a `value class`-
// wrapper around `Any` that is converted into `realm_value_t` in the `cinterop` layer.

/**
 * Interface for overall conversion between public types and C-API input/output types. This is the
 * main abstraction of conversion used throughout the library.
 */
internal interface RealmValueConverter {
    fun MemTrackingAllocator.publicToRealmValue(value: T?): RealmValue
    fun realmValueToPublic(realmValue: RealmValue): T?
}

/**
 * Interface for converting between public user facing type and library storage types.
 *
 * This corresponds to step 1. of the overall conversion described in the top of this file.
 */
internal interface PublicConverter {
    fun fromPublic(value: T?): S?
    fun toPublic(value: S?): T?
}

/**
 * Interface for converting between library storage types and C-API input/output values.
 *
 * This corresponds to step 2. of the overall conversion described in the top of this file.
 */
internal interface StorageTypeConverter {
    fun fromRealmValue(realmValue: RealmValue): T?
    fun MemTrackingAllocator.toRealmValue(value: T?): RealmValue
}

// Top level methods to allow inlining from compiler plugin
// No need to handle null values here since it's handled by the accessors
public inline fun realmValueToLong(transport: RealmValue): Long = transport.getLong()
public inline fun realmValueToBoolean(transport: RealmValue): Boolean = transport.getBoolean()
public inline fun realmValueToString(transport: RealmValue): String = transport.getString()
public inline fun realmValueToByteArray(transport: RealmValue): ByteArray = transport.getByteArray()
public inline fun realmValueToRealmInstant(transport: RealmValue): RealmInstant =
    RealmInstantImpl(transport.getTimestamp())
public inline fun realmValueToFloat(transport: RealmValue): Float = transport.getFloat()
public inline fun realmValueToDouble(transport: RealmValue): Double = transport.getDouble()
public inline fun realmValueToObjectId(transport: RealmValue): BsonObjectId =
    BsonObjectId(transport.getObjectIdBytes())

public inline fun realmValueToRealmUUID(transport: RealmValue): RealmUUID = RealmUUIDImpl(transport.getUUIDBytes())
@OptIn(ExperimentalUnsignedTypes::class)
public inline fun realmValueToDecimal128(transport: RealmValue): Decimal128 =
    transport.getDecimal128Array().let { Decimal128.fromIEEE754BIDEncoding(it[1], it[0]) }

@Suppress("ComplexMethod", "NestedBlockDepth", "LongParameterList")
internal inline fun realmValueToRealmAny(
    realmValue: RealmValue,
    parent: RealmObjectReference<*>?,
    mediator: Mediator,
    owner: RealmReference,
    issueDynamicObject: Boolean,
    issueDynamicMutableObject: Boolean,
    getListFunction: () -> RealmListPointer = { error("Cannot handled embedded lists") },
    getDictionaryFunction: () -> RealmMapPointer = { error("Cannot handled embedded dictionaries") },
): RealmAny? {
    return when (realmValue.isNull()) {
        true -> null
        false -> when (val type = realmValue.getType()) {
            ValueType.RLM_TYPE_NULL -> null
            ValueType.RLM_TYPE_INT -> RealmAny.create(realmValue.getLong())
            ValueType.RLM_TYPE_BOOL -> RealmAny.create(realmValue.getBoolean())
            ValueType.RLM_TYPE_STRING -> RealmAny.create(realmValue.getString())
            ValueType.RLM_TYPE_BINARY -> RealmAny.create(realmValue.getByteArray())
            ValueType.RLM_TYPE_TIMESTAMP -> RealmAny.create(RealmInstantImpl(realmValue.getTimestamp()))
            ValueType.RLM_TYPE_FLOAT -> RealmAny.create(realmValue.getFloat())
            ValueType.RLM_TYPE_DOUBLE -> RealmAny.create(realmValue.getDouble())
            ValueType.RLM_TYPE_DECIMAL128 -> RealmAny.create(realmValueToDecimal128(realmValue))
            ValueType.RLM_TYPE_OBJECT_ID ->
                RealmAny.create(BsonObjectId(realmValue.getObjectIdBytes()))
            ValueType.RLM_TYPE_UUID -> RealmAny.create(RealmUUIDImpl(realmValue.getUUIDBytes()))
            ValueType.RLM_TYPE_LINK -> {
                if (issueDynamicObject) {
                    val clazz = when (issueDynamicMutableObject) {
                        true -> DynamicMutableRealmObject::class
                        false -> DynamicRealmObject::class
                    }
                    val realmObject = realmValueToRealmObject(realmValue, clazz, mediator, owner)
                    RealmAny.create(realmObject!!)
                } else {
                    val clazz = owner.schemaMetadata
                        .get(realmValue.getLink().classKey)
                        ?.clazz
                        ?: throw IllegalArgumentException("The object class is not present in the current schema - are you using an outdated schema version?")
                    val realmObject = realmValueToRealmObject(realmValue, clazz, mediator, owner)
                    @Suppress("UNCHECKED_CAST")
                    RealmAny.create(realmObject!! as RealmObject, clazz as KClass)
                }
            }
            ValueType.RLM_TYPE_LIST -> {
                val nativePointer = getListFunction()
                val operator = realmAnyListOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject)
                RealmAny.create(ManagedRealmList(parent, nativePointer, operator))
            }
            ValueType.RLM_TYPE_DICTIONARY -> {
                val nativePointer = getDictionaryFunction()
                val operator = realmAnyMapOperator(mediator, owner, nativePointer, issueDynamicObject, issueDynamicMutableObject)
                RealmAny.create(ManagedRealmDictionary(parent, nativePointer, operator))
            }
            else -> throw IllegalArgumentException("Unsupported type: ${type.name}")
        }
    }
}

@Suppress("LongParameterList")
internal fun  MemTrackingAllocator.realmAnyHandler(
    value: RealmAny?,
    primitiveValueAsRealmValueHandler: (RealmValue) -> T = { throw IllegalArgumentException("Operation not support for primitive values") },
    referenceAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for objects") },
    listAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for lists") },
    dictionaryAsRealmAnyHandler: (RealmAny) -> T = { throw IllegalArgumentException("Operation not support for dictionaries") },
): T {
    return when (value?.type) {
        null ->
            primitiveValueAsRealmValueHandler(nullTransport())

        io.realm.kotlin.types.RealmAny.Type.INT,
        io.realm.kotlin.types.RealmAny.Type.BOOL,
        io.realm.kotlin.types.RealmAny.Type.STRING,
        io.realm.kotlin.types.RealmAny.Type.BINARY,
        io.realm.kotlin.types.RealmAny.Type.TIMESTAMP,
        io.realm.kotlin.types.RealmAny.Type.FLOAT,
        io.realm.kotlin.types.RealmAny.Type.DOUBLE,
        io.realm.kotlin.types.RealmAny.Type.DECIMAL128,
        io.realm.kotlin.types.RealmAny.Type.OBJECT_ID,
        io.realm.kotlin.types.RealmAny.Type.UUID ->
            primitiveValueAsRealmValueHandler(realmAnyPrimitiveToRealmValue(value))

        io.realm.kotlin.types.RealmAny.Type.OBJECT -> {
            referenceAsRealmAnyHandler(value)
        }

        io.realm.kotlin.types.RealmAny.Type.LIST -> {
            listAsRealmAnyHandler(value)
        }

        io.realm.kotlin.types.RealmAny.Type.DICTIONARY -> {
            dictionaryAsRealmAnyHandler(value)
        }
    }
}

/**
 * Composite converters that combines a [PublicConverter] and a [StorageTypeConverter] into a
 * [RealmValueConverter].
 */
internal abstract class CompositeConverter :
    RealmValueConverter, PublicConverter, StorageTypeConverter {
    override fun MemTrackingAllocator.publicToRealmValue(value: T?): RealmValue {
        val storageValue = fromPublic(value)
        return toRealmValue(storageValue)
    }
    override fun realmValueToPublic(realmValue: RealmValue): T? {
        val fromRealmValue = fromRealmValue(realmValue)
        return toPublic(fromRealmValue)
    }
}

// RealmValueConverter with default pass-through public-to-storage-type implementation
@Suppress("UNCHECKED_CAST")
internal abstract class PassThroughPublicConverter : CompositeConverter() {
    override fun fromPublic(value: T?): T? = passthrough(value) as T?
    override fun toPublic(value: T?): T? = passthrough(value) as T?
}
// Top level methods to allow inlining from compiler plugin
public inline fun passthrough(value: Any?): Any? = value

// Passthrough converters
internal object LongConverter : PassThroughPublicConverter() {
    override fun fromRealmValue(realmValue: RealmValue): Long? =
        if (realmValue.isNull()) null else realmValue.getLong()
    override fun MemTrackingAllocator.toRealmValue(value: Long?): RealmValue =
        longTransport(value)
}

internal object BooleanConverter : PassThroughPublicConverter() {
    override fun fromRealmValue(realmValue: RealmValue): Boolean? =
        if (realmValue.isNull()) null else realmValue.getBoolean()
    override fun MemTrackingAllocator.toRealmValue(value: Boolean?): RealmValue =
        booleanTransport(value)
}

internal object StringConverter : PassThroughPublicConverter() {
    override fun fromRealmValue(realmValue: RealmValue): String? =
        if (realmValue.isNull()) null else realmValue.getString()
    override fun MemTrackingAllocator.toRealmValue(value: String?): RealmValue =
        stringTransport(value)
}

internal object FloatConverter : PassThroughPublicConverter() {
    override fun fromRealmValue(realmValue: RealmValue): Float? =
        if (realmValue.isNull()) null else realmValue.getFloat()
    override fun MemTrackingAllocator.toRealmValue(value: Float?): RealmValue =
        floatTransport(value)
}

internal object DoubleConverter : PassThroughPublicConverter() {
    override fun fromRealmValue(realmValue: RealmValue): Double? =
        if (realmValue.isNull()) null else realmValue.getDouble()
    override fun MemTrackingAllocator.toRealmValue(value: Double?): RealmValue =
        doubleTransport(value)
}

// Converter for Core INT storage type (i.e. Byte, Short, Int and Char public types )
internal interface CoreIntConverter : StorageTypeConverter {
    override fun fromRealmValue(realmValue: RealmValue): Long? =
        if (realmValue.isNull()) null else realmValue.getLong()
    override fun MemTrackingAllocator.toRealmValue(value: Long?): RealmValue =
        longTransport(value)
}

internal object ByteConverter : CoreIntConverter, CompositeConverter() {
    override inline fun fromPublic(value: Byte?): Long? = byteToLong(value)
    override inline fun toPublic(value: Long?): Byte? = longToByte(value)
}
// Top level methods to allow inlining from compiler plugin
public inline fun byteToLong(value: Byte?): Long? = value?.toLong()
public inline fun longToByte(value: Long?): Byte? = value?.toByte()

internal object CharConverter : CoreIntConverter, CompositeConverter() {
    override inline fun fromPublic(value: Char?): Long? = charToLong(value)
    override inline fun toPublic(value: Long?): Char? = longToChar(value)
}
// Top level methods to allow inlining from compiler plugin
public inline fun charToLong(value: Char?): Long? = value?.code?.toLong()
public inline fun longToChar(value: Long?): Char? = value?.toInt()?.toChar()

internal object ShortConverter : CoreIntConverter, CompositeConverter() {
    override inline fun fromPublic(value: Short?): Long? = shortToLong(value)
    override inline fun toPublic(value: Long?): Short? = longToShort(value)
}
// Top level methods to allow inlining from compiler plugin
public inline fun shortToLong(value: Short?): Long? = value?.toLong()
public inline fun longToShort(value: Long?): Short? = value?.toShort()

internal object IntConverter : CoreIntConverter, CompositeConverter() {
    override inline fun fromPublic(value: Int?): Long? = intToLong(value)
    override inline fun toPublic(value: Long?): Int? = longToInt(value)
}
// Top level methods to allow inlining from compiler plugin
public inline fun intToLong(value: Int?): Long? = value?.toLong()
public inline fun longToInt(value: Long?): Int? = value?.toInt()

internal object RealmInstantConverter : PassThroughPublicConverter() {
    override inline fun fromRealmValue(realmValue: RealmValue): RealmInstant? =
        if (realmValue.isNull()) null else realmValueToRealmInstant(realmValue)
    override inline fun MemTrackingAllocator.toRealmValue(value: RealmInstant?): RealmValue =
        timestampTransport(value?.let { it as Timestamp })
}

internal object ObjectIdConverter : PassThroughPublicConverter() {
    override inline fun fromRealmValue(realmValue: RealmValue): BsonObjectId? =
        if (realmValue.isNull()) null else realmValueToObjectId(realmValue)

    override inline fun MemTrackingAllocator.toRealmValue(value: BsonObjectId?): RealmValue =
        objectIdTransport(value?.toByteArray())
}
// Top level methods to allow inlining from compiler plugin

internal object RealmUUIDConverter : PassThroughPublicConverter() {
    override inline fun fromRealmValue(realmValue: RealmValue): RealmUUID? =
        if (realmValue.isNull()) null else realmValueToRealmUUID(realmValue)
    override inline fun MemTrackingAllocator.toRealmValue(value: RealmUUID?): RealmValue =
        uuidTransport(value?.bytes)
}

internal object ByteArrayConverter : PassThroughPublicConverter() {
    override inline fun fromRealmValue(realmValue: RealmValue): ByteArray? =
        if (realmValue.isNull()) null else realmValueToByteArray(realmValue)
    override inline fun MemTrackingAllocator.toRealmValue(value: ByteArray?): RealmValue =
        byteArrayTransport(value)
}

internal object Decimal128Converter : PassThroughPublicConverter() {
    override inline fun fromRealmValue(realmValue: RealmValue): Decimal128? =
        if (realmValue.isNull()) null else realmValueToDecimal128(realmValue)

    override inline fun MemTrackingAllocator.toRealmValue(value: Decimal128?): RealmValue =
        decimal128Transport(value)
}

internal val primitiveTypeConverters: Map, RealmValueConverter<*>> =
    mapOf, RealmValueConverter<*>>(
        Byte::class to ByteConverter,
        Char::class to CharConverter,
        Short::class to ShortConverter,
        Int::class to IntConverter,
        RealmInstant::class to RealmInstantConverter,
        RealmInstantImpl::class to RealmInstantConverter,
        BsonObjectId::class to ObjectIdConverter,
        RealmUUID::class to RealmUUIDConverter,
        RealmUUIDImpl::class to RealmUUIDConverter,
        ByteArray::class to ByteArrayConverter,
        String::class to StringConverter,
        Long::class to LongConverter,
        Boolean::class to BooleanConverter,
        Float::class to FloatConverter,
        Double::class to DoubleConverter,
        Decimal128::class to Decimal128Converter
    )

// Dynamic default primitive value converter to translate primary keys and query arguments to RealmValues
@Suppress("NestedBlockDepth")
internal object RealmValueArgumentConverter {
    fun MemTrackingAllocator.kAnyToPrimaryKeyRealmValue(value: Any?): RealmValue {
        return value?.let { value ->
            primitiveTypeConverters[value::class]?.let { converter ->
                @Suppress("UNCHECKED_CAST")
                with(converter as RealmValueConverter) {
                    publicToRealmValue(value)
                }
            } ?: throw IllegalArgumentException("Cannot use object '$value' of type '${value::class.simpleName}' as primary key argument")
        } ?: nullTransport()
    }

    fun MemTrackingAllocator.kAnyToRealmValueWithoutImport(value: Any?): RealmValue {
        return value?.let { value ->
            try {
                when (value) {
                    is RealmObject -> {
                        realmObjectTransport(realmObjectToRealmReferenceOrError(value))
                    }
                    is RealmAny ->
                        realmAnyToRealmValueWithoutImport(value)
                    else -> {
                        primitiveTypeConverters[value::class]?.let { converter ->
                            @Suppress("UNCHECKED_CAST")
                            with(converter as RealmValueConverter) {
                                publicToRealmValue(value)
                            }
                        }
                            ?: throw IllegalArgumentException("Cannot convert primitive type '$value' of type '${value::class.simpleName}' as query argument")
                    }
                }
            } catch (e: IllegalArgumentException) {
                throw IllegalArgumentException("Invalid query argument: ${e.message}", e)
            }
        } ?: nullTransport()
    }

    @OptIn(ExperimentalGeoSpatialApi::class)
    fun MemTrackingAllocator.convertQueryArg(value: Any?): RealmQueryArgument =
        when (value) {
            is Collection<*> -> {
                RealmQueryListArgument(
                    allocRealmValueList(value.size).apply {
                        value.mapIndexed { index: Int, element: Any? ->
                            set(index, kAnyToRealmValueWithoutImport(element))
                        }
                    }
                )
            }
            // Try to build a list from an iterator and convert the arguments as above
            is Iterable<*> -> {
                val args = value.iterator().asSequence().toList()
                RealmQueryListArgument(
                    allocRealmValueList(args.size).apply {
                        args.mapIndexed { index: Int, element: Any? ->
                            set(index, kAnyToRealmValueWithoutImport(element))
                        }
                    }
                )
            }
            is GeoBox,
            is GeoCircle,
            is GeoPolygon -> {
                // Hack support for geospatial arguments until we have propert C-API support.
                // See https://github.com/realm/realm-core/pull/6934
                RealmQuerySingleArgument(kAnyToRealmValueWithoutImport(value.toString()))
            }
            else -> {
                RealmQuerySingleArgument(kAnyToRealmValueWithoutImport(value))
            }
        }

    internal fun MemTrackingAllocator.convertToQueryArgs(
        queryArgs: Array
    ): RealmQueryArgumentList {
        return queryArgs.map {
            convertQueryArg(it)
        }.let {
            queryArgsOf(it)
        }
    }
}

/**
 * Tries to convert a [RealmValue] into a [RealmAny], it handles the cases for all primitive types
 * and leaves the other cases to an else block.
 */
@PublishedApi
internal inline fun RealmValue.asPrimitiveRealmAnyOrElse(
    elseBlock: RealmValue.() -> RealmAny?
): RealmAny? = when (getType()) {
    ValueType.RLM_TYPE_NULL -> null
    ValueType.RLM_TYPE_INT -> RealmAny.create(getLong())
    ValueType.RLM_TYPE_BOOL -> RealmAny.create(getBoolean())
    ValueType.RLM_TYPE_STRING -> RealmAny.create(getString())
    ValueType.RLM_TYPE_BINARY -> RealmAny.create(getByteArray())
    ValueType.RLM_TYPE_TIMESTAMP -> RealmAny.create(RealmInstantImpl(getTimestamp()))
    ValueType.RLM_TYPE_FLOAT -> RealmAny.create(getFloat())
    ValueType.RLM_TYPE_DOUBLE -> RealmAny.create(getDouble())
    ValueType.RLM_TYPE_DECIMAL128 -> RealmAny.create(realmValueToDecimal128(this))
    ValueType.RLM_TYPE_OBJECT_ID -> RealmAny.create(BsonObjectId(getObjectIdBytes()))
    ValueType.RLM_TYPE_UUID -> RealmAny.create(RealmUUIDImpl(getUUIDBytes()))
    else -> elseBlock()
}

/**
 * Used for converting RealmAny values to RealmValues suitable for query arguments and primary keys.
 * Importing objects isn't allowed here.
 */
internal inline fun MemTrackingAllocator.realmAnyToRealmValueWithoutImport(value: RealmAny?): RealmValue {
    return when (value) {
        null -> nullTransport()
        else -> when (value.type) {
            // We shouldn't be able to land here for primary key arguments!
            RealmAny.Type.OBJECT -> {
                val objRef = realmObjectToRealmReferenceOrError(value.asRealmObject())
                realmObjectTransport(objRef)
            }
            RealmAny.Type.LIST,
            RealmAny.Type.DICTIONARY ->
                throw IllegalArgumentException("Cannot pass unmanaged collections as input argument")
            else -> realmAnyPrimitiveToRealmValue(value)
        }
    }
}

/**
 * Used for converting primitive values to RealmValues.
 */
private inline fun MemTrackingAllocator.realmAnyPrimitiveToRealmValue(value: RealmAny): RealmValue {
    return when (value.type) {
        RealmAny.Type.INT -> longTransport(value.asLong())
        RealmAny.Type.BOOL -> booleanTransport(value.asBoolean())
        RealmAny.Type.STRING -> stringTransport(value.asString())
        RealmAny.Type.BINARY -> byteArrayTransport(value.asByteArray())
        RealmAny.Type.TIMESTAMP -> timestampTransport(value.asRealmInstant() as RealmInstantImpl)
        RealmAny.Type.FLOAT -> floatTransport(value.asFloat())
        RealmAny.Type.DOUBLE -> doubleTransport(value.asDouble())
        RealmAny.Type.DECIMAL128 -> decimal128Transport(value.asDecimal128())
        RealmAny.Type.OBJECT_ID -> objectIdTransport(value.asObjectId().toByteArray())
        RealmAny.Type.UUID -> uuidTransport(value.asRealmUUID().bytes)
        else -> throw UnsupportedOperationException("If you want to convert a 'RealmAny' instance containing an object to a 'RealmValue' use 'realmAnyToRealmValue' (when working with 'RealmQuery') or 'realmAnyToRealmValueWithObjectImport' (when using an accessor).")
    }
}

internal inline fun  realmValueToRealmObject(
    transport: RealmValue,
    clazz: KClass,
    mediator: Mediator,
    realmReference: RealmReference
): T? {
    return when {
        transport.isNull() -> null
        else -> transport.getLink().toRealmObject(clazz, mediator, realmReference)
    }
}

internal fun MemTrackingAllocator.realmObjectToRealmValue(value: BaseRealmObject?): RealmValue {
    return realmObjectTransport(
        value?.let { realmObjectToRealmReferenceOrError(it) as RealmObjectInterop }
    )
}

// Will return a managed realm object reference or null. If the object is unmanaged it will be
// imported according to the update policy. If the object is an outdated object it will throw an
// error.
internal inline fun realmObjectToRealmReferenceWithImport(
    value: BaseRealmObject?,
    mediator: Mediator,
    realmReference: RealmReference,
    updatePolicy: UpdatePolicy = UpdatePolicy.ERROR,
    cache: UnmanagedToManagedObjectCache = mutableMapOf()
): RealmObjectReference? {
    return realmObjectWithImport(value, mediator, realmReference, updatePolicy, cache)
        ?.realmObjectReference
}

// Will return a managed realm object or null. If the object is unmanaged it will be imported
// according to the update policy. If the object is an outdated object it will throw an error.
internal inline fun realmObjectWithImport(
    value: BaseRealmObject?,
    mediator: Mediator,
    realmReference: RealmReference,
    updatePolicy: UpdatePolicy = UpdatePolicy.ERROR,
    cache: UnmanagedToManagedObjectCache = mutableMapOf()
): BaseRealmObject? {
    return value?.let {
        val realmObjectReference = value.realmObjectReference
        // If managed ...
        if (realmObjectReference != null) {
            // and from the same version we just use object as is
            if (realmObjectReference.owner == realmReference) {
                value
            } else {
                throw IllegalArgumentException(
                    """Cannot import an outdated object. Use findLatest(object) to find an
                    |up-to-date version of the object in the given context before importing
                    |it.
                    """.trimMargin()
                )
            }
        } else {
            // otherwise we will import it
            copyToRealm(mediator, realmReference.asValidLiveRealmReference(), value, updatePolicy, cache = cache)
        }
    }
}

// Will return a managed realm object reference (or null) or throw when called with an unmanaged
// object
internal inline fun realmObjectToRealmReferenceOrError(
    value: BaseRealmObject?
): RealmObjectReference? {
    return value?.let {
        value.runIfManaged { this }
            ?: throw IllegalArgumentException("Cannot lookup unmanaged objects in realm")
    }
}

// Returns a converter fixed to convert objects of the given type in the context of the given mediator/realm
@Suppress("UNCHECKED_CAST")
internal fun  converter(
    clazz: KClass
): RealmValueConverter = primitiveTypeConverters.getValue(clazz) as RealmValueConverter




© 2015 - 2024 Weber Informatics LLC | Privacy Policy