Added unsafe data casts for all platforms and safeCast for JVM

This commit is contained in:
Alexander Nozik 2019-09-11 22:44:56 +03:00
parent b730d49e6e
commit 352b98be9b
6 changed files with 47 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.MetaRepr
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -34,7 +35,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr {
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Data<T> = DynamicData(type, meta, context, dependencies, block) ): Data<T> = DynamicData(type, meta, context, dependencies, block)
operator inline fun <reified T : Any> invoke( inline operator fun <reified T : Any> invoke(
meta: Meta = EmptyMeta, meta: Meta = EmptyMeta,
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
dependencies: Collection<Data<*>> = emptyList(), dependencies: Collection<Data<*>> = emptyList(),
@ -50,7 +51,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr {
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Data<T> = NamedData(name, invoke(type, meta, context, dependencies, block)) ): Data<T> = NamedData(name, invoke(type, meta, context, dependencies, block))
operator inline fun <reified T : Any> invoke( inline operator fun <reified T : Any> invoke(
name: String, name: String,
meta: Meta = EmptyMeta, meta: Meta = EmptyMeta,
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
@ -65,7 +66,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr {
} }
fun <R : Any, T : R> Data<T>.cast(type: KClass<R>): Data<R> { fun <R : Any, T : R> Data<T>.upcast(type: KClass<out R>): Data<R> {
return object : Data<R> by this { return object : Data<R> by this {
override val type: KClass<out R> = type override val type: KClass<out R> = type
} }
@ -74,8 +75,24 @@ fun <R : Any, T : R> Data<T>.cast(type: KClass<R>): Data<R> {
/** /**
* Upcast a [Data] to a supertype * Upcast a [Data] to a supertype
*/ */
inline fun <reified R : Any, T : R> Data<T>.cast(): Data<R> = cast(R::class) inline fun <reified R : Any, T : R> Data<T>.upcast(): Data<R> = upcast(R::class)
/**
* Unsafe cast of data node
*/
@Suppress("UNCHECKED_CAST")
fun <R: Any> Data<*>.cast(type: KClass<out R>): Data<R>{
return object : Data<R> {
override val meta: Meta get() = this@cast.meta
override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies
override val result: Deferred<R>? get() = this@cast.result as Deferred<R>
override fun startAsync(scope: CoroutineScope): Deferred<R> = this@cast.startAsync(scope) as Deferred<R>
override fun reset() = this@cast.reset()
override val type: KClass<out R> = type
}
}
inline fun <reified R : Any, T : R> Data<T>.cast(): Data<R> = cast(R::class)
class DynamicData<T : Any>( class DynamicData<T : Any>(
override val type: KClass<out T>, override val type: KClass<out T>,

View File

@ -72,7 +72,7 @@ operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.le
else -> get(name.first()!!.asName()).node?.get(name.cutFirst()) else -> get(name.first()!!.asName()).node?.get(name.cutFirst())
} }
operator fun <T : Any> DataNode<T>.get(name: String): DataItem<T>? = get(name.toString()) operator fun <T : Any> DataNode<T>.get(name: String): DataItem<T>? = get(name.toName())
/** /**
* Sequence of all children including nodes * Sequence of all children including nodes

View File

@ -1,32 +1,21 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSubclassOf
@Suppress("UNCHECKED_CAST")
fun <T : Any, R : Any> Data<T>.safeCast(type: KClass<out R>): Data<R>? {
return if (this.type.isSubclassOf(type)) {
return object : Data<R> {
override val meta: Meta get() = this@safeCast.meta
override val dependencies: Collection<Goal<*>> get() = this@safeCast.dependencies
override val result: Deferred<R>? get() = this@safeCast.result as Deferred<R>
override fun startAsync(scope: CoroutineScope): Deferred<R> = this@safeCast.startAsync(scope) as Deferred<R>
override fun reset() = this@safeCast.reset()
override val type: KClass<out R> = type
}
} else {
null
}
}
inline fun <reified R : Any> Data<*>.safeCast(): Data<R>? = safeCast(R::class) fun <T : Any, R : Any> Data<T>.canCast(type: KClass<out R>): Boolean =
this.type.isSubclassOf(type)
fun <T : Any, R : Any> Data<T>.safeCast(type: KClass<out R>): Data<R>? =
if (canCast(type)) cast(type) else null
//inline fun <reified R : Any> Data<*>.safeCast(): Data<R>? = safeCast(R::class)
class CastDataNode<out T : Any>(val origin: DataNode<Any>, override val type: KClass<out T>) : DataNode<T> { class CastDataNode<out T : Any>(val origin: DataNode<Any>, override val type: KClass<out T>) : DataNode<T> {
override val items: Map<NameToken, DataItem<T>> by lazy { override val items: Map<NameToken, DataItem<T>> by lazy {
origin.items.mapNotNull { (key, item) -> origin.items.mapNotNull { (key, item) ->
when (item) { when (item) {
is DataItem.Leaf -> { is DataItem.Leaf -> {
@ -35,7 +24,7 @@ class CastDataNode<out T : Any>(val origin: DataNode<Any>, override val type: KC
} }
} }
is DataItem.Node -> { is DataItem.Node -> {
key to DataItem.Node(item.value.cast(type)) key to DataItem.Node(item.value.safeCast(type))
} }
} }
}.associate { it } }.associate { it }

View File

@ -12,7 +12,7 @@ fun <T : Any> Data<T>.get(): T = runBlocking { await() }
/** /**
* Check that node is compatible with given type meaning that each element could be cast to the type * Check that node is compatible with given type meaning that each element could be cast to the type
*/ */
actual fun <T: Any> DataNode<*>.checkType(type: KClass<out T>) { actual fun <T : Any> DataNode<*>.checkType(type: KClass<out T>) {
if (!type.isSuperclassOf(type)) { if (!type.isSuperclassOf(type)) {
error("$type expected, but $type received") error("$type expected, but $type received")
} }
@ -22,12 +22,20 @@ actual fun <T: Any> DataNode<*>.checkType(type: KClass<out T>) {
* Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type], * Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type],
* but could contain empty nodes * but could contain empty nodes
*/ */
fun <T : Any, R : Any> DataNode<T>.cast(type: KClass<out R>): DataNode<R> { fun <R : Any> DataNode<*>.safeCast(type: KClass<out R>): DataNode<R> {
return if (this is CastDataNode) { return if (this is CastDataNode) {
origin.cast(type) origin.safeCast(type)
} else { } else {
CastDataNode(this, type) CastDataNode(this, type)
} }
} }
inline fun <T : Any, reified R : Any> DataNode<T>.cast(): DataNode<R> = cast(R::class) inline fun <reified R : Any> DataNode<*>.cast(): DataNode<R> = safeCast(R::class)
fun <R : Any> DataItem<*>?.safeCast(type: KClass<out R>): DataItem<R>? = when (this) {
null -> null
is DataItem.Node -> DataItem.Node(this.value.safeCast(type))
is DataItem.Leaf -> DataItem.Leaf(
this.value.safeCast(type) ?: error("Can't cast data with type ${this.value.type} to $type")
)
}

View File

@ -43,10 +43,9 @@ class EnvelopeServerTest {
} }
} }
@Test @Test
fun doEchoTest() { fun doEchoTest() {
val client = EnvelopeClient(Global, host = "localhost", port = 7778)
val request = Envelope.build { val request = Envelope.build {
type = "test.echo" type = "test.echo"
meta { meta {
@ -56,6 +55,7 @@ class EnvelopeServerTest {
writeDouble(22.7) writeDouble(22.7)
} }
} }
val client = EnvelopeClient(Global, host = "localhost", port = 7778)
runBlocking { runBlocking {
val response = client.respond(request) val response = client.respond(request)

View File

@ -49,7 +49,7 @@ class TaskBuilder(val name: String) {
dataTransforms += DataTransformation(from, to) { context, model, data -> dataTransforms += DataTransformation(from, to) { context, model, data ->
data.checkType(inputType) data.checkType(inputType)
val env = TaskEnv(EmptyName, model.meta, context) val env = TaskEnv(EmptyName, model.meta, context)
env.block(data.cast(inputType)) env.block(data.safeCast(inputType))
} }
} }