View Javadoc
1   /*
2    * Copyright 2022-2026 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    *      http://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  
17  package org.bremersee.pagebuilder;
18  
19  import static java.util.Objects.requireNonNullElse;
20  
21  import java.util.Arrays;
22  import java.util.Comparator;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Spliterator;
27  import java.util.Spliterators;
28  import java.util.function.Function;
29  import java.util.function.Predicate;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  import java.util.stream.StreamSupport;
33  import org.bremersee.comparator.ComparatorBuilder;
34  import org.bremersee.comparator.ValueComparator;
35  import org.bremersee.comparator.model.SortOrder;
36  import org.bremersee.comparator.model.SortOrderItem;
37  import org.bremersee.comparator.spring.mapper.SortMapper;
38  import org.springframework.data.domain.Page;
39  import org.springframework.data.domain.PageImpl;
40  import org.springframework.data.domain.PageRequest;
41  import org.springframework.data.domain.Pageable;
42  import org.springframework.data.domain.Sort;
43  import org.springframework.util.ObjectUtils;
44  
45  /**
46   * The page builder.
47   *
48   * @param <S> the source type
49   * @param <T> the target type
50   * @author Christian Bremer
51   */
52  public class PageBuilder<S, T> {
53  
54    private Stream<? extends S> sourceEntries;
55  
56    private Predicate<S> sourceFilter;
57  
58    private Function<SortOrderItem, Comparator<?>> sourceSortFn;
59  
60    private Integer pageNumber;
61  
62    private Integer pageSize;
63  
64    private List<SortOrderItem> sort;
65  
66    private SortTarget sortTarget;
67  
68    private Function<S, T> converter;
69  
70    private Predicate<T> targetFilter;
71  
72    private Function<SortOrderItem, Comparator<?>> targetSortFn;
73  
74    /**
75     * Instantiates a new page builder.
76     */
77    public PageBuilder() {
78      sourceFilter = sourceEntry -> true;
79      //noinspection unchecked
80      sourceSortFn = cf -> (Comparator<S>) new ValueComparator(cf);
81      //noinspection unchecked
82      converter = e -> (T) e;
83      targetFilter = targetEntry -> true;
84      //noinspection unchecked
85      targetSortFn = cf -> (Comparator<T>) new ValueComparator(cf);
86    }
87  
88    /**
89     * Sets source entries to the page builder.
90     *
91     * @param entries the entries
92     * @return the page builder
93     */
94    public PageBuilder<S, T> sourceEntries(Stream<? extends S> entries) {
95      if (!Objects.isNull(entries)) {
96        this.sourceEntries = entries;
97      }
98      return this;
99    }
100 
101   /**
102    * Sets source entries to the page builder.
103    *
104    * @param entries the entries
105    * @return the page builder
106    */
107   public PageBuilder<S, T> sourceEntries(Iterable<? extends S> entries) {
108     if (!Objects.isNull(entries)) {
109       this.sourceEntries = StreamSupport.stream(entries.spliterator(), false);
110     }
111     return this;
112   }
113 
114   /**
115    * Sets source entries to the page builder.
116    *
117    * @param entries the entries
118    * @return the page builder
119    */
120   public PageBuilder<S, T> sourceEntries(Iterator<? extends S> entries) {
121     if (!Objects.isNull(entries)) {
122       this.sourceEntries = StreamSupport.stream(Spliterators
123           .spliteratorUnknownSize(entries, Spliterator.ORDERED), false);
124     }
125     return this;
126   }
127 
128   /**
129    * Sets source filter to the page builder.
130    *
131    * @param sourceFilter the source filter
132    * @return the page builder
133    */
134   public PageBuilder<S, T> sourceFilter(Predicate<S> sourceFilter) {
135     if (!Objects.isNull(sourceFilter)) {
136       this.sourceFilter = sourceFilter;
137     }
138     return this;
139   }
140 
141   /**
142    * Sets source sort function to the page builder.
143    *
144    * @param sourceSortFn the source sort function
145    * @return the page builder
146    */
147   public PageBuilder<S, T> sourceSortFn(
148       Function<SortOrderItem, Comparator<?>> sourceSortFn) {
149     if (!Objects.isNull(sourceSortFn)) {
150       this.sourceSortFn = sourceSortFn;
151     }
152     return this;
153   }
154 
155   /**
156    * Sets pageable to the page builder.
157    *
158    * @param pageable the pageable
159    * @return the page builder
160    */
161   public PageBuilder<S, T> pageable(Pageable pageable) {
162     return pageable(pageable, SortTarget.TARGET_ENTRIES);
163   }
164 
165   /**
166    * Sets pageable to the page builder.
167    *
168    * @param pageable the pageable
169    * @param sortTarget the sort target
170    * @return the page builder
171    */
172   public PageBuilder<S, T> pageable(Pageable pageable, SortTarget sortTarget) {
173     if (!Objects.isNull(pageable)) {
174       return pageable(
175           pageable.getPageNumber(),
176           pageable.getPageSize(),
177           sortTarget,
178           SortMapper.defaultSortMapper().fromSort(pageable.getSort()).getItems());
179     }
180     return this;
181   }
182 
183   /**
184    * Sets pageable to the page builder.
185    *
186    * @param pageNumber the page number
187    * @param pageSize the page size
188    * @param sort the sort
189    * @return the page builder
190    */
191   public PageBuilder<S, T> pageable(
192       Integer pageNumber,
193       Integer pageSize,
194       SortOrderItem... sort) {
195     return pageable(
196         pageNumber,
197         pageSize,
198         SortTarget.TARGET_ENTRIES,
199         Objects.isNull(sort) ? null : Arrays.asList(sort));
200   }
201 
202   /**
203    * Sets pageable to the page builder.
204    *
205    * @param pageNumber the page number
206    * @param pageSize the page size
207    * @param sort the sort
208    * @return the page builder
209    */
210   public PageBuilder<S, T> pageable(
211       Integer pageNumber,
212       Integer pageSize,
213       List<SortOrderItem> sort) {
214     return pageable(pageNumber, pageSize, SortTarget.TARGET_ENTRIES, sort);
215   }
216 
217   /**
218    * Sets pageable to the page builder.
219    *
220    * @param pageNumber the page number
221    * @param pageSize the page size
222    * @param sortTarget the sort target
223    * @param sort the sort
224    * @return the page builder
225    */
226   public PageBuilder<S, T> pageable(
227       Integer pageNumber,
228       Integer pageSize,
229       SortTarget sortTarget,
230       SortOrderItem... sort) {
231     return pageable(
232         pageNumber,
233         pageSize,
234         sortTarget,
235         Objects.isNull(sort) ? null : Arrays.asList(sort));
236   }
237 
238   /**
239    * Sets pageable to the page builder.
240    *
241    * @param pageNumber the page number
242    * @param pageSize the page size
243    * @param sortTarget the sort target
244    * @param sort the sort
245    * @return the page builder
246    */
247   public PageBuilder<S, T> pageable(
248       Integer pageNumber,
249       Integer pageSize,
250       SortTarget sortTarget,
251       List<SortOrderItem> sort) {
252 
253     Pageable pageable = PageRequest.of(pageNumber, pageSize);
254     this.pageNumber = pageable.getPageNumber();
255     this.pageSize = pageable.getPageSize();
256     this.sort = sort;
257     if (ObjectUtils.isEmpty(this.sort)) {
258       this.sortTarget = null;
259     } else {
260       this.sortTarget = requireNonNullElse(sortTarget, SortTarget.TARGET_ENTRIES);
261     }
262     return this;
263   }
264 
265   /**
266    * Sets entry converter to the page builder.
267    *
268    * @param converter the entry converter
269    * @return the page builder
270    */
271   public PageBuilder<S, T> converter(Function<S, T> converter) {
272     if (!Objects.isNull(converter)) {
273       this.converter = converter;
274     }
275     return this;
276   }
277 
278   /**
279    * Sets target filter to the page builder.
280    *
281    * @param targetFilter the target filter
282    * @return the page builder
283    */
284   public PageBuilder<S, T> targetFilter(Predicate<T> targetFilter) {
285     if (!Objects.isNull(targetFilter)) {
286       this.targetFilter = targetFilter;
287     }
288     return this;
289   }
290 
291   /**
292    * Sets target sort function to the page builder.
293    *
294    * @param targetSortFn the target sort function
295    * @return the page builder
296    */
297   public PageBuilder<S, T> targetSortFn(
298       Function<SortOrderItem, Comparator<?>> targetSortFn) {
299     if (!Objects.isNull(targetSortFn)) {
300       this.targetSortFn = targetSortFn;
301     }
302     return this;
303   }
304 
305   /**
306    * Builds the page.
307    *
308    * @return the page
309    */
310   public Page<T> build() {
311     //noinspection unchecked
312     final List<S> source = ((Stream<S>) requireNonNullElse(this.sourceEntries, Stream.empty()))
313         .filter(sourceFilter)
314         .collect(Collectors.toList());
315     if (SortTarget.SOURCE_ENTRIES.equals(sortTarget) && !ObjectUtils.isEmpty(sort)) {
316       source.sort(ComparatorBuilder.newInstance()
317           .addAll(sort, sourceSortFn)
318           .build());
319     }
320     final List<T> target = source.stream()
321         .map(converter)
322         .filter(targetFilter)
323         .collect(Collectors.toList());
324     final Sort pageSort;
325     if (SortTarget.TARGET_ENTRIES.equals(sortTarget) && !ObjectUtils.isEmpty(sort)) {
326       target.sort(ComparatorBuilder.newInstance()
327           .addAll(sort, targetSortFn)
328           .build());
329       pageSort = SortMapper.defaultSortMapper().toSort(new SortOrder(sort));
330     } else {
331       pageSort = Sort.unsorted();
332     }
333 
334     int number = requireNonNullElse(pageNumber, 0);
335     int size = requireNonNullElse(pageSize, Integer.MAX_VALUE);
336     final Pageable pageable = PageRequest.of(number, size, pageSort);
337     final List<T> content = target.stream()
338         .skip(pageable.getOffset())
339         .limit(pageable.getPageSize())
340         .toList();
341     return new PageImpl<>(content, pageable, target.size());
342   }
343 
344   /**
345    * The sort target.
346    */
347   public enum SortTarget {
348     /**
349      * Source entries sort target.
350      */
351     SOURCE_ENTRIES,
352     /**
353      * Target entries sort target.
354      */
355     TARGET_ENTRIES
356   }
357 }