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