Skip to content

Commit 42ff423

Browse files
committed
Polishing.
1 parent df02708 commit 42ff423

File tree

8 files changed

+81
-78
lines changed

8 files changed

+81
-78
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,36 @@
2222
/**
2323
* Interface providing type-safe property references.
2424
* <p>
25-
* This functional interface is typically implemented through method references or lambda expressions that allow for
25+
* This functional interface is typically implemented through method references and lambda expressions that allow for
2626
* compile-time type safety and refactoring support. Instead of string-based property names that are easy to miss when
27-
* changing the domain model, {@code TypedPropertyReference} leverages Java's declarative method references and lambda
27+
* changing the domain model, {@code PropertyReference} leverages Java's declarative method references and lambda
2828
* expressions to ensure type-safe property access.
2929
* <p>
3030
* Create a typed property reference using the static factory method {@link #of(PropertyReference)} with a method
3131
* reference or lambda, for example:
3232
*
3333
* <pre class="code">
34-
* TypedPropertyReference&lt;Person, String&gt; name = TypedPropertyReference.of(Person::getName);
34+
* PropertyReference&lt;Person, String&gt; name = PropertyReference.of(Person::getName);
3535
* </pre>
3636
*
3737
* The resulting object can be used to obtain the {@link #getName() property name} and to interact with the target
3838
* property. Typed references can be used to compose {@link TypedPropertyPath property paths} to navigate nested object
3939
* structures using {@link #then(PropertyReference)}:
4040
*
4141
* <pre class="code">
42-
* TypedPropertyPath&lt;Person, String&gt; city = TypedPropertyReference.of(Person::getAddress).then(Address::getCity);
42+
* TypedPropertyPath&lt;Person, String&gt; city = PropertyReference.of(Person::getAddress).then(Address::getCity);
4343
* </pre>
4444
* <p>
4545
* The generic type parameters preserve type information across the property path chain: {@code T} represents the owning
4646
* type of the current segment (or the root type for composed paths), while {@code P} represents the property value type
4747
* at this segment. Composition automatically flows type information forward, ensuring that {@code then()} preserves the
4848
* full chain's type safety.
4949
* <p>
50-
* Implement {@code TypedPropertyReference} using method references (strongly recommended) or lambdas that directly
51-
* access a property getter. Constructor references, method calls with parameters, and complex expressions are not
52-
* supported and result in {@link org.springframework.dao.InvalidDataAccessApiUsageException}. Unlike method references,
53-
* introspection of lambda expressions requires bytecode analysis of the declaration site classes and thus depends on
54-
* their availability at runtime.
50+
* Implement {@code PropertyReference} using method references (strongly recommended) or lambdas that directly access a
51+
* property getter. Constructor references, method calls with parameters, and complex expressions are not supported and
52+
* result in {@link org.springframework.dao.InvalidDataAccessApiUsageException}. Unlike method references, introspection
53+
* of lambda expressions requires bytecode analysis of the declaration site classes and thus depends on their
54+
* availability at runtime.
5555
*
5656
* @param <T> the owning type of this property.
5757
* @param <P> the property value type.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import org.jspecify.annotations.Nullable;
2323

2424
/**
25-
* Interface providing type-safe property path navigation through method references or lambda expressions.
25+
* Interface providing type-safe property path navigation through method references and lambda expressions.
2626
* <p>
2727
* This functional interface extends {@link PropertyPath} to provide compile-time type safety and refactoring support.
2828
* Instead of using {@link PropertyPath#from(String, TypeInformation) string-based property paths} for textual property

src/main/kotlin/org/springframework/data/core/PropertyReferenceExtensions.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,31 @@ class KPropertyReference {
5858
companion object {
5959

6060
/**
61-
* Create a [PropertyReference] from a [KProperty1].
61+
* Create a [PropertyReference] from a [KProperty1] reference.
62+
* @param property the property reference, must not be a property path.
6263
*/
6364
fun <T : Any, P : Any> of(property: KProperty1<T, P?>): PropertyReference<T, P> {
6465
return of((property as KProperty<P?>))
6566
}
6667

6768
/**
68-
* Create a [PropertyReference] from a collection-like [KProperty1].
69+
* Create a [PropertyReference] from a collection-like [KProperty1] reference.
70+
* @param property the property reference, must not be a property path.
6971
*/
7072
@JvmName("ofMany")
7173
fun <T : Any, P : Any> of(property: KProperty1<T, Iterable<P?>?>): PropertyReference<T, P> {
7274
return of((property as KProperty<P?>))
7375
}
7476

75-
7677
/**
7778
* Create a [PropertyReference] from a [KProperty].
7879
*/
7980
fun <T, P> of(property: KProperty<P?>): PropertyReference<T, P> {
8081

82+
if (property is KPropertyReferenceImpl<*, *>) {
83+
throw IllegalArgumentException("Property reference ${property.name} must not be a property path")
84+
}
85+
8186
if (property is KProperty1<*, *>) {
8287

8388
val property1 = property as KProperty1<*, *>

src/test/java/org/springframework/data/core/PropertyReferenceUnitTests.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,74 +31,74 @@
3131
*/
3232
class PropertyReferenceUnitTests {
3333

34-
@Test
34+
@Test // GH-3400
3535
void resolvesMHSimplePath() {
3636
assertThat(PropertyReference.of(PersonQuery::getName).getName()).isEqualTo("name");
3737
}
3838

39-
@Test
39+
@Test // GH-3400
4040
void resolvesMHComposedPath() {
4141
assertThat(PropertyReference.of(PersonQuery::getAddress).then(Address::getCountry).toDotPath())
4242
.isEqualTo("address.country");
4343
}
4444

45-
@Test
45+
@Test // GH-3400
4646
void resolvesCollectionPath() {
4747
assertThat(PropertyReference.ofMany(PersonQuery::getAddresses).then(Address::getCity).toDotPath())
4848
.isEqualTo("addresses.city");
4949
}
5050

51-
@Test
51+
@Test // GH-3400
5252
@SuppressWarnings("Convert2MethodRef")
5353
void resolvesInitialLambdaGetter() {
5454
assertThat(PropertyReference.of((PersonQuery person) -> person.getName()).getName()).isEqualTo("name");
5555
}
5656

57-
@Test
57+
@Test // GH-3400
5858
@SuppressWarnings("Convert2MethodRef")
5959
void resolvesComposedLambdaGetter() {
6060
assertThat(PropertyReference.of(PersonQuery::getAddress).then(it -> it.getCity()).toDotPath())
6161
.isEqualTo("address.city");
6262
}
6363

64-
@Test
64+
@Test // GH-3400
6565
void resolvesComposedLambdaFieldAccess() {
6666
assertThat(PropertyReference.of(PersonQuery::getAddress).then(it -> it.city).toDotPath()).isEqualTo("address.city");
6767
}
6868

69-
@Test
69+
@Test // GH-3400
7070
void resolvesInterfaceMethodReferenceGetter() {
7171
assertThat(PropertyReference.of(PersonProjection::getName).getName()).isEqualTo("name");
7272
}
7373

74-
@Test
74+
@Test // GH-3400
7575
@SuppressWarnings("Convert2MethodRef")
7676
void resolvesInterfaceLambdaGetter() {
7777
assertThat(PropertyReference.of((PersonProjection person) -> person.getName()).getName()).isEqualTo("name");
7878
}
7979

80-
@Test
80+
@Test // GH-3400
8181
void resolvesSuperclassMethodReferenceGetter() {
8282
assertThat(PropertyReference.of(PersonQuery::getTenant).getName()).isEqualTo("tenant");
8383
}
8484

85-
@Test
85+
@Test // GH-3400
8686
void resolvesSuperclassLambdaGetter() {
8787
assertThat(PropertyReference.of((PersonQuery person) -> person.getTenant()).getName()).isEqualTo("tenant");
8888
}
8989

90-
@Test
90+
@Test // GH-3400
9191
void resolvesPrivateMethodReference() {
9292
assertThat(PropertyReference.of(Secret::getSecret).getName()).isEqualTo("secret");
9393
}
9494

95-
@Test
95+
@Test // GH-3400
9696
@SuppressWarnings("Convert2MethodRef")
9797
void resolvesPrivateMethodLambda() {
9898
assertThat(PropertyReference.of((Secret secret) -> secret.getSecret()).getName()).isEqualTo("secret");
9999
}
100100

101-
@Test
101+
@Test // GH-3400
102102
void switchingOwningTypeFails() {
103103

104104
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
@@ -107,21 +107,21 @@ void switchingOwningTypeFails() {
107107
}));
108108
}
109109

110-
@Test
110+
@Test // GH-3400
111111
void constructorCallsShouldFail() {
112112

113113
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
114114
.isThrownBy(() -> PropertyReference.of((PersonQuery person) -> new PersonQuery(person)));
115115
}
116116

117-
@Test
117+
@Test // GH-3400
118118
void enumShouldFail() {
119119

120120
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
121121
.isThrownBy(() -> PropertyReference.of(NotSupported.INSTANCE));
122122
}
123123

124-
@Test
124+
@Test // GH-3400
125125
void returningSomethingShouldFail() {
126126

127127
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
@@ -132,7 +132,7 @@ void returningSomethingShouldFail() {
132132
.isThrownBy(() -> PropertyReference.of((PropertyReference<Object, Object>) obj -> ""));
133133
}
134134

135-
@Test
135+
@Test // GH-3400
136136
@SuppressWarnings("Convert2Lambda")
137137
void classImplementationShouldFail() {
138138

@@ -145,14 +145,14 @@ void classImplementationShouldFail() {
145145
}));
146146
}
147147

148-
@Test
148+
@Test // GH-3400
149149
void constructorMethodReferenceShouldFail() {
150150

151151
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
152152
.isThrownBy(() -> PropertyReference.<PersonQuery, PersonQuery> of(PersonQuery::new));
153153
}
154154

155-
@Test
155+
@Test // GH-3400
156156
void failsResolutionWith$StrangeStuff() {
157157

158158
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
@@ -163,7 +163,7 @@ void constructorMethodReferenceShouldFail() {
163163
}).getName());
164164
}
165165

166-
@Test
166+
@Test // GH-3400
167167
void arithmeticOpsFail() {
168168
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> {
169169
PropertyReference.of((PersonQuery person) -> {
@@ -173,7 +173,7 @@ void arithmeticOpsFail() {
173173
});
174174
}
175175

176-
@Test
176+
@Test // GH-3400
177177
void failsResolvingCallingLocalMethod() {
178178

179179
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
@@ -186,13 +186,13 @@ void failsResolvingCallingLocalMethod() {
186186
@Nested
187187
class NestedTestClass {
188188

189-
@Test
189+
@Test // GH-3400
190190
@SuppressWarnings("Convert2MethodRef")
191191
void resolvesInterfaceLambdaGetter() {
192192
assertThat(PropertyReference.of((PersonProjection person) -> person.getName()).getName()).isEqualTo("name");
193193
}
194194

195-
@Test
195+
@Test // GH-3400
196196
void resolvesSuperclassMethodReferenceGetter() {
197197
assertThat(PropertyReference.of(PersonQuery::getTenant).getName()).isEqualTo("tenant");
198198
}

0 commit comments

Comments
 (0)