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 (spring-projects/spring-data-mongodb#3515) Original Pull Request: #478 Closes: #2250
This commit is contained in:
committed by
Christoph Strobl
parent
e8ce3b04b0
commit
2bf5e53a3b
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
/**
|
||||
* Abstraction of a property path consisting of [KProperty].
|
||||
*
|
||||
* @author Tjeu Kayim
|
||||
* @author Mark Paluch
|
||||
* @author Yoann de Martino
|
||||
* @since 2.5
|
||||
*/
|
||||
private class KPropertyPath<T, U>(
|
||||
val parent: KProperty<U?>,
|
||||
val child: KProperty1<U, T>
|
||||
) : KProperty<T> by child
|
||||
|
||||
/**
|
||||
* Recursively construct field name for a nested property.
|
||||
* @author Tjeu Kayim
|
||||
*/
|
||||
internal fun asString(property: KProperty<*>): String {
|
||||
return when (property) {
|
||||
is KPropertyPath<*, *> ->
|
||||
"${asString(property.parent)}.${property.child.name}"
|
||||
else -> property.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds [KPropertyPath] from Property References.
|
||||
* Refer to a nested property in an embeddable or association.
|
||||
*
|
||||
* For example, referring to the field "author.name":
|
||||
* ```
|
||||
* Book::author / Author::name isEqualTo "Herman Melville"
|
||||
* ```
|
||||
* @author Tjeu Kayim
|
||||
* @author Yoann de Martino
|
||||
* @since 2.5
|
||||
*/
|
||||
operator fun <T, U> KProperty<T?>.div(other: KProperty1<T, U>): KProperty<U> =
|
||||
KPropertyPath(this, other)
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Extension for [KProperty] providing an `toPath` function to render a [KProperty] in dot notation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.5
|
||||
* @see PropertyPath.toDotPath
|
||||
*/
|
||||
fun KProperty<*>.toDotPath(): String = asString(this)
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2018-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit tests for [KPropertyPath] and its extensions.
|
||||
*
|
||||
* @author Tjeu Kayim
|
||||
* @author Yoann de Martino
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class KPropertyPathTests {
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert normal KProperty to field name`() {
|
||||
|
||||
val property = Book::title.toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("title")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert nested KProperty to field name`() {
|
||||
|
||||
val property = (Book::author / Author::name).toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("author.name")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert double nested KProperty to field name`() {
|
||||
|
||||
class Entity(val book: Book)
|
||||
|
||||
val property = (Entity::book / Book::author / Author::name).toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("book.author.name")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert triple nested KProperty to field name`() {
|
||||
|
||||
class Entity(val book: Book)
|
||||
class AnotherEntity(val entity: Entity)
|
||||
|
||||
val property = asString(AnotherEntity::entity / Entity::book / Book::author / Author::name)
|
||||
|
||||
assertThat(property).isEqualTo("entity.book.author.name")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert simple KProperty to property path using toDotPath`() {
|
||||
|
||||
class AnotherEntity(val entity: String)
|
||||
|
||||
val property = AnotherEntity::entity.toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("entity")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert nested KProperty to field name using toDotPath()`() {
|
||||
|
||||
val property = (Book::author / Author::name).toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("author.name")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert triple nested KProperty to property path using toDotPath`() {
|
||||
|
||||
class Entity(val book: Book)
|
||||
class AnotherEntity(val entity: Entity)
|
||||
|
||||
val property =
|
||||
(AnotherEntity::entity / Entity::book / Book::author / Author::name).toDotPath()
|
||||
|
||||
assertThat(property).isEqualTo("entity.book.author.name")
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1835
|
||||
fun `Convert nullable KProperty to field name`() {
|
||||
|
||||
class Cat(val name: String?)
|
||||
class Owner(val cat: Cat?)
|
||||
|
||||
val property = asString(Owner::cat / Cat::name)
|
||||
assertThat(property).isEqualTo("cat.name")
|
||||
}
|
||||
|
||||
class Book(val title: String, val author: Author)
|
||||
class Author(val name: String)
|
||||
}
|
||||
Reference in New Issue
Block a user