Introduce KIterablePropertyPath for type-safe property path mappings using collections.

See #3010
Original pull request: #3241

Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>
This commit is contained in:
mipo256
2025-02-09 19:15:59 +03:00
committed by Mark Paluch
parent 4e848f3bf7
commit 11bdb9a0a9
2 changed files with 55 additions and 0 deletions

View File

@@ -31,14 +31,29 @@ private class KPropertyPath<T, U>(
val child: KProperty1<U, T>
) : KProperty<T> by child
/**
* Abstraction of a property path that consists of parent [KProperty],
* and child property [KProperty], where parent [parent] has an [Iterable]
* of children, so it represents 1-M mapping, not 1-1, like [KPropertyPath]
*
* @author Mikhail Polivakha
*/
internal class KIterablePropertyPath<T, U>(
val parent: KProperty<Iterable<U?>?>,
val child: KProperty1<U, T>
) : KProperty<T> by child
/**
* Recursively construct field name for a nested property.
* @author Tjeu Kayim
* @author Mikhail Polivakha
*/
internal fun asString(property: KProperty<*>): String {
return when (property) {
is KPropertyPath<*, *> ->
"${asString(property.parent)}.${property.child.name}"
is KIterablePropertyPath<*, *> ->
"${asString(property.parent)}.${property.child.name}"
else -> property.name
}
}
@@ -55,5 +70,32 @@ internal fun asString(property: KProperty<*>): String {
* @author Yoann de Martino
* @since 2.5
*/
@JvmName("div")
operator fun <T, U> KProperty<T?>.div(other: KProperty1<T, U>): KProperty<U> =
KPropertyPath(this, other)
/**
* Builds [KPropertyPath] from Property References.
* Refer to a nested property in an embeddable or association.
*
* Note, that this function is different from [div] above in the
* way that it represents a division operator overloading for
* child references, where parent to child reference relation is 1-M, not 1-1.
* It implies that parent has an [Iterable] or any liner [Collection] of children.
**
* For example, referring to the field "addresses.street":
* ```
* User::addresses / Author::street contains "Austin"
* ```
*
* And the entities may look like this:
* ```
* class User(val addresses: List<Address>)
*
* class Address(val street: String)
* ```
* @author Mikhail Polivakha
*/
@JvmName("divIterable")
operator fun <T, U> KProperty<Iterable<T?>?>.div(other: KProperty1<T, U>): KProperty<U> =
KIterablePropertyPath(this, other)

View File

@@ -24,6 +24,7 @@ import org.junit.Test
* @author Tjeu Kayim
* @author Yoann de Martino
* @author Mark Paluch
* @author Mikhail Polivakha
*/
class KPropertyPathTests {
@@ -43,6 +44,14 @@ class KPropertyPathTests {
assertThat(property).isEqualTo("author.name")
}
@Test // DATACMNS-3010
fun `Convert from Iterable nested KProperty to field name`() {
val property = (User::addresses / Address::street).toDotPath()
assertThat(property).isEqualTo("addresses.street")
}
@Test // DATACMNS-1835
fun `Convert double nested KProperty to field name`() {
@@ -106,4 +115,8 @@ class KPropertyPathTests {
class Book(val title: String, val author: Author)
class Author(val name: String)
class User(val addresses: List<Address>)
class Address(val street: String)
}