Skip to content

Commit 028e7db

Browse files
committed
DATACMNS-1835 - Provide Type-safe Kotlin query extension.
We now provide a KProperty 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. In contrast to the previous implementation, KPropertyPath is private and the rendering function was renamed to toDotPath to align with PropertyPath.toDotPath. Related ticket: DATAMONGO-2138.
1 parent 4850cdc commit 028e7db

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-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+
private class KPropertyPath<T, U>(
30+
val parent: KProperty<U?>,
31+
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>): KProperty<U> =
59+
KPropertyPath(this, other)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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] in dot notation.
22+
*
23+
* @author Mark Paluch
24+
* @since 2.5
25+
* @see PropertyPath.toDotPath
26+
*/
27+
fun KProperty<*>.toDotPath(): String = asString(this)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.toDotPath()
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).toDotPath()
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).toDotPath()
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 toDotPath`() {
69+
70+
class AnotherEntity(val entity: String)
71+
72+
val property = AnotherEntity::entity.toDotPath()
73+
74+
assertThat(property).isEqualTo("entity")
75+
}
76+
77+
@Test // DATACMNS-1835
78+
fun `Convert nested KProperty to field name using toDotPath()`() {
79+
80+
val property = (Book::author / Author::name).toDotPath()
81+
82+
assertThat(property).isEqualTo("author.name")
83+
}
84+
85+
@Test // DATACMNS-1835
86+
fun `Convert triple nested KProperty to property path using toDotPath`() {
87+
88+
class Entity(val book: Book)
89+
class AnotherEntity(val entity: Entity)
90+
91+
val property =
92+
(AnotherEntity::entity / Entity::book / Book::author / Author::name).toDotPath()
93+
94+
assertThat(property).isEqualTo("entity.book.author.name")
95+
}
96+
97+
@Test // DATACMNS-1835
98+
fun `Convert nullable KProperty to field name`() {
99+
100+
class Cat(val name: String?)
101+
class Owner(val cat: Cat?)
102+
103+
val property = asString(Owner::cat / Cat::name)
104+
assertThat(property).isEqualTo("cat.name")
105+
}
106+
107+
class Book(val title: String, val author: Author)
108+
class Author(val name: String)
109+
}

0 commit comments

Comments
 (0)