Skip to content

Commit 9233eab

Browse files
committed
DATACMNS-1835 - Provide Type-safe Kotlin query extension.
We now provide a KPropertyPath extension leveraging KProperty references to express a property path. Using Kotlin property references (such as Author::name that translates to `book` or Book::author / Author.name translating to `book.author`) is refactoring-safe as the property expressions are part of the Kotlin language. We render KPropertyPath using the same semantics as our PropertyPath so that store-specific modules can leverage either property paths or accept KProperty directly. Related ticket: DATAMONGO-2138.
1 parent 4850cdc commit 9233eab

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2018-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mapping
17+
18+
import kotlin.reflect.KProperty
19+
import kotlin.reflect.KProperty1
20+
21+
/**
22+
* Abstraction of a property path consisting of [KProperty].
23+
*
24+
* @author Tjeu Kayim
25+
* @author Mark Paluch
26+
* @author Yoann de Martino
27+
* @since 2.5
28+
*/
29+
class KPropertyPath<T, U>(
30+
internal val parent: KProperty<U?>,
31+
internal val child: KProperty1<U, T>
32+
) : KProperty<T> by child
33+
34+
/**
35+
* Recursively construct field name for a nested property.
36+
* @author Tjeu Kayim
37+
*/
38+
internal fun asString(property: KProperty<*>): String {
39+
return when (property) {
40+
is KPropertyPath<*, *> ->
41+
"${asString(property.parent)}.${property.child.name}"
42+
else -> property.name
43+
}
44+
}
45+
46+
/**
47+
* Builds [KPropertyPath] from Property References.
48+
* Refer to a nested property in an embeddable or association.
49+
*
50+
* For example, referring to the field "author.name":
51+
* ```
52+
* Book::author / Author::name isEqualTo "Herman Melville"
53+
* ```
54+
* @author Tjeu Kayim
55+
* @author Yoann de Martino
56+
* @since 2.5
57+
*/
58+
operator fun <T, U> KProperty<T?>.div(other: KProperty1<T, U>) =
59+
KPropertyPath(this, other)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mapping
17+
18+
import kotlin.reflect.KProperty
19+
20+
/**
21+
* Extension for [KProperty] providing an `toPath` function to render a [KProperty] as property path.
22+
*
23+
* @author Mark Paluch
24+
* @since 2.5
25+
*/
26+
fun KProperty<*>.toPath(): String = asString(this)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2018-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mapping
17+
18+
import org.assertj.core.api.Assertions.assertThat
19+
import org.junit.Test
20+
21+
/**
22+
* Unit tests for [KPropertyPath] and its extensions.
23+
*
24+
* @author Tjeu Kayim
25+
* @author Yoann de Martino
26+
* @author Mark Paluch
27+
*/
28+
class KPropertyPathTests {
29+
30+
@Test // DATACMNS-1835
31+
fun `Convert normal KProperty to field name`() {
32+
33+
val property = Book::title.toPath()
34+
35+
assertThat(property).isEqualTo("title")
36+
}
37+
38+
@Test // DATACMNS-1835
39+
fun `Convert nested KProperty to field name`() {
40+
41+
val property = (Book::author / Author::name).toPath()
42+
43+
assertThat(property).isEqualTo("author.name")
44+
}
45+
46+
@Test // DATACMNS-1835
47+
fun `Convert double nested KProperty to field name`() {
48+
49+
class Entity(val book: Book)
50+
51+
val property = (Entity::book / Book::author / Author::name).toPath()
52+
53+
assertThat(property).isEqualTo("book.author.name")
54+
}
55+
56+
@Test // DATACMNS-1835
57+
fun `Convert triple nested KProperty to field name`() {
58+
59+
class Entity(val book: Book)
60+
class AnotherEntity(val entity: Entity)
61+
62+
val property = asString(AnotherEntity::entity / Entity::book / Book::author / Author::name)
63+
64+
assertThat(property).isEqualTo("entity.book.author.name")
65+
}
66+
67+
@Test // DATACMNS-1835
68+
fun `Convert simple KProperty to property path using toPath`() {
69+
70+
class AnotherEntity(val entity: String)
71+
72+
val property = AnotherEntity::entity.toPath()
73+
74+
assertThat(property).isEqualTo("entity")
75+
}
76+
77+
@Test // DATACMNS-1835
78+
fun `Convert nested KProperty to field name using toPath()`() {
79+
80+
val property = (Book::author / Author::name).toPath()
81+
82+
assertThat(property).isEqualTo("author.name")
83+
}
84+
85+
@Test // DATACMNS-1835
86+
fun `Convert triple nested KProperty to property path using toPath`() {
87+
88+
class Entity(val book: Book)
89+
class AnotherEntity(val entity: Entity)
90+
91+
val property = (AnotherEntity::entity / Entity::book / Book::author / Author::name).toPath()
92+
93+
assertThat(property).isEqualTo("entity.book.author.name")
94+
}
95+
96+
@Test // DATACMNS-1835
97+
fun `Convert nullable KProperty to field name`() {
98+
99+
class Cat(val name: String?)
100+
class Owner(val cat: Cat?)
101+
102+
val property = asString(Owner::cat / Cat::name)
103+
assertThat(property).isEqualTo("cat.name")
104+
}
105+
106+
class Book(val title: String, val author: Author)
107+
class Author(val name: String)
108+
}

0 commit comments

Comments
 (0)