Skip to content

Commit 0630e67

Browse files
committed
Refine Kotlin syntax.
1 parent 12399a4 commit 0630e67

File tree

12 files changed

+258
-146
lines changed

12 files changed

+258
-146
lines changed

src/main/antora/modules/ROOT/pages/property-paths.adoc

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ class Person {
3232
int age;
3333
Address address;
3434
List<Address> previousAddresses;
35-
35+
3636
String getFirstname() { … }; // other property accessors omitted for brevity
3737
3838
}
3939
4040
class Address {
4141
String city, street;
42-
42+
4343
// accessors omitted for brevity
4444
4545
}
@@ -139,11 +139,13 @@ Kotlin::
139139
+
140140
[source,kotlin,role="secondary"]
141141
----
142-
TypedPropertyPath.of<Person, Address>(Person::address)
143-
.then(Address::city)
144-
145-
// Kotlin Exension
142+
// Kotlin API
146143
KTypedPropertyPath.of(Person::address).then(Address::city)
144+
145+
// Extension for KProperty1
146+
KTypedPropertyPath.of(Person::address / Address::city)
147+
148+
(Person::address / Address::city).toPath()
147149
----
148150
======
149151

@@ -163,20 +165,19 @@ Java::
163165
Sort.by(Person::getFirstName, Person::getLastName);
164166
165167
// Composed navigation
166-
TypedPropertyPath.of(Person::getAddress)
167-
.then(Address::getCity);
168+
Sort.by(TypedPropertyPath.of(Person::getAddress).then(Address::getCity),
169+
Person::getLastName);
168170
----
169171
170172
Kotlin::
171173
+
172174
[source,kotlin,role="secondary"]
173175
----
174176
// Inline usage with Sort
175-
Sort.by(Person::firstName, Person::lastName);
177+
Sort.by(Person::firstName, Person::lastName)
176178
177-
// Kotlin extension with composed navigation
178-
KTypedPropertyPath.of(Person::address)
179-
.then(Address::city)
179+
// Composed navigation
180+
Sort.by((Person::address / Address::city).toPath(), Person::lastName)
180181
----
181182
======
182183

src/main/java/org/springframework/data/core/TypedPropertyPath.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
* @see #then(TypedPropertyPath)
6464
*/
6565
@FunctionalInterface
66-
public interface TypedPropertyPath<T, P> extends PropertyPath, Serializable {
66+
public interface TypedPropertyPath<T, P extends @Nullable Object> extends PropertyPath, Serializable {
6767

6868
/**
6969
* Syntax sugar to create a {@link TypedPropertyPath} from a method reference or lambda.

src/main/java/org/springframework/data/core/TypedPropertyPaths.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class TypedPropertyPaths {
5757
* Compose a {@link TypedPropertyPath} by appending {@code next}.
5858
*/
5959
@SuppressWarnings({ "rawtypes", "unchecked" })
60-
public static <T, M, R> TypedPropertyPath<T, R> compose(TypedPropertyPath<T, M> owner, TypedPropertyPath<M, R> next) {
60+
public static <T, M, P> TypedPropertyPath<T, P> compose(TypedPropertyPath<T, M> owner, TypedPropertyPath<M, P> next) {
6161

6262
if (owner instanceof ForwardingPropertyPath<?, ?, ?> fwd) {
6363

@@ -373,10 +373,10 @@ static class ResolvedKPropertyPath<T, P> extends ResolvedTypedPropertyPathSuppor
373373
* @param leaf cached leaf property.
374374
* @param toStringRepresentation cached toString representation.
375375
*/
376-
record ForwardingPropertyPath<T, M, R>(TypedPropertyPath<T, M> self, TypedPropertyPath<M, R> nextSegment,
377-
PropertyPath leaf, String dotPath, String toStringRepresentation) implements TypedPropertyPath<T, R> {
376+
record ForwardingPropertyPath<T, M, P>(TypedPropertyPath<T, M> self, TypedPropertyPath<M, P> nextSegment,
377+
PropertyPath leaf, String dotPath, String toStringRepresentation) implements TypedPropertyPath<T, P> {
378378

379-
public ForwardingPropertyPath(TypedPropertyPath<T, M> self, TypedPropertyPath<M, R> nextSegment) {
379+
public ForwardingPropertyPath(TypedPropertyPath<T, M> self, TypedPropertyPath<M, P> nextSegment) {
380380
this(self, nextSegment, nextSegment.getLeafProperty(), getDotPath(self, nextSegment),
381381
getToString(self, nextSegment));
382382
}
@@ -394,7 +394,7 @@ public static PropertyPath getSelf(PropertyPath path) {
394394
}
395395

396396
@Override
397-
public @Nullable R get(T obj) {
397+
public @Nullable P get(T obj) {
398398
M intermediate = self.get(obj);
399399
return intermediate != null ? nextSegment.get(intermediate) : null;
400400
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2025 the original author or authors.
2+
* Copyright 2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,51 +18,28 @@ package org.springframework.data.core
1818
import kotlin.reflect.KProperty
1919
import kotlin.reflect.KProperty1
2020

21+
2122
/**
22-
* Abstraction of a property path consisting of [KProperty].
23+
* Extension for [KProperty] providing an `toPath` function to render a [KProperty] in dot notation.
2324
*
24-
* @author Tjeu Kayim
2525
* @author Mark Paluch
26-
* @author Yoann de Martino
2726
* @since 2.5
27+
* @see org.springframework.data.core.PropertyPath.toDotPath
2828
*/
29-
internal class KPropertyPath<T, U>(
30-
val parent: KProperty<U?>,
31-
val child: KProperty1<U, T>
32-
) : KProperty<T> by child
29+
fun KProperty<*>.toDotPath(): String = asString(this)
3330

3431
/**
35-
* Abstraction of a property path that consists of parent [KProperty],
36-
* and child property [KProperty], where parent [parent] has an [Iterable]
37-
* of children, so it represents 1-M mapping.
32+
* Extension for [KProperty1] providing an `toPath` function to create a [TypedPropertyPath].
3833
*
39-
* @author Mikhail Polivakha
40-
* @since 3.5
41-
*/
42-
internal class KIterablePropertyPath<T, U>(
43-
val parent: KProperty<Iterable<U?>?>,
44-
val child: KProperty1<U, T>
45-
) : KProperty<T> by child
46-
47-
/**
48-
* Recursively construct field name for a nested property.
49-
* @author Tjeu Kayim
50-
* @author Mikhail Polivakha
34+
* @author Mark Paluch
35+
* @since 4.1
36+
* @see org.springframework.data.core.PropertyPath.toDotPath
5137
*/
52-
fun asString(property: KProperty<*>): String {
53-
return when (property) {
54-
is KPropertyPath<*, *> ->
55-
"${asString(property.parent)}.${property.child.name}"
56-
57-
is KIterablePropertyPath<*, *> ->
58-
"${asString(property.parent)}.${property.child.name}"
59-
60-
else -> property.name
61-
}
62-
}
38+
fun <T : Any, P> KProperty1<T, P>.toPath(): TypedPropertyPath<T, P> =
39+
KTypedPropertyPath.of(this)
6340

6441
/**
65-
* Builds [KPropertyPath] from Property References.
42+
* Builds [KPropertyReference] from Property References.
6643
* Refer to a nested property in an embeddable or association.
6744
*
6845
* For example, referring to the field "author.name":
@@ -71,14 +48,14 @@ fun asString(property: KProperty<*>): String {
7148
* ```
7249
* @author Tjeu Kayim
7350
* @author Yoann de Martino
74-
* @since 2.5
51+
* @since 4.1
7552
*/
7653
@JvmName("div")
77-
operator fun <T, U> KProperty<T?>.div(other: KProperty1<T, U>): KProperty<U> =
78-
KPropertyPath(this, other)
54+
operator fun <T, M, P> KProperty1<T, M?>.div(other: KProperty1<M, P?>): KProperty1<T, P> =
55+
KSinglePropertyReference(this, other) as KProperty1<T, P>
7956

8057
/**
81-
* Builds [KPropertyPath] from Property References.
58+
* Builds [KPropertyReference] from Property References.
8259
* Refer to a nested property in an embeddable or association.
8360
*
8461
* Note, that this function is different from [div] above in the
@@ -91,8 +68,8 @@ operator fun <T, U> KProperty<T?>.div(other: KProperty1<T, U>): KProperty<U> =
9168
* Author::books / Book::title contains "Bartleby"
9269
* ```
9370
* @author Mikhail Polivakha
94-
* @since 3.5
71+
* @since 4.1
9572
*/
9673
@JvmName("divIterable")
97-
operator fun <T, U> KProperty<Collection<T?>?>.div(other: KProperty1<T, U>): KProperty<U> =
98-
KIterablePropertyPath(this, other)
74+
operator fun <T, M, P> KProperty1<T, Collection<M?>?>.div(other: KProperty1<M, P?>): KProperty1<T, P> =
75+
KIterablePropertyReference(this, other) as KProperty1<T, P>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2018-2025 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+
@file:Suppress("UNCHECKED_CAST")
17+
18+
package org.springframework.data.core
19+
20+
import kotlin.reflect.KProperty
21+
import kotlin.reflect.KProperty1
22+
23+
/**
24+
* Abstraction of a property path consisting of [KProperty1].
25+
*
26+
* @author Tjeu Kayim
27+
* @author Mark Paluch
28+
* @author Yoann de Martino
29+
* @since 4.1
30+
*/
31+
internal interface KPropertyReference<T, out P> : KProperty1<T, P> {
32+
val property: KProperty1<T, *>
33+
val leaf: KProperty1<*, P>
34+
}
35+
36+
internal class KSinglePropertyReference<T, M, out P>(
37+
val parent: KProperty1<T, M?>,
38+
val child: KProperty1<M, P>
39+
) : KProperty1<T, P> by child as KProperty1<T, P>, KPropertyReference<T, P> {
40+
41+
override fun get(receiver: T): P {
42+
43+
val get = parent.get(receiver)
44+
45+
if (get != null) {
46+
return child.get(get)
47+
}
48+
49+
throw NullPointerException("Parent property returned null")
50+
}
51+
52+
override fun getDelegate(receiver: T): Any {
53+
return child
54+
}
55+
56+
override val property: KProperty1<T, *>
57+
get() = parent
58+
override val leaf: KProperty1<*, P>
59+
get() = child
60+
}
61+
62+
/**
63+
* Abstraction of a property path that consists of parent [KProperty],
64+
* and child property [KProperty], where parent [parent] has an [Iterable]
65+
* of children, so it represents 1-M mapping.
66+
*
67+
* @author Mikhail Polivakha
68+
* @since 4.1
69+
*/
70+
71+
internal class KIterablePropertyReference<T, M, out P>(
72+
val parent: KProperty1<T, Iterable<M?>?>,
73+
val child: KProperty1<M, P>
74+
) : KProperty1<T, P> by child as KProperty1<T, P>, KPropertyReference<T, P> {
75+
76+
override fun get(receiver: T): P {
77+
throw UnsupportedOperationException("Collection retrieval not supported")
78+
}
79+
80+
override fun getDelegate(receiver: T): Any {
81+
throw UnsupportedOperationException("Collection retrieval not supported")
82+
}
83+
84+
override val property: KProperty1<T, *>
85+
get() = parent
86+
override val leaf: KProperty1<*, P>
87+
get() = child
88+
}
89+
90+
91+
/**
92+
* Recursively construct field name for a nested property.
93+
* @author Tjeu Kayim
94+
* @author Mikhail Polivakha
95+
*/
96+
internal fun asString(property: KProperty<*>): String {
97+
return when (property) {
98+
is KPropertyReference<*, *> ->
99+
"${asString(property.property)}.${property.leaf.name}"
100+
101+
else -> property.name
102+
}
103+
}
104+

0 commit comments

Comments
 (0)