API for tables

This commit is contained in:
Alexander Nozik 2020-02-08 21:36:27 +03:00
parent 819ab3ab20
commit 061c570407
10 changed files with 182 additions and 86 deletions

View File

@ -0,0 +1,10 @@
package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.reflect.KClass
data class ColumnDef<out T : Any>(
override val name: String,
override val type: KClass<out T>,
override val meta: Meta
): ColumnHeader<T>

View File

@ -2,34 +2,26 @@ package hep.dataforge.tables
import kotlin.reflect.KClass
class ColumnTable(override val columns: Map<String, Column<*>>) :
Table {
private val rowsNum = columns.values.first().size
/**
* @param C bottom type for all columns in the table
*/
class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table {
private val rowsNum = columns.first().size
init {
require(columns.values.all { it.size == rowsNum }) { "All columns must be of the same size" }
require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" }
}
override val rows: List<Row>
get() = (0 until rowsNum).map { VirtualRow(it) }
get() = (0 until rowsNum).map { VirtualRow(this, it) }
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
val value = columns[column]?.get(row)
return when {
value == null -> null
type.isInstance(value) -> value as T
else -> error("Expected type is $type, but found ${value::class}")
}
}
private inner class VirtualRow(val index: Int) : Row {
override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = getValue(index, column, type)
return type.cast(value)
}
}
class ColumnTableBuilder {
private val columns = ArrayList<Column<*>>()
internal class VirtualRow(val table: Table, val index: Int) : Row {
override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type)
}
fun build() = ColumnTable(columns.associateBy { it.name })
}

View File

@ -0,0 +1,58 @@
package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.reflect.KClass
class ColumnTableBuilder(val size: Int) : Table {
private val _columns = ArrayList<Column<*>>()
override val columns: List<Column<*>> get() = _columns
override val rows: List<Row> get() = (0 until size).map {
VirtualRow(this, it)
}
override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? {
val value = columns[column]?.get(row)
return type.cast(value)
}
/**
* Add a fixed column to the end of the table
*/
fun add(column: Column<*>) {
require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
_columns.add(column)
}
/**
* Insert a column at [index]
*/
fun insert(index: Int, column: Column<*>) {
require(column.size == this.size) { "Required column size $size, but found ${column.size}" }
_columns.add(index, column)
}
}
class MapColumn<T : Any, R : Any>(
val source: Column<T>,
override val type: KClass<out R>,
override val name: String,
override val meta: Meta = source.meta,
val mapper: (T?) -> R?
) : Column<R> {
override val size: Int get() = source.size
override fun get(index: Int): R? = mapper(source[index])
}
class CachedMapColumn<T : Any, R : Any>(
val source: Column<T>,
override val type: KClass<out R>,
override val name: String,
override val meta: Meta = source.meta,
val mapper: (T?) -> R?
) : Column<R> {
override val size: Int get() = source.size
private val values: HashMap<Int, R?> = HashMap()
override fun get(index: Int): R? = values.getOrPut(index) { mapper(source[index]) }
}

View File

@ -16,9 +16,16 @@ class ListColumn<T : Any>(
companion object {
inline operator fun <reified T : Any> invoke(
name: String,
data: List<T>,
noinline metaBuilder: ColumnScheme.() -> Unit
): ListColumn<T> = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta())
def: ColumnScheme,
data: List<T?>
): ListColumn<T> = ListColumn(name, data, T::class, def.toMeta())
inline operator fun <reified T : Any> invoke(
name: String,
def: ColumnScheme,
size: Int,
dataBuilder: (Int) -> T?
): ListColumn<T> = invoke(name, def, List(size, dataBuilder))
}
}

View File

@ -3,17 +3,8 @@ package hep.dataforge.tables
import kotlin.reflect.KClass
class MapRow(val values: Map<String, Any>) : Row {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getValue(column: String, type: KClass<out T>): T? {
val value = values[column]
return when {
value == null -> null
type.isInstance(value) -> {
value as T?
}
else -> {
error("Expected type is $type, but found ${value::class}")
}
}
return type.cast(value)
}
}

View File

@ -0,0 +1,11 @@
package hep.dataforge.tables
import hep.dataforge.meta.scheme.Scheme
import hep.dataforge.meta.scheme.SchemeSpec
import hep.dataforge.meta.scheme.string
class MetaQuery : Scheme() {
var field by string()
companion object : SchemeSpec<MetaQuery>(::MetaQuery)
}

View File

@ -3,24 +3,21 @@ package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.reflect.KClass
data class ColumnDef<T : Any>(val name: String, val type: KClass<T>, val meta: Meta)
class RowTable<R : Row>(override val rows: List<R>, private val columnDefs: List<ColumnDef<*>>) : Table {
class RowTable<R : Row>(override val rows: List<R>, override val header: TableHeader) : Table {
override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? =
rows[row].getValue(column, type)
override val columns: Map<String, Column<*>>
get() = columnDefs.associate { it.name to VirtualColumn(it) }
override val columns: List<Column<*>> get() = header.map { RotTableColumn(it) }
private inner class VirtualColumn<T : Any>(val def: ColumnDef<T>) :
Column<T> {
override val name: String get() = def.name
override val type: KClass<out T> get() = def.type
override val meta: Meta get() = def.meta
private inner class RotTableColumn<T : Any>(val header: ColumnHeader<T>) : Column<T> {
override val name: String get() = header.name
override val type: KClass<out T> get() = header.type
override val meta: Meta get() = header.meta
override val size: Int get() = rows.size
override fun get(index: Int): T? = rows[index].getValue(name, type)
}
}

View File

@ -3,16 +3,44 @@ package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.reflect.KClass
interface Table {
fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
val columns: Map<String, Column<*>>
val rows: List<Row>
//TODO to be removed in 1.3.70
@Suppress("UNCHECKED_CAST")
internal fun <T : Any> KClass<T>.cast(value: Any?): T? {
return when {
value == null -> null
!isInstance(value) -> error("Expected type is $this, but found ${value::class}")
else -> value as T
}
}
interface Column<out T : Any> {
typealias TableHeader = List<ColumnHeader<*>>
interface Table {
fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T?
val columns: Collection<Column<*>>
val header: TableHeader get() = columns.toList()
val rows: List<Row>
/**
* Apply query to a table and return lazy Flow
*/
//fun find(query: Any): Flow<Row>
}
operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name }
inline operator fun <reified T : Any> Table.get(row: Int, column: String): T? = getValue(row, column, T::class)
interface ColumnHeader<out T : Any> {
val name: String
val type: KClass<out T>
val meta: Meta
}
operator fun <T : Any> Table.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type)
interface Column<out T : Any> : ColumnHeader<T> {
val size: Int
operator fun get(index: Int): T?
}
@ -20,11 +48,14 @@ interface Column<out T : Any> {
val Column<*>.indices get() = (0 until size)
operator fun <T : Any> Column<T>.iterator() = iterator {
for (i in indices){
for (i in indices) {
yield(get(i))
}
}
interface Row {
fun <T : Any> getValue(column: String, type: KClass<out T>): T?
}
}
inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class)
operator fun <T : Any> Row.get(column: Column<T>): T? = getValue(column.name, column.type)

View File

@ -0,0 +1,36 @@
package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.cast
import kotlin.reflect.full.isSubclassOf
@Suppress("UNCHECKED_CAST")
fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
return if (type.isSubclassOf(this.type)) {
this as Column<T>
} else {
CastColumn(this, type)
}
}
class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : Column<T> {
override val name: String get() = origin.name
override val meta: Meta get() = origin.meta
override val size: Int get() = origin.size
override fun get(index: Int): T? = type.cast(origin[index])
}
class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T> {
val name = property.name
return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type)
}
}
operator fun <T : Any> Collection<Column<*>>.get(header: ColumnHeader<T>): Column<T>? =
find { it.name == header.name }?.cast(header.type)

View File

@ -1,37 +0,0 @@
package hep.dataforge.tables
import hep.dataforge.meta.Meta
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.cast
import kotlin.reflect.full.isSubclassOf
class TableAccessor(val table: Table) : Table by table {
inline fun <reified T : Any> column() = ColumnProperty(table, T::class)
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> {
return if (type.isSubclassOf(this.type)) {
this as Column<T>
} else {
ColumnWrapper(this, type)
}
}
class ColumnWrapper<T : Any>(val column: Column<*>, override val type: KClass<T>) : Column<T> {
override val name: String get() = column.name
override val meta: Meta get() = column.meta
override val size: Int get() = column.size
override fun get(index: Int): T? = type.cast(column[index])
}
class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T>? {
val name = property.name
return table.columns[name]?.cast(type)
}
}