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.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 import org.springframework.data.domain.PageRequest;
38 import org.springframework.data.domain.Pageable;
39 import org.springframework.data.domain.Sort;
40 import org.springframework.data.domain.Sort.Direction;
41 import org.springframework.data.domain.Sort.NullHandling;
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 default Sort.Order toSortOrder(@Nullable SortOrderItem sortOrderItem) {
128 if (sortOrderItem == null || sortOrderItem.getField() == null) {
129 return null;
130 }
131 Direction direction = sortOrderItem.getDirection().isAscending()
132 ? Direction.ASC
133 : Direction.DESC;
134 NullHandling nullHandling = switch (sortOrderItem.getNullHandling()) {
135 case NULLS_FIRST -> NullHandling.NULLS_FIRST;
136 case NULLS_LAST -> NullHandling.NULLS_LAST;
137 case NATIVE -> NullHandling.NATIVE;
138 };
139 Sort.Order order = new Sort.Order(direction, sortOrderItem.getField(), nullHandling);
140 return sortOrderItem.getCaseHandling().isInsensitive()
141 ? order.ignoreCase()
142 : order;
143 }
144
145
146
147
148
149
150
151
152 @NonNull
153 default SortOrder fromSort(@Nullable Sort sort) {
154 List<SortOrderItem> items = Stream.ofNullable(sort)
155 .flatMap(Sort::stream)
156 .map(this::fromSortOrder)
157 .filter(Objects::nonNull)
158 .toList();
159 return new SortOrder(items);
160 }
161
162
163
164
165
166
167
168 @Nullable
169 default SortOrderItem fromSortOrder(Sort.Order sortOrder) {
170 if (isNull(sortOrder)) {
171 return null;
172 }
173 SortOrderItem.Direction direction = sortOrder.getDirection().isAscending()
174 ? SortOrderItem.Direction.ASC
175 : SortOrderItem.Direction.DESC;
176 CaseHandling caseHandling = sortOrder.isIgnoreCase()
177 ? CaseHandling.INSENSITIVE
178 : CaseHandling.SENSITIVE;
179 SortOrderItem.NullHandling nullHandling = switch (sortOrder.getNullHandling()) {
180 case NULLS_FIRST -> SortOrderItem.NullHandling.NULLS_FIRST;
181 case NULLS_LAST -> SortOrderItem.NullHandling.NULLS_LAST;
182 case NATIVE -> SortOrderItem.NullHandling.NATIVE;
183 };
184 return new SortOrderItem(
185 sortOrder.getProperty(),
186 direction,
187 caseHandling,
188 nullHandling);
189 }
190
191
192
193
194
195
196
197
198
199
200
201 @Nullable
202 default Pageable applyDefaults(
203 @Nullable Pageable source,
204 @Nullable Direction direction,
205 @Nullable Boolean ignoreCase,
206 @Nullable NullHandling nullHandling,
207 @Nullable String... properties) {
208
209 return isNull(source) ? null : PageRequest.of(
210 source.getPageNumber(),
211 source.getPageSize(),
212 applyDefaults(source.getSort(), direction, ignoreCase, nullHandling, properties));
213 }
214
215
216
217
218
219
220
221
222
223
224
225 @NonNull
226 default Sort applyDefaults(
227 @Nullable Sort source,
228 @Nullable Direction direction,
229 @Nullable Boolean ignoreCase,
230 @Nullable NullHandling nullHandling,
231 @Nullable String... properties) {
232
233 if (isNull(source)) {
234 return Sort.unsorted();
235 }
236 if (isNull(direction) && isNull(ignoreCase) && isNull(nullHandling)) {
237 return source;
238 }
239 Set<String> names;
240 if (isEmpty(properties)) {
241 names = source.stream().map(Sort.Order::getProperty).collect(Collectors.toSet());
242 } else {
243 names = Arrays.stream(properties).collect(Collectors.toSet());
244 }
245 return Sort.by(source.stream()
246 .map(sortOrder -> {
247 if (names.contains(sortOrder.getProperty())) {
248 Sort.Order order = Sort.Order.by(sortOrder.getProperty())
249 .with(newDirection(sortOrder.getDirection(), direction))
250 .with(newNullHandling(sortOrder.getNullHandling(), nullHandling));
251 return withNewCaseHandling(order, sortOrder.isIgnoreCase(), ignoreCase);
252 }
253 return sortOrder;
254 })
255 .toList());
256 }
257
258 private Direction newDirection(Direction oldDirection, Direction newDirection) {
259 return Optional.ofNullable(newDirection)
260 .orElse(oldDirection);
261 }
262
263 private NullHandling newNullHandling(NullHandling oldNullHandling, NullHandling newNullHandling) {
264 return Optional.ofNullable(newNullHandling)
265 .orElse(oldNullHandling);
266 }
267
268 private Sort.Order withNewCaseHandling(
269 Sort.Order order,
270 boolean oldIgnoresCase,
271 Boolean newIgnoresCase) {
272
273 return Optional.ofNullable(newIgnoresCase)
274 .map(ignoreCase -> ignoreCase ? order.ignoreCase() : order)
275 .orElseGet(() -> oldIgnoresCase ? order.ignoreCase() : order);
276 }
277
278
279
280
281 @SuppressWarnings("ClassCanBeRecord")
282 class DefaultSortMapper implements SortMapper {
283
284 private final SortOrderConverter converter;
285
286
287
288
289
290
291 DefaultSortMapper(SortOrderConverter converter) {
292 this.converter = Objects.requireNonNullElseGet(converter, SortOrderConverter::new);
293 }
294
295 @Override
296 public SortOrder getSortOrder(@Nullable String sortOrderText) {
297 if (isNull(sortOrderText)) {
298 return SortOrder.unsorted();
299 }
300 return converter.convert(sortOrderText);
301 }
302
303 @Override
304 public String getSortOrderText(@Nullable SortOrder sortOrder) {
305 if (isNull(sortOrder)) {
306 return null;
307 }
308 if (sortOrder.isEmpty()) {
309 return "";
310 }
311 return sortOrder.getSortOrderText(converter.getSeparators());
312 }
313
314 @Override
315 public List<String> getSortOrderItemText(SortOrder sortOrder) {
316 return Stream.ofNullable(sortOrder)
317 .map(SortOrder::getItems)
318 .filter(Objects::nonNull)
319 .flatMap(Collection::stream)
320 .map(item -> item.getSortOrderText(converter.getSeparators()))
321 .filter(Objects::nonNull)
322 .toList();
323 }
324
325 }
326
327 }