1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.comparator.spring.mapper;
18
19 import static java.util.Objects.isNull;
20 import static org.springframework.util.ObjectUtils.isEmpty;
21
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
30 import org.bremersee.comparator.model.SortOrder;
31 import org.bremersee.comparator.model.SortOrderItem;
32 import org.bremersee.comparator.model.SortOrderItem.CaseHandling;
33 import org.bremersee.comparator.model.SortOrderTextSeparators;
34 import org.bremersee.comparator.spring.converter.SortOrderConverter;
35 import org.springframework.data.domain.PageRequest;
36 import org.springframework.data.domain.Pageable;
37 import org.springframework.data.domain.Sort;
38 import org.springframework.data.domain.Sort.Direction;
39 import org.springframework.data.domain.Sort.NullHandling;
40 import org.springframework.lang.NonNull;
41 import org.springframework.lang.Nullable;
42
43
44
45
46
47
48
49 public interface SortMapper {
50
51
52
53
54
55
56 static SortMapper defaultSortMapper() {
57 return defaultSortMapper(SortOrderTextSeparators.defaults());
58 }
59
60
61
62
63
64
65
66 static SortMapper defaultSortMapper(SortOrderTextSeparators sortOrderTextSeparators) {
67 return defaultSortMapper(new SortOrderConverter(sortOrderTextSeparators));
68 }
69
70
71
72
73
74
75
76 static SortMapper defaultSortMapper(SortOrderConverter sortOrderConverter) {
77 return new DefaultSortMapper(sortOrderConverter);
78 }
79
80
81
82
83
84
85
86 SortOrder getSortOrder(@Nullable String sortOrderText);
87
88
89
90
91
92
93
94 String getSortOrderText(@Nullable SortOrder sortOrder);
95
96
97
98
99
100
101
102 List<String> getSortOrderItemText(@Nullable SortOrder sortOrder);
103
104
105
106
107
108
109
110 @NonNull
111 default Sort toSort(@Nullable SortOrder sortOrder) {
112 List<Sort.Order> orderList = Stream.ofNullable(sortOrder)
113 .map(SortOrder::getItems)
114 .flatMap(Collection::stream)
115 .filter(Objects::nonNull)
116 .map(this::toSortOrder)
117 .toList();
118 return orderList.isEmpty() ? Sort.unsorted() : Sort.by(orderList);
119 }
120
121
122
123
124
125
126
127 @Nullable
128 default Sort.Order toSortOrder(@Nullable SortOrderItem sortOrderItem) {
129 if (sortOrderItem == null || sortOrderItem.getField() == null) {
130 return null;
131 }
132 Direction direction = sortOrderItem.getDirection().isAscending()
133 ? Direction.ASC
134 : Direction.DESC;
135 NullHandling nullHandling = switch (sortOrderItem.getNullHandling()) {
136 case NULLS_FIRST -> NullHandling.NULLS_FIRST;
137 case NULLS_LAST -> NullHandling.NULLS_LAST;
138 case NATIVE -> NullHandling.NATIVE;
139 };
140 Sort.Order order = new Sort.Order(direction, sortOrderItem.getField(), nullHandling);
141 return sortOrderItem.getCaseHandling().isInsensitive()
142 ? order.ignoreCase()
143 : order;
144 }
145
146
147
148
149
150
151
152
153 @NonNull
154 default SortOrder fromSort(@Nullable Sort sort) {
155 List<SortOrderItem> items = Stream.ofNullable(sort)
156 .flatMap(Sort::stream)
157 .map(this::fromSortOrder)
158 .filter(Objects::nonNull)
159 .toList();
160 return new SortOrder(items);
161 }
162
163
164
165
166
167
168
169 @Nullable
170 default SortOrderItem fromSortOrder(@Nullable Sort.Order sortOrder) {
171 if (isNull(sortOrder)) {
172 return null;
173 }
174 SortOrderItem.Direction direction = sortOrder.getDirection().isAscending()
175 ? SortOrderItem.Direction.ASC
176 : SortOrderItem.Direction.DESC;
177 CaseHandling caseHandling = sortOrder.isIgnoreCase()
178 ? CaseHandling.INSENSITIVE
179 : CaseHandling.SENSITIVE;
180 SortOrderItem.NullHandling nullHandling = switch (sortOrder.getNullHandling()) {
181 case NULLS_FIRST -> SortOrderItem.NullHandling.NULLS_FIRST;
182 case NULLS_LAST -> SortOrderItem.NullHandling.NULLS_LAST;
183 case NATIVE -> SortOrderItem.NullHandling.NATIVE;
184 };
185 return new SortOrderItem(
186 sortOrder.getProperty(),
187 direction,
188 caseHandling,
189 nullHandling);
190 }
191
192
193
194
195
196
197
198
199
200
201
202 @Nullable
203 default Pageable applyDefaults(
204 @Nullable Pageable source,
205 @Nullable Direction direction,
206 @Nullable Boolean ignoreCase,
207 @Nullable NullHandling nullHandling,
208 @Nullable String... properties) {
209
210 return isNull(source) ? null : PageRequest.of(
211 source.getPageNumber(),
212 source.getPageSize(),
213 applyDefaults(source.getSort(), direction, ignoreCase, nullHandling, properties));
214 }
215
216
217
218
219
220
221
222
223
224
225
226 @NonNull
227 default Sort applyDefaults(
228 @Nullable Sort source,
229 @Nullable Direction direction,
230 @Nullable Boolean ignoreCase,
231 @Nullable NullHandling nullHandling,
232 @Nullable String... properties) {
233
234 if (isNull(source)) {
235 return Sort.unsorted();
236 }
237 if (isNull(direction) && isNull(ignoreCase) && isNull(nullHandling)) {
238 return source;
239 }
240 Set<String> names;
241 if (isEmpty(properties)) {
242 names = source.stream().map(Sort.Order::getProperty).collect(Collectors.toSet());
243 } else {
244 names = Arrays.stream(properties).collect(Collectors.toSet());
245 }
246 return Sort.by(source.stream()
247 .map(sortOrder -> {
248 if (names.contains(sortOrder.getProperty())) {
249 Sort.Order order = Sort.Order.by(sortOrder.getProperty())
250 .with(newDirection(sortOrder.getDirection(), direction))
251 .with(newNullHandling(sortOrder.getNullHandling(), nullHandling));
252 return withNewCaseHandling(order, sortOrder.isIgnoreCase(), ignoreCase);
253 }
254 return sortOrder;
255 })
256 .toList());
257 }
258
259 private Direction newDirection(Direction oldDirection, Direction newDirection) {
260 return Optional.ofNullable(newDirection)
261 .orElse(oldDirection);
262 }
263
264 private NullHandling newNullHandling(NullHandling oldNullHandling, NullHandling newNullHandling) {
265 return Optional.ofNullable(newNullHandling)
266 .orElse(oldNullHandling);
267 }
268
269 private Sort.Order withNewCaseHandling(
270 Sort.Order order,
271 boolean oldIgnoresCase,
272 Boolean newIgnoresCase) {
273
274 return Optional.ofNullable(newIgnoresCase)
275 .map(ignoreCase -> ignoreCase ? order.ignoreCase() : order)
276 .orElseGet(() -> oldIgnoresCase ? order.ignoreCase() : order);
277 }
278
279
280
281
282 @SuppressWarnings("ClassCanBeRecord")
283 class DefaultSortMapper implements SortMapper {
284
285 private final SortOrderConverter converter;
286
287
288
289
290
291
292 DefaultSortMapper(SortOrderConverter converter) {
293 this.converter = Objects.requireNonNullElseGet(converter, SortOrderConverter::new);
294 }
295
296 @Override
297 public SortOrder getSortOrder(@Nullable String sortOrderText) {
298 if (isNull(sortOrderText)) {
299 return SortOrder.unsorted();
300 }
301 return converter.convert(sortOrderText);
302 }
303
304 @Override
305 public String getSortOrderText(@Nullable SortOrder sortOrder) {
306 if (isNull(sortOrder)) {
307 return null;
308 }
309 if (sortOrder.isEmpty()) {
310 return "";
311 }
312 return sortOrder.getSortOrderText(converter.getSeparators());
313 }
314
315 @Override
316 public List<String> getSortOrderItemText(SortOrder sortOrder) {
317 return Stream.ofNullable(sortOrder)
318 .map(SortOrder::getItems)
319 .filter(Objects::nonNull)
320 .flatMap(Collection::stream)
321 .map(item -> item.getSortOrderText(converter.getSeparators()))
322 .filter(Objects::nonNull)
323 .toList();
324 }
325
326 }
327
328 }