Skip to content

Commit

Permalink
Introduce KIterablePropertyPath for type-safe property path mapping…
Browse files Browse the repository at this point in the history
…s using collections.

See #3010
Original pull request: #3241

Signed-off-by: mipo256 <[email protected]>
  • Loading branch information
mipo256 authored and mp911de committed Feb 10, 2025
1 parent 4e848f3 commit 11bdb9a
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/main/kotlin/org/springframework/data/mapping/KPropertyPath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.junit.Test
* @author Tjeu Kayim
* @author Yoann de Martino
* @author Mark Paluch
* @author Mikhail Polivakha
*/
class KPropertyPathTests {

Expand All @@ -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`() {

Expand Down Expand Up @@ -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)
}

0 comments on commit 11bdb9a

Please sign in to comment.