Skip to content

Commit

Permalink
added value type support
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenton committed Jan 3, 2024
1 parent c0b3e8a commit 38d9314
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
This list is not intended to be all-encompassing - it will document major and breaking API changes with their rationale
when appropriate:

### v2.11.1.0
- **data4k** : Add support for Value classes and removed inner class structures to tidy up.

### v2.11.0.0
- **data4k** : [New module] [data4k](https://github.com/fork-handles/forkhandles/blob/trunk/data4k/README.md): Typesafe data-oriented programming.

Expand Down
4 changes: 2 additions & 2 deletions data4k/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<a href="http//www.apache.org/licenses/LICENSE-2.0"><img alt="GitHub license" src="https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat"></a>
<a href="https://codebeat.co/projects/github-com-fork-handles-forkhandles-trunk"><img alt="codebeat badge" src="https://codebeat.co/badges/5b369ed4-af27-46f4-ad9c-a307d900617e"></a>

Library to make working with Data-Oriented programming in Kotlin easier, to extract values from dynamic data structures such as Maps.
Library to make working with Data-Oriented programming in Kotlin easier, to extract typed values from dynamic data structures such as Maps.

## Installation

Expand All @@ -19,7 +19,7 @@ implementation("dev.forkhandles:data4k")

## Usage

The library defines a DataContainer class and implementations for:
The library defines a `DataContainer` class and implementations for:

- Map<String, Any?>
- Jackson JSON Node
Expand Down
1 change: 1 addition & 0 deletions data4k/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ description = 'ForkHandles data-oriented programming library'

dependencies {
api("org.jetbrains.kotlin:kotlin-reflect:_")
api(project(":values4k"))
compileOnly("com.fasterxml.jackson.core:jackson-databind:_")
testImplementation("com.fasterxml.jackson.core:jackson-databind:_")
testImplementation("io.strikt:strikt-jvm:_")
Expand Down
29 changes: 23 additions & 6 deletions data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dev.forkhandles.data

import dev.forkhandles.values.Value
import dev.forkhandles.values.ValueFactory

/**
* Superclass for all container implementations. Defines the delegate property classes to extract data from the
* underlying data structure.
*/
@Suppress("UNCHECKED_CAST", "ClassName")
@Suppress("UNCHECKED_CAST")
abstract class DataContainer<CONTENT>(
protected val data: CONTENT,
existsFn: (CONTENT, String) -> Boolean,
Expand All @@ -13,11 +16,25 @@ abstract class DataContainer<CONTENT>(
private val exists: DataContainer<CONTENT>.(String) -> Boolean = { existsFn(data, it) }
private val get: DataContainer<CONTENT>.(String) -> Any? = { getFn(data, it) }

inner class field<OUT> : DataProperty<DataContainer<CONTENT>, OUT>(exists, get)
fun <OUT> field() = DataProperty<DataContainer<CONTENT>, OUT>(exists, get)

fun <OUT, NEXT> field(mapFn: (OUT) -> NEXT) = DataProperty<DataContainer<CONTENT>, NEXT>(exists) {
(get(it) as OUT).let(mapFn)
}

fun <IN : Any, OUT : Value<IN>> field(factory: ValueFactory<OUT, IN>) =
DataProperty<DataContainer<CONTENT>, OUT>(exists) { (get(it) as IN).let(factory::of) }

fun <IN, OUT> list(mapFn: (IN) -> OUT) =
DataProperty<DataContainer<CONTENT>, List<OUT>>(exists) { (get(it) as List<IN>).map(mapFn) }

fun <IN : Any, OUT : Value<IN>> list(factory: ValueFactory<OUT, IN>) =
DataProperty<DataContainer<CONTENT>, List<OUT>>(exists) { (get(it) as List<IN>).map(factory::of) }

fun <OUT> list() = list<OUT, OUT> { it }

inner class list<IN : Any, OUT>(mapFn: (IN) -> OUT) :
DataProperty<DataContainer<CONTENT>, List<OUT>>(exists, { (get(it) as List<IN>).map(mapFn) })
fun <OUT : DataContainer<CONTENT>> obj(mapFn: (CONTENT) -> OUT) =
DataProperty<DataContainer<CONTENT>, OUT>(exists) { mapFn(get(it) as CONTENT) }

inner class obj<OUT : DataContainer<CONTENT>>(mapFn: (CONTENT) -> OUT) :
DataProperty<DataContainer<CONTENT>, OUT>(exists, { mapFn(get(it) as CONTENT) })
fun obj() = DataProperty<DataContainer<CONTENT>, CONTENT>(exists) { get(it) as CONTENT }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.jvmErasure

abstract class DataProperty<IN, OUT>(
open class DataProperty<IN, OUT>(
private val existsFn: IN.(String) -> Boolean,
private val getFn: IN.(String) -> Any?
) : ReadOnlyProperty<IN, OUT> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dev.forkhandles.lens

import dev.forkhandles.values.IntValue
import dev.forkhandles.values.IntValueFactory
import org.junit.jupiter.api.Test
import strikt.api.expectThat
import strikt.api.expectThrows
Expand All @@ -15,17 +17,25 @@ interface MainClassFields {
val notAStringField: String
val optionalField: String?
val noSuchField: String
val listField: List<String>
val listSubClassField: List<SubClassFields>
val listIntsField: List<Int>
val listValueField: List<MyType>
val listStringsField: List<String>
val objectField: SubClassFields
val valueField: MyType
val mappedField: Int
}

interface SubClassFields {
val stringField: String
val noSuchField: String
}

class MyType private constructor(value: Int) : IntValue(value) {
companion object : IntValueFactory<MyType>(::MyType)
}

abstract class DataContainerContract {

abstract fun container(input: Map<String, Any?>): MainClassFields
Expand All @@ -41,6 +51,11 @@ abstract class DataContainerContract {
"longField" to Long.MAX_VALUE,
"decimalField" to 1.1234,
"notAStringField" to 123,
"valueField" to 123,
"mappedField" to "123",
"listField" to listOf("hello"),
"listField" to listOf("hello"),
"listValueField" to listOf(1, 2, 3),
"listIntsField" to listOf(1, 2, 3),
"listSubClassField" to listOf(
mapOf("stringField" to "string1"),
Expand All @@ -59,7 +74,11 @@ abstract class DataContainerContract {
expectThat(input.intField).isEqualTo(123)
expectThat(input.longField).isEqualTo(Long.MAX_VALUE)
expectThat(input.decimalField).isEqualTo(1.1234)
expectThat(input.valueField).isEqualTo(MyType.of(123))
expectThat(input.mappedField).isEqualTo(123)
expectThat(input.listField).isEqualTo(listOf("hello"))
expectThat(input.listIntsField).isEqualTo(listOf(1, 2, 3))
expectThat(input.listValueField).isEqualTo(listOf(1, 2, 3).map(MyType::of))
expectThat(input.listSubClassField.map { it.stringField }).isEqualTo(listOf("string1", "string2"))
expectThat(input.listStringsField).isEqualTo(listOf("string1", "string2"))
expectThat(input.objectField.stringField).isEqualTo("string")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ class JacksonDataContainerTest : DataContainerContract() {
override val noSuchField by field<String>()
override val listSubClassField by list(::SubNodeBacked)
override val listStringsField by list(Any::toString)
override val listIntsField by list<Any, Int> { it as Int }
override val listField by list<String>()
override val listIntsField by list<Int>()
override val listValueField by list(MyType)
override val objectField by obj(::SubNodeBacked)
override val valueField by field(MyType)
override val mappedField by field(String::toInt)
}

override fun container(input: Map<String, Any?>) = NodeBacked(ObjectMapper().valueToTree(input))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ class MapDataContainerTest : DataContainerContract() {
override val decimalField by field<Double>()
override val notAStringField by field<String>()
override val noSuchField by field<String>()
override val listField by list<String>()
override val listValueField by list(MyType)
override val listSubClassField by list(::SubMap)
override val listStringsField by list(Any::toString)
override val listIntsField by list<Any, Int> { it as Int }
override val listIntsField by list<Int>()
override val objectField by obj(::SubMap)
override val valueField by field(MyType)
override val mappedField by field(String::toInt)
}

override fun container(input: Map<String, Any?>): MainClassFields = MapBacked(input)
Expand Down

0 comments on commit 38d9314

Please sign in to comment.