1 /*
2 * Copyright 2008-2020 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 package org.springframework.data.domain;
17
18 import lombok.AccessLevel;
19 import lombok.RequiredArgsConstructor;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Optional;
29 import java.util.function.Function;
30 import java.util.stream.Collectors;
31
32 import org.springframework.data.util.MethodInvocationRecorder;
33 import org.springframework.data.util.MethodInvocationRecorder.Recorded;
34 import org.springframework.data.util.Streamable;
35 import org.springframework.lang.Nullable;
36 import org.springframework.util.Assert;
37 import org.springframework.util.StringUtils;
38
39 /**
40 * Sort option for queries. You have to provide at least a list of properties to sort for that must not include
41 * {@literal null} or empty strings. The direction defaults to {@link Sort#DEFAULT_DIRECTION}.
42 *
43 * @author Oliver Gierke
44 * @author Thomas Darimont
45 * @author Mark Paluch
46 */
47 @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
48 public class Sort implements Streamable<org.springframework.data.domain.Sort.Order>, Serializable {
49
50 private static final long serialVersionUID = 5737186511678863905L;
51
52 private static final Sort UNSORTED = Sort.by(new Order[0]);
53
54 public static final Direction DEFAULT_DIRECTION = Direction.ASC;
55
56 private final List<Order> orders;
57
58 /**
59 * Creates a new {@link Sort} instance.
60 *
61 * @param direction defaults to {@link Sort#DEFAULT_DIRECTION} (for {@literal null} cases, too)
62 * @param properties must not be {@literal null} or contain {@literal null} or empty strings.
63 */
64 private Sort(Direction direction, List<String> properties) {
65
66 if (properties == null || properties.isEmpty()) {
67 throw new IllegalArgumentException("You have to provide at least one property to sort by!");
68 }
69
70 this.orders = properties.stream() //
71 .map(it -> new Order(direction, it)) //
72 .collect(Collectors.toList());
73 }
74
75 /**
76 * Creates a new {@link Sort} for the given properties.
77 *
78 * @param properties must not be {@literal null}.
79 * @return
80 */
81 public static Sort by(String... properties) {
82
83 Assert.notNull(properties, "Properties must not be null!");
84
85 return properties.length == 0 //
86 ? Sort.unsorted() //
87 : new Sort(DEFAULT_DIRECTION, Arrays.asList(properties));
88 }
89
90 /**
91 * Creates a new {@link Sort} for the given {@link Order}s.
92 *
93 * @param orders must not be {@literal null}.
94 * @return
95 */
96 public static Sort by(List<Order> orders) {
97
98 Assert.notNull(orders, "Orders must not be null!");
99
100 return orders.isEmpty() ? Sort.unsorted() : new Sort(orders);
101 }
102
103 /**
104 * Creates a new {@link Sort} for the given {@link Order}s.
105 *
106 * @param orders must not be {@literal null}.
107 * @return
108 */
109 public static Sort by(Order... orders) {
110
111 Assert.notNull(orders, "Orders must not be null!");
112
113 return new Sort(Arrays.asList(orders));
114 }
115
116 /**
117 * Creates a new {@link Sort} for the given {@link Order}s.
118 *
119 * @param direction must not be {@literal null}.
120 * @param properties must not be {@literal null}.
121 * @return
122 */
123 public static Sort by(Direction direction, String... properties) {
124
125 Assert.notNull(direction, "Direction must not be null!");
126 Assert.notNull(properties, "Properties must not be null!");
127 Assert.isTrue(properties.length > 0, "At least one property must be given!");
128
129 return Sort.by(Arrays.stream(properties)//
130 .map(it -> new Order(direction, it))//
131 .collect(Collectors.toList()));
132 }
133
134 /**
135 * Creates a new {@link TypedSort} for the given type.
136 *
137 * @param type must not be {@literal null}.
138 * @return
139 * @since 2.2
140 */
141 public static <T> TypedSort<T> sort(Class<T> type) {
142 return new TypedSort<>(type);
143 }
144
145 /**
146 * Returns a {@link Sort} instances representing no sorting setup at all.
147 *
148 * @return
149 */
150 public static Sort unsorted() {
151 return UNSORTED;
152 }
153
154 /**
155 * Returns a new {@link Sort} with the current setup but descending order direction.
156 *
157 * @return
158 */
159 public Sort descending() {
160 return withDirection(Direction.DESC);
161 }
162
163 /**
164 * Returns a new {@link Sort} with the current setup but ascending order direction.
165 *
166 * @return
167 */
168 public Sort ascending() {
169 return withDirection(Direction.ASC);
170 }
171
172 public boolean isSorted() {
173 return !orders.isEmpty();
174 }
175
176 public boolean isUnsorted() {
177 return !isSorted();
178 }
179
180 /**
181 * Returns a new {@link Sort} consisting of the {@link Order}s of the current {@link Sort} combined with the given
182 * ones.
183 *
184 * @param sort must not be {@literal null}.
185 * @return
186 */
187 public Sort and(Sort sort) {
188
189 Assert.notNull(sort, "Sort must not be null!");
190
191 ArrayList<Order> these = new ArrayList<>(this.orders);
192
193 for (Order order : sort) {
194 these.add(order);
195 }
196
197 return Sort.by(these);
198 }
199
200 /**
201 * Returns the order registered for the given property.
202 *
203 * @param property
204 * @return
205 */
206 @Nullable
207 public Order getOrderFor(String property) {
208
209 for (Order order : this) {
210 if (order.getProperty().equals(property)) {
211 return order;
212 }
213 }
214
215 return null;
216 }
217
218 /*
219 * (non-Javadoc)
220 * @see java.lang.Iterable#iterator()
221 */
222 public Iterator<Order> iterator() {
223 return this.orders.iterator();
224 }
225
226 /*
227 * (non-Javadoc)
228 * @see java.lang.Object#equals(java.lang.Object)
229 */
230 @Override
231 public boolean equals(@Nullable Object obj) {
232
233 if (this == obj) {
234 return true;
235 }
236
237 if (!(obj instanceof Sort)) {
238 return false;
239 }
240
241 Sort that = (Sort) obj;
242
243 return this.orders.equals(that.orders);
244 }
245
246 /*
247 * (non-Javadoc)
248 * @see java.lang.Object#hashCode()
249 */
250 @Override
251 public int hashCode() {
252
253 int result = 17;
254 result = 31 * result + orders.hashCode();
255 return result;
256 }
257
258 /*
259 * (non-Javadoc)
260 * @see java.lang.Object#toString()
261 */
262 @Override
263 public String toString() {
264 return orders.isEmpty() ? "UNSORTED" : StringUtils.collectionToCommaDelimitedString(orders);
265 }
266
267 /**
268 * Creates a new {@link Sort} with the current setup but the given order direction.
269 *
270 * @param direction
271 * @return
272 */
273 private Sort withDirection(Direction direction) {
274
275 return Sort.by(orders.stream().map(it -> new Order(direction, it.getProperty())).collect(Collectors.toList()));
276 }
277
278 /**
279 * Enumeration for sort directions.
280 *
281 * @author Oliver Gierke
282 */
283 public static enum Direction {
284
285 ASC, DESC;
286
287 /**
288 * Returns whether the direction is ascending.
289 *
290 * @return
291 * @since 1.13
292 */
293 public boolean isAscending() {
294 return this.equals(ASC);
295 }
296
297 /**
298 * Returns whether the direction is descending.
299 *
300 * @return
301 * @since 1.13
302 */
303 public boolean isDescending() {
304 return this.equals(DESC);
305 }
306
307 /**
308 * Returns the {@link Direction} enum for the given {@link String} value.
309 *
310 * @param value
311 * @throws IllegalArgumentException in case the given value cannot be parsed into an enum value.
312 * @return
313 */
314 public static Direction fromString(String value) {
315
316 try {
317 return Direction.valueOf(value.toUpperCase(Locale.US));
318 } catch (Exception e) {
319 throw new IllegalArgumentException(String.format(
320 "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).", value), e);
321 }
322 }
323
324 /**
325 * Returns the {@link Direction} enum for the given {@link String} or null if it cannot be parsed into an enum
326 * value.
327 *
328 * @param value
329 * @return
330 */
331 public static Optional<Direction> fromOptionalString(String value) {
332
333 try {
334 return Optional.of(fromString(value));
335 } catch (IllegalArgumentException e) {
336 return Optional.empty();
337 }
338 }
339 }
340
341 /**
342 * Enumeration for null handling hints that can be used in {@link Order} expressions.
343 *
344 * @author Thomas Darimont
345 * @since 1.8
346 */
347 public static enum NullHandling {
348
349 /**
350 * Lets the data store decide what to do with nulls.
351 */
352 NATIVE,
353
354 /**
355 * A hint to the used data store to order entries with null values before non null entries.
356 */
357 NULLS_FIRST,
358
359 /**
360 * A hint to the used data store to order entries with null values after non null entries.
361 */
362 NULLS_LAST;
363 }
364
365 /**
366 * PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for
367 * {@link Sort}
368 *
369 * @author Oliver Gierke
370 * @author Kevin Raymond
371 */
372 public static class Order implements Serializable {
373
374 private static final long serialVersionUID = 1522511010900108987L;
375 private static final boolean DEFAULT_IGNORE_CASE = false;
376 private static final NullHandling DEFAULT_NULL_HANDLING = NullHandling.NATIVE;
377
378 private final Direction direction;
379 private final String property;
380 private final boolean ignoreCase;
381 private final NullHandling nullHandling;
382
383 /**
384 * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
385 * {@link Sort#DEFAULT_DIRECTION}
386 *
387 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
388 * @param property must not be {@literal null} or empty.
389 */
390 public Order(@Nullable Direction direction, String property) {
391 this(direction, property, DEFAULT_IGNORE_CASE, DEFAULT_NULL_HANDLING);
392 }
393
394 /**
395 * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
396 * {@link Sort#DEFAULT_DIRECTION}
397 *
398 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
399 * @param property must not be {@literal null} or empty.
400 * @param nullHandling must not be {@literal null}.
401 */
402 public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) {
403 this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint);
404 }
405
406 /**
407 * Creates a new {@link Order} instance. Takes a single property. Direction defaults to
408 * {@link Sort#DEFAULT_DIRECTION}.
409 *
410 * @param property must not be {@literal null} or empty.
411 * @since 2.0
412 */
413 public static Order by(String property) {
414 return new Order(DEFAULT_DIRECTION, property);
415 }
416
417 /**
418 * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and
419 * NullHandling {@link NullHandling#NATIVE}.
420 *
421 * @param property must not be {@literal null} or empty.
422 * @since 2.0
423 */
424 public static Order asc(String property) {
425 return new Order(Direction.ASC, property, DEFAULT_NULL_HANDLING);
426 }
427
428 /**
429 * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#DESC} and
430 * NullHandling {@link NullHandling#NATIVE}.
431 *
432 * @param property must not be {@literal null} or empty.
433 * @since 2.0
434 */
435 public static Order desc(String property) {
436 return new Order(Direction.DESC, property, DEFAULT_NULL_HANDLING);
437 }
438
439 /**
440 * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
441 * {@link Sort#DEFAULT_DIRECTION}
442 *
443 * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
444 * @param property must not be {@literal null} or empty.
445 * @param ignoreCase true if sorting should be case insensitive. false if sorting should be case sensitive.
446 * @param nullHandling must not be {@literal null}.
447 * @since 1.7
448 */
449 private Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) {
450
451 if (!StringUtils.hasText(property)) {
452 throw new IllegalArgumentException("Property must not null or empty!");
453 }
454
455 this.direction = direction == null ? DEFAULT_DIRECTION : direction;
456 this.property = property;
457 this.ignoreCase = ignoreCase;
458 this.nullHandling = nullHandling;
459 }
460
461 /**
462 * Returns the order the property shall be sorted for.
463 *
464 * @return
465 */
466 public Direction getDirection() {
467 return direction;
468 }
469
470 /**
471 * Returns the property to order for.
472 *
473 * @return
474 */
475 public String getProperty() {
476 return property;
477 }
478
479 /**
480 * Returns whether sorting for this property shall be ascending.
481 *
482 * @return
483 */
484 public boolean isAscending() {
485 return this.direction.isAscending();
486 }
487
488 /**
489 * Returns whether sorting for this property shall be descending.
490 *
491 * @return
492 * @since 1.13
493 */
494 public boolean isDescending() {
495 return this.direction.isDescending();
496 }
497
498 /**
499 * Returns whether or not the sort will be case sensitive.
500 *
501 * @return
502 */
503 public boolean isIgnoreCase() {
504 return ignoreCase;
505 }
506
507 /**
508 * Returns a new {@link Order} with the given {@link Direction}.
509 *
510 * @param direction
511 * @return
512 */
513 public Order with(Direction direction) {
514 return new Order(direction, this.property, this.ignoreCase, this.nullHandling);
515 }
516
517 /**
518 * Returns a new {@link Order}
519 *
520 * @param property must not be {@literal null} or empty.
521 * @return
522 * @since 1.13
523 */
524 public Order withProperty(String property) {
525 return new Order(this.direction, property, this.ignoreCase, this.nullHandling);
526 }
527
528 /**
529 * Returns a new {@link Sort} instance for the given properties.
530 *
531 * @param properties
532 * @return
533 */
534 public Sort withProperties(String... properties) {
535 return Sort.by(this.direction, properties);
536 }
537
538 /**
539 * Returns a new {@link Order} with case insensitive sorting enabled.
540 *
541 * @return
542 */
543 public Order ignoreCase() {
544 return new Order(direction, property, true, nullHandling);
545 }
546
547 /**
548 * Returns a {@link Order} with the given {@link NullHandling}.
549 *
550 * @param nullHandling can be {@literal null}.
551 * @return
552 * @since 1.8
553 */
554 public Order with(NullHandling nullHandling) {
555 return new Order(direction, this.property, ignoreCase, nullHandling);
556 }
557
558 /**
559 * Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint.
560 *
561 * @return
562 * @since 1.8
563 */
564 public Order nullsFirst() {
565 return with(NullHandling.NULLS_FIRST);
566 }
567
568 /**
569 * Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint.
570 *
571 * @return
572 * @since 1.7
573 */
574 public Order nullsLast() {
575 return with(NullHandling.NULLS_LAST);
576 }
577
578 /**
579 * Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint.
580 *
581 * @return
582 * @since 1.7
583 */
584 public Order nullsNative() {
585 return with(NullHandling.NATIVE);
586 }
587
588 /**
589 * Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore.
590 *
591 * @return
592 * @since 1.7
593 */
594 public NullHandling getNullHandling() {
595 return nullHandling;
596 }
597
598 /*
599 * (non-Javadoc)
600 * @see java.lang.Object#hashCode()
601 */
602 @Override
603 public int hashCode() {
604
605 int result = 17;
606
607 result = 31 * result + direction.hashCode();
608 result = 31 * result + property.hashCode();
609 result = 31 * result + (ignoreCase ? 1 : 0);
610 result = 31 * result + nullHandling.hashCode();
611
612 return result;
613 }
614
615 /*
616 * (non-Javadoc)
617 * @see java.lang.Object#equals(java.lang.Object)
618 */
619 @Override
620 public boolean equals(@Nullable Object obj) {
621
622 if (this == obj) {
623 return true;
624 }
625
626 if (!(obj instanceof Order)) {
627 return false;
628 }
629
630 Order that = (Order) obj;
631
632 return this.direction.equals(that.direction) && this.property.equals(that.property)
633 && this.ignoreCase == that.ignoreCase && this.nullHandling.equals(that.nullHandling);
634 }
635
636 /*
637 * (non-Javadoc)
638 * @see java.lang.Object#toString()
639 */
640 @Override
641 public String toString() {
642
643 String result = String.format("%s: %s", property, direction);
644
645 if (!NullHandling.NATIVE.equals(nullHandling)) {
646 result += ", " + nullHandling;
647 }
648
649 if (ignoreCase) {
650 result += ", ignoring case";
651 }
652
653 return result;
654 }
655 }
656
657 /**
658 * Extension of Sort to use method handles to define properties to sort by.
659 *
660 * @author Oliver Gierke
661 * @since 2.2
662 * @soundtrack The Intersphere - Linger (The Grand Delusion)
663 */
664 public static class TypedSort<T> extends Sort {
665
666 private static final long serialVersionUID = -3550403511206745880L;
667
668 private final Recorded<T> recorded;
669
670 private TypedSort(Class<T> type) {
671 this(MethodInvocationRecorder.forProxyOf(type));
672 }
673
674 private TypedSort(Recorded<T> recorded) {
675
676 super(Collections.emptyList());
677 this.recorded = recorded;
678 }
679
680 public <S> TypedSort<S> by(Function<T, S> property) {
681 return new TypedSort<>(recorded.record(property));
682 }
683
684 public <S> TypedSort<S> by(Recorded.ToCollectionConverter<T, S> collectionProperty) {
685 return new TypedSort<>(recorded.record(collectionProperty));
686 }
687
688 public <S> TypedSort<S> by(Recorded.ToMapConverter<T, S> mapProperty) {
689 return new TypedSort<>(recorded.record(mapProperty));
690 }
691
692 @Override
693 public Sort ascending() {
694 return withDirection(Sort::ascending);
695 }
696
697 @Override
698 public Sort descending() {
699 return withDirection(Sort::descending);
700 }
701
702 private Sort withDirection(Function<Sort, Sort> direction) {
703
704 return recorded.getPropertyPath() //
705 .map(Sort::by) //
706 .map(direction) //
707 .orElseGet(Sort::unsorted);
708 }
709
710 /*
711 * (non-Javadoc)
712 * @see org.springframework.data.domain.Sort#iterator()
713 */
714 @Override
715 public Iterator<Order> iterator() {
716
717 return recorded.getPropertyPath() //
718 .map(Order::by) //
719 .map(Collections::singleton) //
720 .orElseGet(Collections::emptySet).iterator();
721
722 }
723
724 /*
725 * (non-Javadoc)
726 * @see org.springframework.data.domain.Sort#toString()
727 */
728 @Override
729 public String toString() {
730
731 return recorded.getPropertyPath() //
732 .map(Sort::by) //
733 .orElseGet(Sort::unsorted) //
734 .toString();
735 }
736 }
737 }
738