From fa1cf89dbc20b9cc8ebd425bdb60344fbdde0868 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 27 Oct 2025 11:54:33 +0100 Subject: [PATCH] Retrofit Sort with TypedPropertyPath. --- .../org/springframework/data/domain/Sort.java | 103 ++++++++++++++++++ .../data/domain/SortUnitTests.java | 55 +++++++++- 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/domain/Sort.java b/src/main/java/org/springframework/data/domain/Sort.java index f3d39640d..4b3974933 100644 --- a/src/main/java/org/springframework/data/domain/Sort.java +++ b/src/main/java/org/springframework/data/domain/Sort.java @@ -31,7 +31,9 @@ import org.jspecify.annotations.Nullable; import org.springframework.data.util.MethodInvocationRecorder; import org.springframework.data.util.MethodInvocationRecorder.Recorded; +import org.springframework.data.util.PropertyPath; import org.springframework.data.util.Streamable; +import org.springframework.data.util.TypedPropertyPath; import org.springframework.lang.CheckReturnValue; import org.springframework.lang.Contract; import org.springframework.util.Assert; @@ -94,6 +96,24 @@ public class Sort implements Streamable Sort by(TypedPropertyPath... properties) { + + Assert.notNull(properties, "Properties must not be null"); + + return properties.length == 0 // + ? Sort.unsorted() // + : new Sort(DEFAULT_DIRECTION, Arrays.stream(properties).map(TypedPropertyPath::of).map(PropertyPath::toDotPath) + .collect(Collectors.toList())); + } + /** * Creates a new {@link Sort} for the given {@link Order}s. * @@ -120,6 +140,25 @@ public class Sort implements Streamable Sort by(Direction direction, TypedPropertyPath... properties) { + + Assert.notNull(direction, "Direction must not be null"); + Assert.notNull(properties, "Properties must not be null"); + Assert.isTrue(properties.length > 0, "At least one property must be given"); + + return by(Arrays.stream(properties).map(TypedPropertyPath::of).map(PropertyPath::toDotPath) + .map(it -> new Order(direction, it)).toList()); + } + /** * Creates a new {@link Sort} for the given {@link Direction} and properties. * @@ -144,7 +183,9 @@ public class Sort implements Streamable TypedSort sort(Class type) { return new TypedSort<>(type); } @@ -460,6 +501,17 @@ public class Sort implements Streamable Order by(TypedPropertyPath propertyPath) { + return by(TypedPropertyPath.of(propertyPath).toDotPath()); + } + /** * Creates a new {@link Order} instance. Takes a single property. Direction defaults to * {@link Sort#DEFAULT_DIRECTION}. @@ -471,6 +523,17 @@ public class Sort implements Streamable Order asc(TypedPropertyPath propertyPath) { + return asc(TypedPropertyPath.of(propertyPath).toDotPath()); + } + /** * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and * NullHandling {@link NullHandling#NATIVE}. @@ -482,6 +545,17 @@ public class Sort implements Streamable Order desc(TypedPropertyPath propertyPath) { + return desc(TypedPropertyPath.of(propertyPath).toDotPath()); + } + /** * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#DESC} and * NullHandling {@link NullHandling#NATIVE}. @@ -562,6 +636,19 @@ public class Sort implements Streamable new") + @CheckReturnValue + public Order withProperty(TypedPropertyPath propertyPath) { + return withProperty(TypedPropertyPath.of(propertyPath).toDotPath()); + } + /** * Returns a new {@link Order} with the {@code property} name applied. * @@ -575,6 +662,20 @@ public class Sort implements Streamable new") + @CheckReturnValue + public Sort withProperties(TypedPropertyPath... propertyPaths) { + return Sort.by(this.direction, + Arrays.stream(propertyPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new)); + } + /** * Returns a new {@link Sort} instance for the given properties using {@link #getDirection()}. * @@ -696,7 +797,9 @@ public class Sort implements Streamable extends Sort { private static final @Serial long serialVersionUID = -3550403511206745880L; diff --git a/src/test/java/org/springframework/data/domain/SortUnitTests.java b/src/test/java/org/springframework/data/domain/SortUnitTests.java index 7d03e8c6d..4e0b1c853 100755 --- a/src/test/java/org/springframework/data/domain/SortUnitTests.java +++ b/src/test/java/org/springframework/data/domain/SortUnitTests.java @@ -24,6 +24,8 @@ import org.junit.jupiter.api.Test; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.data.geo.Circle; +import org.springframework.data.mapping.Person; +import org.springframework.data.util.TypedPropertyPath; /** * Unit test for {@link Sort}. @@ -44,6 +46,29 @@ class SortUnitTests { assertThat(Sort.by("foo").iterator().next().getDirection()).isEqualTo(Sort.DEFAULT_DIRECTION); } + @Test + void appliesDefaultForOrderForProperty() { + assertThat(Sort.by(Person::getFirstName).iterator().next().getDirection()).isEqualTo(Sort.DEFAULT_DIRECTION); + } + + @Test + void appliesPropertyPath() { + + record PersonHolder(Person person) { + } + + assertThat(Sort.by(Person::getFirstName).iterator().next().getProperty()).isEqualTo("firstName"); + assertThat( + Sort.by(TypedPropertyPath.of(PersonHolder::person).then(Person::getFirstName)).iterator().next().getProperty()) + .isEqualTo("person.firstName"); + } + + @Test + void appliesPropertyPaths() { + assertThat(Sort.by(Person::getFirstName, Person::getLastName).stream().map(Order::getProperty)) + .containsSequence("firstName", "lastName"); + } + /** * Asserts that the class rejects {@code null} as properties array. */ @@ -74,7 +99,7 @@ class SortUnitTests { */ @Test void preventsNoProperties() { - assertThatIllegalArgumentException().isThrownBy(() -> Sort.by(Direction.ASC)); + assertThatIllegalArgumentException().isThrownBy(() -> Sort.by(Direction.ASC, new String[0])); } @Test @@ -108,6 +133,14 @@ class SortUnitTests { assertThat(Order.desc("foo").isIgnoreCase()).isFalse(); } + @Test + void orderFactoryMethodsConsiderPropertyPath() { + + assertThat(Order.by(Person::getFirstName)).isEqualTo(Order.by("firstName")); + assertThat(Order.asc(Person::getFirstName)).isEqualTo(Order.asc("firstName")); + assertThat(Order.desc(Person::getFirstName)).isEqualTo(Order.desc("firstName")); + } + @Test // DATACMNS-1021 void createsOrderWithDirection() { @@ -166,6 +199,26 @@ class SortUnitTests { assertThat(result.isIgnoreCase()).isEqualTo(source.isIgnoreCase()); } + @Test + void createsNewOrderForDifferentPropertyPath() { + + var source = Order.desc("foo").nullsFirst().ignoreCase(); + var result = source.withProperty(Person::getFirstName); + + assertThat(result.getProperty()).isEqualTo("firstName"); + assertThat(result.getDirection()).isEqualTo(source.getDirection()); + assertThat(result.getNullHandling()).isEqualTo(source.getNullHandling()); + assertThat(result.isIgnoreCase()).isEqualTo(source.isIgnoreCase()); + } + + @Test + void createsNewOrderFromPaths() { + + var sort = Order.desc("foo").withProperties(Person::getFirstName, Person::getLastName); + + assertThat(sort).isEqualTo(Sort.by(Direction.DESC, "firstName", "lastName")); + } + @Test @SuppressWarnings("null") void preventsNullDirection() {