From 061c570407ad8dd8c8b4dd8eca5618d896a71386 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 8 Feb 2020 21:36:27 +0300 Subject: [PATCH] API for tables --- .../kotlin/hep/dataforge/tables/ColumnDef.kt | 10 ++++ .../hep/dataforge/tables/ColumnTable.kt | 30 ++++------ .../dataforge/tables/ColumnTableBuilder.kt | 58 +++++++++++++++++++ .../kotlin/hep/dataforge/tables/ListColumn.kt | 13 ++++- .../kotlin/hep/dataforge/tables/MapRow.kt | 11 +--- .../kotlin/hep/dataforge/tables/MetaQuery.kt | 11 ++++ .../kotlin/hep/dataforge/tables/RowTable.kt | 17 +++--- .../kotlin/hep/dataforge/tables/Table.kt | 45 +++++++++++--- .../kotlin/hep/dataforge/tables/CastColumn.kt | 36 ++++++++++++ .../hep/dataforge/tables/TableAccessor.kt | 37 ------------ 10 files changed, 182 insertions(+), 86 deletions(-) create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt create mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt delete mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt new file mode 100644 index 00000000..2df51d41 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt @@ -0,0 +1,10 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +data class ColumnDef( + override val name: String, + override val type: KClass, + override val meta: Meta +): ColumnHeader \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt index ce7b7dae..07ea5d94 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -2,34 +2,26 @@ package hep.dataforge.tables import kotlin.reflect.KClass -class ColumnTable(override val columns: Map>) : - Table { - private val rowsNum = columns.values.first().size +/** + * @param C bottom type for all columns in the table + */ +class ColumnTable(override val columns: Collection>) : 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 - get() = (0 until rowsNum).map { VirtualRow(it) } + get() = (0 until rowsNum).map { VirtualRow(this, it) } - @Suppress("UNCHECKED_CAST") override fun getValue(row: Int, column: String, type: KClass): 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 getValue(column: String, type: KClass): T? = getValue(index, column, type) + return type.cast(value) } } -class ColumnTableBuilder { - private val columns = ArrayList>() +internal class VirtualRow(val table: Table, val index: Int) : Row { + override fun getValue(column: String, type: KClass): T? = table.getValue(index, column, type) +} - fun build() = ColumnTable(columns.associateBy { it.name }) -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt new file mode 100644 index 00000000..b453c957 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt @@ -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>() + + override val columns: List> get() = _columns + override val rows: List get() = (0 until size).map { + VirtualRow(this, it) + } + + override fun getValue(row: Int, column: String, type: KClass): 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( + val source: Column, + override val type: KClass, + override val name: String, + override val meta: Meta = source.meta, + val mapper: (T?) -> R? +) : Column { + override val size: Int get() = source.size + + override fun get(index: Int): R? = mapper(source[index]) +} + +class CachedMapColumn( + val source: Column, + override val type: KClass, + override val name: String, + override val meta: Meta = source.meta, + val mapper: (T?) -> R? +) : Column { + override val size: Int get() = source.size + private val values: HashMap = HashMap() + override fun get(index: Int): R? = values.getOrPut(index) { mapper(source[index]) } +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt index a926a2ad..fc7f03ea 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt @@ -16,9 +16,16 @@ class ListColumn( companion object { inline operator fun invoke( name: String, - data: List, - noinline metaBuilder: ColumnScheme.() -> Unit - ): ListColumn = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta()) + def: ColumnScheme, + data: List + ): ListColumn = ListColumn(name, data, T::class, def.toMeta()) + + inline operator fun invoke( + name: String, + def: ColumnScheme, + size: Int, + dataBuilder: (Int) -> T? + ): ListColumn = invoke(name, def, List(size, dataBuilder)) } } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt index 0a78ca9e..76002e9d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -3,17 +3,8 @@ package hep.dataforge.tables import kotlin.reflect.KClass class MapRow(val values: Map) : Row { - @Suppress("UNCHECKED_CAST") override fun getValue(column: String, type: KClass): 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) } } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt new file mode 100644 index 00000000..cf07a5c8 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt @@ -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) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt index fbcec0b7..c640aade 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -3,24 +3,21 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -data class ColumnDef(val name: String, val type: KClass, val meta: Meta) -class RowTable(override val rows: List, private val columnDefs: List>) : Table { +class RowTable(override val rows: List, override val header: TableHeader) : Table { override fun getValue(row: Int, column: String, type: KClass): T? = rows[row].getValue(column, type) - override val columns: Map> - get() = columnDefs.associate { it.name to VirtualColumn(it) } + override val columns: List> get() = header.map { RotTableColumn(it) } - private inner class VirtualColumn(val def: ColumnDef) : - Column { - - override val name: String get() = def.name - override val type: KClass get() = def.type - override val meta: Meta get() = def.meta + private inner class RotTableColumn(val header: ColumnHeader) : Column { + override val name: String get() = header.name + override val type: KClass 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) } + } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt index 52171625..4961f9bb 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -3,16 +3,44 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -interface Table { - fun getValue(row: Int, column: String, type: KClass): T? - val columns: Map> - val rows: List +//TODO to be removed in 1.3.70 +@Suppress("UNCHECKED_CAST") +internal fun KClass.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 { +typealias TableHeader = List> + + +interface Table { + fun getValue(row: Int, column: String, type: KClass): T? + val columns: Collection> + val header: TableHeader get() = columns.toList() + val rows: List + + /** + * Apply query to a table and return lazy Flow + */ + //fun find(query: Any): Flow +} + +operator fun Collection>.get(name: String): Column<*>? = find { it.name == name } + +inline operator fun Table.get(row: Int, column: String): T? = getValue(row, column, T::class) + +interface ColumnHeader { val name: String val type: KClass val meta: Meta +} + +operator fun Table.get(row: Int, column: Column): T? = getValue(row, column.name, column.type) + +interface Column : ColumnHeader { val size: Int operator fun get(index: Int): T? } @@ -20,11 +48,14 @@ interface Column { val Column<*>.indices get() = (0 until size) operator fun Column.iterator() = iterator { - for (i in indices){ + for (i in indices) { yield(get(i)) } } interface Row { fun getValue(column: String, type: KClass): T? -} \ No newline at end of file +} + +inline operator fun Row.get(column: String): T? = getValue(column, T::class) +operator fun Row.get(column: Column): T? = getValue(column.name, column.type) \ No newline at end of file diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt new file mode 100644 index 00000000..9b9801c8 --- /dev/null +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -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 Column<*>.cast(type: KClass): Column { + return if (type.isSubclassOf(this.type)) { + this as Column + } else { + CastColumn(this, type) + } +} + +class CastColumn(val origin: Column<*>, override val type: KClass) : Column { + 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(val table: Table, val type: KClass) : ReadOnlyProperty> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Column { + val name = property.name + return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type) + } +} + +operator fun Collection>.get(header: ColumnHeader): Column? = + find { it.name == header.name }?.cast(header.type) diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt deleted file mode 100644 index 21453426..00000000 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt +++ /dev/null @@ -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 column() = ColumnProperty(table, T::class) -} - -@Suppress("UNCHECKED_CAST") -fun Column<*>.cast(type: KClass): Column { - return if (type.isSubclassOf(this.type)) { - this as Column - } else { - ColumnWrapper(this, type) - } -} - -class ColumnWrapper(val column: Column<*>, override val type: KClass) : Column { - 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(val table: Table, val type: KClass) : ReadOnlyProperty?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Column? { - val name = property.name - return table.columns[name]?.cast(type) - } -} \ No newline at end of file