Skip to content

Instantly share code, notes, and snippets.

@mskoroglu
Created April 20, 2022 08:23
Show Gist options
  • Save mskoroglu/fdff98483868e0039df8f68d7ed13469 to your computer and use it in GitHub Desktop.
Save mskoroglu/fdff98483868e0039df8f68d7ed13469 to your computer and use it in GitHub Desktop.
Kotlin Table DSL
fun table(block: Table.() -> Unit): Table {
val table = Table()
table.block()
return table
}
class Table : Iterable<Row>, WithMetadataFilter<Row> {
private val rows = hashSetOf<Row>()
fun row(index: Int? = null, block: Row.() -> Unit): Row {
val row = index?.let { createRow(it) } ?: createRow()
row.block()
return row
}
fun createRow(index: Int = (getItems().lastOrNull()?.index ?: -1) + 1): Row {
return Row(index).also(rows::add)
}
fun <CellType> ref(rowIndex: Int, columnIndex: Int): Cell<CellType> {
@Suppress("UNCHECKED_CAST")
return rows
.find { it.index == rowIndex }!!
.find { it.columnIndex == columnIndex }!! as Cell<CellType>
}
fun <CellType> ref(row: Row, columnIndex: Int): Cell<CellType> {
return ref(row.index, columnIndex)
}
override fun getItems(): Set<Row> {
return rows.sortedBy(Row::index).toSet()
}
override fun iterator(): Iterator<Row> {
return getItems().iterator()
}
override fun hashCode(): Int {
return getItems().hashCode()
}
override fun equals(other: Any?): Boolean {
return other is Table && getItems() == other.getItems()
}
}
class Row(val index: Int) : Iterable<Cell<*>>, WithMetadataFilter<Cell<*>>, WithMetadata<Row>() {
private val cells = hashSetOf<Cell<*>>()
private fun <CellType> createCell(): Cell<CellType> {
return Cell<CellType>(rowIndex = index, columnIndex = cells.size).also(cells::add)
}
fun <CellType> cell(block: Cell<*>.() -> CellType): Cell<CellType> {
val cell = createCell<CellType>()
val value = cell.block()
cell.setValue { value }
return cell
}
fun <CellType> createCell(value: CellType): Cell<CellType> {
return createCell<CellType>().also { it.setValue { value } }
}
fun <CellType> ref(columnIndex: Int): Cell<CellType> {
@Suppress("UNCHECKED_CAST")
return find { it.columnIndex == columnIndex }!! as Cell<CellType>
}
override fun getItems(): Set<Cell<*>> {
return cells.sortedBy(Cell<*>::columnIndex).toSet()
}
override fun iterator(): Iterator<Cell<*>> {
return getItems().iterator()
}
override fun hashCode(): Int {
return getItems().hashCode()
}
override fun equals(other: Any?): Boolean {
return other is Row && getItems() == other.getItems()
}
}
class Cell<CellType>(
val rowIndex: Int,
val columnIndex: Int
) : WithMetadata<Cell<CellType>>() {
private var isCalculated = false
private var calculatedValue: CellType? = null
private var valueSupplier: (Cell<CellType>.() -> CellType)? = null
internal fun setValue(valueSupplier: Cell<CellType>.() -> CellType) {
this.valueSupplier = valueSupplier
}
val value: CellType?
get() {
if (!this.isCalculated) {
calculatedValue = valueSupplier!!(this)
isCalculated = true
}
return calculatedValue
}
fun getRef(): String {
return "${rowIndex}:${columnIndex}"
}
override fun hashCode(): Int {
return getRef().hashCode()
}
override fun equals(other: Any?): Boolean {
return other is Cell<*> && getRef() == other.getRef()
}
}
open class WithMetadata<RowOrCell> {
private var metadata: MutableMap<String, Any?>? = null
fun getMetadata(): Map<String, Any?>? {
return metadata?.toMap()
}
fun <MetadataValue> metadata(key: String): MetadataValue? {
@Suppress("UNCHECKED_CAST")
return metadata?.get(key) as? MetadataValue
}
fun <MetadataValue> metadata(key: String, value: MetadataValue): RowOrCell {
if (metadata == null) {
metadata = mutableMapOf()
}
metadata!![key] = value
@Suppress("UNCHECKED_CAST")
return this as RowOrCell
}
}
interface WithMetadataFilter<ItemType : WithMetadata<*>> {
fun getItems(): Set<ItemType>
fun <MetadataValue> filter(key: String, value: Any?): Set<ItemType> {
return getItems().filter { it.metadata<MetadataValue>(key) == value }.toSet()
}
fun <MetadataValue> find(key: String, value: Any?): ItemType? {
return getItems().find { it.metadata<MetadataValue>(key) == value }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment