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

dev.forkhandles.data.DataContainer.kt Maven / Gradle / Ivy

The newest version!
package dev.forkhandles.data

import dev.forkhandles.values.Value
import dev.forkhandles.values.ValueFactory
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.isAccessible

/**
 * Superclass for all container implementations. Defines the delegate property classes to extract data from the
 * underlying data structure.
 */
@Suppress("UNCHECKED_CAST")
abstract class DataContainer(
    private val data: DATA,
    private val existsFn: (DATA, String) -> Boolean,
    private val getFn: (DATA, String) -> Any?,
    private val setFn: (DATA, String, Any?) -> Unit
) {
    /**
     * Retrieve the attached metadata data about each of the properties
     */
    fun propertyMetadata(): List = kClass().memberProperties
        .mapNotNull { prop ->
            prop.isAccessible = true
            val delegate = prop.getDelegate(this)
            when {
                delegate is DataProperty<*, *> -> PropertyMetadata(prop.name, prop.returnType, delegate.data)
                else -> null
            }
        }

    /**
     * Expose the backing data structure
     */
    fun unwrap() = data

    /** Required **/

    protected fun  required(
        mapInFn: (OUT) -> NEXT,
        mapOutFn: (NEXT) -> OUT?,
        vararg metaData: Metadatum
    ) = property(mapInFn, mapOutFn, *metaData)

    protected fun  required(mapInFn: (OUT) -> NEXT, vararg metaData: Metadatum) =
        required(mapInFn, { error("no outbound mapping defined") }, *metaData)

    protected fun  required(vararg metaData: Metadatum) = required({ it }, { it }, *metaData)

    protected fun > required(
        factory: ValueFactory,
        vararg metaData: Metadatum
    ) = required(factory.parse(), factory.show(), *metaData)

    /** Optional **/

    protected fun  optional(
        mapInFn: (OUT) -> NEXT,
        mapOutFn: (NEXT) -> OUT?,
        vararg metaData: Metadatum
    ) = property(mapInFn, { it?.let(mapOutFn) }, *metaData)

    protected fun  optional(mapInFn: (OUT) -> NEXT, vararg metaData: Metadatum) =
        optional(mapInFn, { error("no outbound mapping defined") }, *metaData)

    protected fun  optional(vararg metaData: Metadatum) = property({ it }, { it }, *metaData)

    protected fun > optional(
        factory: ValueFactory,
        vararg metaData: Metadatum
    ) =
        optional(factory.parse(), factory.show(), *metaData)

    /** Object **/

    protected fun > requiredObj(
        mapInFn: (DATA) -> OUT,
        mapOutFn: (OUT) -> DATA?,
        vararg metaData: Metadatum
    ) = property(mapInFn, mapOutFn, *metaData)

    protected fun > requiredObj(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        requiredObj(mapInFn, { it.unwrap() }, *metaData)

    protected fun > optionalObj(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        property(mapInFn, { it?.unwrap() }, *metaData)

    /** Data **/

    protected fun requiredData(vararg metaData: Metadatum) =
        property({ it }, { it }, *metaData)

    protected fun optionalData(vararg metaData: Metadatum) =
        property({ it }, { it }, *metaData)

    /** List **/

    protected fun  requiredList(
        mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?,
        vararg metaData: Metadatum
    ) =
        property, List, List>({ it.map(mapInFn) }, { it.mapNotNull(mapOutFn) }, *metaData)

    protected fun  requiredList(mapInFn: (IN) -> OUT, vararg metaData: Metadatum) =
        requiredList(mapInFn, { error("no outbound mapping defined") }, *metaData)

    protected fun  requiredList(vararg metaData: Metadatum) = requiredList({ it }, { it }, *metaData)

    protected fun > requiredList(factory: ValueFactory, vararg metaData: Metadatum) =
        requiredList(factory.parse(), factory.show(), *metaData)

    @JvmName("listDataContainer")
    protected fun ?> requiredList(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        requiredList(mapInFn, { it?.unwrap() }, *metaData)

    protected fun  optionalList(mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?, vararg metaData: Metadatum) =
        property?, List, List>({ it.map(mapInFn) }, { it?.mapNotNull(mapOutFn) }, *metaData)

    protected fun  optionalList(mapInFn: (IN) -> OUT, vararg metaData: Metadatum) =
        optionalList(mapInFn, { error("no outbound mapping defined") }, *metaData)

    protected fun  optionalList(vararg metaData: Metadatum) =
        optionalList({ it }, { it }, *metaData)

    protected fun > optionalList(factory: ValueFactory, vararg metaData: Metadatum) =
        optionalList(factory.parse(), factory.show(), *metaData)

    @JvmName("optionalListDataContainer")
    protected fun ?> optionalList(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        optionalList(mapInFn, { it?.unwrap() }, *metaData)

    /** Utility **/

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as DataContainer<*>

        return unwrap() == other.unwrap()
    }

    override fun hashCode() = unwrap()?.hashCode() ?: 0

    override fun toString() = unwrap().toString()

    private fun  property(
        mapInFn: (OUT) -> IN,
        mapOutFn: (IN) -> NEXT?,
        vararg metaData: Metadatum
    ) = DataProperty, IN>(
        { name -> existsFn(unwrap(), name) },
        { name -> getFn(unwrap(), name)?.let { value -> value as OUT }?.let(mapInFn) },
        { name, value -> setFn(unwrap(), name, value?.let(mapOutFn)) },
        metaData.toList(),
    )

    private fun Any.kClass() = this::class as KClass>

    private fun > ValueFactory.parse(): (IN) -> OUT = {
        when (it) {
            is String, is Boolean, is Number -> parse(it.toString())
            else -> of(it)
        }
    }

    private fun > ValueFactory.show(): (OUT) -> IN = {
        when (it.value) {
            is String, is Boolean, is Number, is ByteArray -> it.value
            else -> show(it) as IN
        }
    }

    /**
     * Update container with the given property updated to the given value
     */
    inline fun , PROP> updateWith(property: KProperty1, value: PROP) {
        property.isAccessible = true

        NEXT::class.primaryConstructor?.call(unwrap())?.let {
            @Suppress("UNCHECKED_CAST")
            (property.getDelegate(it) as ReadWriteProperty).setValue(it, property, value)
        } ?: error("No constructor defined in ${NEXT::class}")
    }

    /** Deprecated **/

    @Deprecated("renamed", ReplaceWith("requiredData( *metaData)"))
    protected fun data(vararg metaData: Metadatum) = requiredData(*metaData)

    @Deprecated("renamed", ReplaceWith("requiredList(mapInFn, mapOutFn, *metaData)"))
    protected fun  list(
        mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?,
        vararg metaData: Metadatum
    ) = requiredList(mapInFn, mapOutFn, *metaData)

    @Deprecated("renamed", ReplaceWith("requiredList(mapInFn, *metaData)"))
    protected fun  list(mapInFn: (IN) -> OUT, vararg metaData: Metadatum) =
        requiredList(mapInFn, *metaData)

    @Deprecated("renamed", ReplaceWith("requiredList(*metaData)"))
    protected fun  list(vararg metaData: Metadatum) = requiredList(*metaData)

    @Deprecated("renamed", ReplaceWith("requiredList(factory, *metaData)"))
    protected fun > list(factory: ValueFactory, vararg metaData: Metadatum) =
        requiredList(factory, *metaData)

    @JvmName("listDataContainerDeprecated")
    @Deprecated("renamed", ReplaceWith("requiredList(mapInFn,  *metaData)"))
    protected fun ?> list(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        requiredList(mapInFn, *metaData)

    @Deprecated("renamed", ReplaceWith("requiredObj(mapInFn, mapOutFn, *metaData)"))
    protected fun > obj(
        mapInFn: (DATA) -> OUT,
        mapOutFn: (OUT) -> DATA?,
        vararg metaData: Metadatum
    ) = requiredObj(mapInFn, mapOutFn, *metaData)

    @Deprecated("renamed", ReplaceWith("Customize Toolbar..."))
    protected fun > obj(mapInFn: (DATA) -> OUT, vararg metaData: Metadatum) =
        requiredObj(mapInFn, *metaData)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy