dev.forkhandles.data.DataContainer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of data4k Show documentation
Show all versions of data4k Show documentation
ForkHandles data-oriented programming library
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)
}