Skip to content

Commit cfde864

Browse files
committed
Retrofit Sort with TypedPropertyPath.
1 parent 46add89 commit cfde864

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

src/main/java/org/springframework/data/domain/Sort.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232
import org.springframework.data.util.MethodInvocationRecorder;
3333
import org.springframework.data.util.MethodInvocationRecorder.Recorded;
34+
import org.springframework.data.util.PropertyPath;
3435
import org.springframework.data.util.Streamable;
36+
import org.springframework.data.util.TypedPropertyPath;
3537
import org.springframework.lang.CheckReturnValue;
3638
import org.springframework.lang.Contract;
3739
import org.springframework.util.Assert;
@@ -94,6 +96,24 @@ public static Sort by(String... properties) {
9496
: new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
9597
}
9698

99+
/**
100+
* Creates a new {@link Sort} for the given properties.
101+
*
102+
* @param properties must not be {@literal null}.
103+
* @return {@link Sort} for the given properties.
104+
* @since 4.1
105+
*/
106+
@SafeVarargs
107+
public static <T> Sort by(TypedPropertyPath<T, ?>... properties) {
108+
109+
Assert.notNull(properties, "Properties must not be null");
110+
111+
return properties.length == 0 //
112+
? Sort.unsorted() //
113+
: new Sort(DEFAULT_DIRECTION, Arrays.stream(properties).map(TypedPropertyPath::of).map(PropertyPath::toDotPath)
114+
.collect(Collectors.toList()));
115+
}
116+
97117
/**
98118
* Creates a new {@link Sort} for the given {@link Order}s.
99119
*
@@ -120,6 +140,25 @@ public static Sort by(Order... orders) {
120140
return new Sort(Arrays.asList(orders));
121141
}
122142

143+
/**
144+
* Creates a new {@link Sort} for the given {@link Direction} and properties.
145+
*
146+
* @param direction must not be {@literal null}.
147+
* @param properties must not be {@literal null}.
148+
* @return {@link Sort} for the given {@link Direction} and properties.
149+
* @since 4.1
150+
*/
151+
@SafeVarargs
152+
public static <T> Sort by(Direction direction, TypedPropertyPath<T, ?>... properties) {
153+
154+
Assert.notNull(direction, "Direction must not be null");
155+
Assert.notNull(properties, "Properties must not be null");
156+
Assert.isTrue(properties.length > 0, "At least one property must be given");
157+
158+
return by(Arrays.stream(properties).map(TypedPropertyPath::of).map(PropertyPath::toDotPath)
159+
.map(it -> new Order(direction, it)).toList());
160+
}
161+
123162
/**
124163
* Creates a new {@link Sort} for the given {@link Direction} and properties.
125164
*
@@ -144,7 +183,9 @@ public static Sort by(Direction direction, String... properties) {
144183
* @param type must not be {@literal null}.
145184
* @return {@link TypedSort} for the given type.
146185
* @since 2.2
186+
* @deprecated since 4.1 in favor of {@link Sort#by(TypedPropertyPath[])}.
147187
*/
188+
@Deprecated(since = "4.1")
148189
public static <T> TypedSort<T> sort(Class<T> type) {
149190
return new TypedSort<>(type);
150191
}
@@ -460,6 +501,17 @@ public Order(@Nullable Direction direction, String property, boolean ignoreCase,
460501
this.nullHandling = nullHandling;
461502
}
462503

504+
/**
505+
* Creates a new {@link Order} instance. Takes a property path. Direction defaults to
506+
* {@link Sort#DEFAULT_DIRECTION}.
507+
*
508+
* @param propertyPath must not be {@literal null} or empty.
509+
* @since 4.1
510+
*/
511+
public static <T, P> Order by(TypedPropertyPath<T, P> propertyPath) {
512+
return by(TypedPropertyPath.of(propertyPath).toDotPath());
513+
}
514+
463515
/**
464516
* Creates a new {@link Order} instance. Takes a single property. Direction defaults to
465517
* {@link Sort#DEFAULT_DIRECTION}.
@@ -471,6 +523,17 @@ public static Order by(String property) {
471523
return new Order(DEFAULT_DIRECTION, property);
472524
}
473525

526+
/**
527+
* Creates a new {@link Order} instance. Takes a property path. Direction is {@link Direction#ASC} and NullHandling
528+
* {@link NullHandling#NATIVE}.
529+
*
530+
* @param propertyPath must not be {@literal null} or empty.
531+
* @since 4.1
532+
*/
533+
public static <T, P> Order asc(TypedPropertyPath<T, P> propertyPath) {
534+
return asc(TypedPropertyPath.of(propertyPath).toDotPath());
535+
}
536+
474537
/**
475538
* Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and
476539
* NullHandling {@link NullHandling#NATIVE}.
@@ -482,6 +545,17 @@ public static Order asc(String property) {
482545
return new Order(Direction.ASC, property, DEFAULT_NULL_HANDLING);
483546
}
484547

548+
/**
549+
* Creates a new {@link Order} instance. Takes a property path. Direction is {@link Direction#DESC} and NullHandling
550+
* {@link NullHandling#NATIVE}.
551+
*
552+
* @param propertyPath must not be {@literal null} or empty.
553+
* @since 4.1
554+
*/
555+
public static <T, P> Order desc(TypedPropertyPath<T, P> propertyPath) {
556+
return desc(TypedPropertyPath.of(propertyPath).toDotPath());
557+
}
558+
485559
/**
486560
* Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#DESC} and
487561
* NullHandling {@link NullHandling#NATIVE}.
@@ -562,6 +636,19 @@ public Order reverse() {
562636
return with(this.direction == Direction.ASC ? Direction.DESC : Direction.ASC);
563637
}
564638

639+
/**
640+
* Returns a new {@link Order} with the {@code propertyPath} applied.
641+
*
642+
* @param propertyPath must not be {@literal null} or empty.
643+
* @return a new {@link Order} with the {@code propertyPath} applied.
644+
* @since 4.1
645+
*/
646+
@Contract("_ -> new")
647+
@CheckReturnValue
648+
public <T, P> Order withProperty(TypedPropertyPath<T, P> propertyPath) {
649+
return withProperty(TypedPropertyPath.of(propertyPath).toDotPath());
650+
}
651+
565652
/**
566653
* Returns a new {@link Order} with the {@code property} name applied.
567654
*
@@ -575,6 +662,20 @@ public Order withProperty(String property) {
575662
return new Order(this.direction, property, this.ignoreCase, this.nullHandling);
576663
}
577664

665+
/**
666+
* Returns a new {@link Sort} instance for the given properties using {@link #getDirection()}.
667+
*
668+
* @param propertyPaths properties to sort by.
669+
* @return a new {@link Sort} instance for the given properties using {@link #getDirection()}.
670+
* @since 4.1
671+
*/
672+
@Contract("_ -> new")
673+
@CheckReturnValue
674+
public <T> Sort withProperties(TypedPropertyPath<T, ?>... propertyPaths) {
675+
return Sort.by(this.direction,
676+
Arrays.stream(propertyPaths).map(TypedPropertyPath::toDotPath).toArray(String[]::new));
677+
}
678+
578679
/**
579680
* Returns a new {@link Sort} instance for the given properties using {@link #getDirection()}.
580681
*
@@ -696,7 +797,9 @@ public String toString() {
696797
* @author Oliver Gierke
697798
* @since 2.2
698799
* @soundtrack The Intersphere - Linger (The Grand Delusion)
800+
* @deprecated since 4.1 in favor of {@link Sort#by(org.springframework.data.util.TypedPropertyPath...)}
699801
*/
802+
@Deprecated(since = "4.1")
700803
public static class TypedSort<T> extends Sort {
701804

702805
private static final @Serial long serialVersionUID = -3550403511206745880L;

src/test/java/org/springframework/data/domain/SortUnitTests.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.springframework.data.domain.Sort.Direction;
2525
import org.springframework.data.domain.Sort.Order;
2626
import org.springframework.data.geo.Circle;
27+
import org.springframework.data.mapping.Person;
28+
import org.springframework.data.util.TypedPropertyPath;
2729

2830
/**
2931
* Unit test for {@link Sort}.
@@ -44,6 +46,29 @@ void appliesDefaultForOrder() {
4446
assertThat(Sort.by("foo").iterator().next().getDirection()).isEqualTo(Sort.DEFAULT_DIRECTION);
4547
}
4648

49+
@Test
50+
void appliesDefaultForOrderForProperty() {
51+
assertThat(Sort.by(Person::getFirstName).iterator().next().getDirection()).isEqualTo(Sort.DEFAULT_DIRECTION);
52+
}
53+
54+
@Test
55+
void appliesPropertyPath() {
56+
57+
record PersonHolder(Person person) {
58+
}
59+
60+
assertThat(Sort.by(Person::getFirstName).iterator().next().getProperty()).isEqualTo("firstName");
61+
assertThat(
62+
Sort.by(TypedPropertyPath.of(PersonHolder::person).then(Person::getFirstName)).iterator().next().getProperty())
63+
.isEqualTo("person.firstName");
64+
}
65+
66+
@Test
67+
void appliesPropertyPaths() {
68+
assertThat(Sort.by(Person::getFirstName, Person::getLastName).stream().map(Order::getProperty))
69+
.containsSequence("firstName", "lastName");
70+
}
71+
4772
/**
4873
* Asserts that the class rejects {@code null} as properties array.
4974
*/
@@ -74,7 +99,7 @@ void preventsEmptyProperty() {
7499
*/
75100
@Test
76101
void preventsNoProperties() {
77-
assertThatIllegalArgumentException().isThrownBy(() -> Sort.by(Direction.ASC));
102+
assertThatIllegalArgumentException().isThrownBy(() -> Sort.by(Direction.ASC, new String[0]));
78103
}
79104

80105
@Test
@@ -108,6 +133,14 @@ void orderDoesNotIgnoreCaseByDefault() {
108133
assertThat(Order.desc("foo").isIgnoreCase()).isFalse();
109134
}
110135

136+
@Test
137+
void orderFactoryMethodsConsiderPropertyPath() {
138+
139+
assertThat(Order.by(Person::getFirstName)).isEqualTo(Order.by("firstName"));
140+
assertThat(Order.asc(Person::getFirstName)).isEqualTo(Order.asc("firstName"));
141+
assertThat(Order.desc(Person::getFirstName)).isEqualTo(Order.desc("firstName"));
142+
}
143+
111144
@Test // DATACMNS-1021
112145
void createsOrderWithDirection() {
113146

@@ -166,6 +199,26 @@ void createsNewOrderForDifferentProperty() {
166199
assertThat(result.isIgnoreCase()).isEqualTo(source.isIgnoreCase());
167200
}
168201

202+
@Test
203+
void createsNewOrderForDifferentPropertyPath() {
204+
205+
var source = Order.desc("foo").nullsFirst().ignoreCase();
206+
var result = source.withProperty(Person::getFirstName);
207+
208+
assertThat(result.getProperty()).isEqualTo("firstName");
209+
assertThat(result.getDirection()).isEqualTo(source.getDirection());
210+
assertThat(result.getNullHandling()).isEqualTo(source.getNullHandling());
211+
assertThat(result.isIgnoreCase()).isEqualTo(source.isIgnoreCase());
212+
}
213+
214+
@Test
215+
void createsNewOrderFromPaths() {
216+
217+
var sort = Order.desc("foo").withProperties(Person::getFirstName, Person::getLastName);
218+
219+
assertThat(sort).isEqualTo(Sort.by(Direction.DESC, "firstName", "lastName"));
220+
}
221+
169222
@Test
170223
@SuppressWarnings("null")
171224
void preventsNullDirection() {

0 commit comments

Comments
 (0)