diff --git a/pom.xml b/pom.xml index d395a08985..02a3791eb9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-commons - 2.5.0-SNAPSHOT + 2.5.0-DATACMNS-1835-SNAPSHOT Spring Data Core diff --git a/src/main/kotlin/org/springframework/data/mapping/KPropertyPath.kt b/src/main/kotlin/org/springframework/data/mapping/KPropertyPath.kt new file mode 100644 index 0000000000..1c0625e309 --- /dev/null +++ b/src/main/kotlin/org/springframework/data/mapping/KPropertyPath.kt @@ -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( + val parent: KProperty, + val child: KProperty1 +) : KProperty 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 KProperty.div(other: KProperty1): KProperty = + KPropertyPath(this, other) diff --git a/src/main/kotlin/org/springframework/data/mapping/KPropertyPathExtensions.kt b/src/main/kotlin/org/springframework/data/mapping/KPropertyPathExtensions.kt new file mode 100644 index 0000000000..6024c341fa --- /dev/null +++ b/src/main/kotlin/org/springframework/data/mapping/KPropertyPathExtensions.kt @@ -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) diff --git a/src/test/kotlin/org/springframework/data/mapping/KPropertyPathTests.kt b/src/test/kotlin/org/springframework/data/mapping/KPropertyPathTests.kt new file mode 100644 index 0000000000..d62f685314 --- /dev/null +++ b/src/test/kotlin/org/springframework/data/mapping/KPropertyPathTests.kt @@ -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) +}