Skip to content

Commit 816b6e8

Browse files
committed
Add PageCollector
1 parent 69397a1 commit 816b6e8

File tree

5 files changed

+615
-0
lines changed

5 files changed

+615
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
package org.springframework.data.util;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.Comparator;
6+
import java.util.List;
7+
import java.util.Objects;
8+
import java.util.Set;
9+
import java.util.function.BiConsumer;
10+
import java.util.function.BinaryOperator;
11+
import java.util.function.Function;
12+
import java.util.function.Predicate;
13+
import java.util.function.Supplier;
14+
import java.util.stream.Collector;
15+
import java.util.stream.Collector.Characteristics;
16+
import java.util.stream.Collectors;
17+
import java.util.stream.Stream;
18+
19+
import org.springframework.data.domain.Page;
20+
import org.springframework.data.domain.PageImpl;
21+
import org.springframework.data.domain.Pageable;
22+
23+
public final class PageCollectors<T> {
24+
25+
private static final Set<Characteristics> characteristics = Collections.emptySet();
26+
27+
/**
28+
* Reduce the {@link Stream} as {@link Page} based on the {@link Pageable}
29+
* information.
30+
*
31+
* @param <T>
32+
* @param p
33+
* @return a {@link Page} containing a subset of the {@link Stream}
34+
*/
35+
public static <T> Collector<T, List<T>, Page<T>> toPage(final Pageable p) {
36+
return new PageCollectorImpl<>(p);
37+
}
38+
39+
private static class PageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {
40+
41+
Pageable p;
42+
43+
public PageCollectorImpl(final Pageable p) {
44+
this.p = Objects.requireNonNull(p);
45+
}
46+
47+
@Override
48+
public Set<Characteristics> characteristics() {
49+
return characteristics;
50+
}
51+
52+
@Override
53+
public Supplier<List<T>> supplier() {
54+
return ArrayList::new;
55+
}
56+
57+
@Override
58+
public BiConsumer<List<T>, T> accumulator() {
59+
return List::add;
60+
}
61+
62+
@Override
63+
public BinaryOperator<List<T>> combiner() {
64+
return (left, right) -> {
65+
left.addAll(right);
66+
return left;
67+
};
68+
}
69+
70+
@Override
71+
public Function<List<T>, Page<T>> finisher() {
72+
return t -> {
73+
final int pageNumber = p.getPageNumber();
74+
final int pageSize = p.getPageSize();
75+
final int fromIndex = Math.min(t.size(), pageNumber * pageSize);
76+
final int toIndex = Math.min(t.size(), (pageNumber + 1) * pageSize);
77+
78+
return new PageImpl<>(t.subList(fromIndex, toIndex), p, t.size());
79+
};
80+
}
81+
82+
}
83+
84+
/**
85+
* Reduce the {@link Stream} as {@link Page} based on the {@link Pageable}
86+
* information.
87+
*
88+
* @param <T>
89+
* @param p
90+
* @return a {@link Page} containing a subset of the {@link Stream} sort
91+
* following the {@link Comparator}
92+
*/
93+
public static <T> Collector<T, List<T>, Page<T>> toSortedPage(final Pageable p, final Comparator<T> c) {
94+
return new SortedPageCollectorImpl<>(p, c);
95+
}
96+
97+
private static class SortedPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {
98+
99+
Pageable p;
100+
Comparator<T> c;
101+
102+
public SortedPageCollectorImpl(final Pageable p, final Comparator<T> c) {
103+
this.p = Objects.requireNonNull(p);
104+
this.c = Objects.requireNonNull(c);
105+
}
106+
107+
@Override
108+
public Set<Characteristics> characteristics() {
109+
return characteristics;
110+
}
111+
112+
@Override
113+
public Supplier<List<T>> supplier() {
114+
return ArrayList::new;
115+
}
116+
117+
@Override
118+
public BiConsumer<List<T>, T> accumulator() {
119+
return List::add;
120+
}
121+
122+
@Override
123+
public BinaryOperator<List<T>> combiner() {
124+
return (left, right) -> {
125+
left.addAll(right);
126+
return left;
127+
};
128+
}
129+
130+
@Override
131+
public Function<List<T>, Page<T>> finisher() {
132+
return t -> {
133+
final int pageNumber = p.getPageNumber();
134+
final int pageSize = p.getPageSize();
135+
final int fromIndex = Math.min(t.size(), pageNumber * pageSize);
136+
final int toIndex = Math.min(t.size(), (pageNumber + 1) * pageSize);
137+
138+
final List<T> data = t.subList(fromIndex, toIndex);
139+
data.sort(c);
140+
141+
return new PageImpl<>(data, p, t.size());
142+
};
143+
}
144+
145+
}
146+
147+
/**
148+
* Reduce the {@link Stream} as {@link Page} based on the {@link Pageable}
149+
* information. <br>
150+
*
151+
* <strong>The {@link Stream} is filtered before subset of data
152+
* isolated</strong>
153+
*
154+
* @param <T>
155+
* @param p
156+
* @return a {@link Page} containing a subset of the {@link Stream}
157+
*/
158+
public static <T> Collector<T, List<T>, Page<T>> toFilteredPage(final Pageable p, final Predicate<T> f) {
159+
return new FilteredPageCollectorImpl<>(p, f);
160+
}
161+
162+
private static class FilteredPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {
163+
164+
Pageable p;
165+
Predicate<T> f;
166+
167+
public FilteredPageCollectorImpl(final Pageable p, final Predicate<T> f) {
168+
this.p = Objects.requireNonNull(p);
169+
this.f = Objects.requireNonNull(f);
170+
}
171+
172+
@Override
173+
public Set<Characteristics> characteristics() {
174+
return characteristics;
175+
}
176+
177+
@Override
178+
public Supplier<List<T>> supplier() {
179+
return ArrayList::new;
180+
}
181+
182+
@Override
183+
public BiConsumer<List<T>, T> accumulator() {
184+
return List::add;
185+
}
186+
187+
@Override
188+
public BinaryOperator<List<T>> combiner() {
189+
return (left, right) -> {
190+
left.addAll(right);
191+
return left;
192+
};
193+
}
194+
195+
@Override
196+
public Function<List<T>, Page<T>> finisher() {
197+
return t -> {
198+
final List<T> data = t.stream().filter(f).collect(Collectors.toList());
199+
200+
final int pageNumber = p.getPageNumber();
201+
final int pageSize = p.getPageSize();
202+
final int fromIndex = Math.min(data.size(), pageNumber * pageSize);
203+
final int toIndex = Math.min(data.size(), (pageNumber + 1) * pageSize);
204+
205+
return new PageImpl<>(data.subList(fromIndex, toIndex), p, t.size());
206+
};
207+
}
208+
209+
}
210+
211+
/**
212+
* Reduce the {@link Stream} as {@link Page} based on the {@link Pageable}
213+
* information. <br>
214+
*
215+
* <strong>The {@link Stream} is filtered then sorted then the subset of data
216+
* isolated</strong>
217+
*
218+
* @param <T>
219+
* @param p
220+
* @return a {@link Page} containing a subset of the {@link Stream}
221+
*/
222+
public static <T> Collector<T, List<T>, Page<T>> toFilteredSortedPage(final Pageable p, final Predicate<T> f,
223+
final Comparator<T> c) {
224+
return new FilteredSortedPageCollectorImpl<>(p, f, c);
225+
}
226+
227+
private static class FilteredSortedPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {
228+
229+
Pageable p;
230+
Predicate<T> f;
231+
Comparator<T> c;
232+
233+
public FilteredSortedPageCollectorImpl(final Pageable p, final Predicate<T> f, final Comparator<T> c) {
234+
this.p = Objects.requireNonNull(p);
235+
this.f = Objects.requireNonNull(f);
236+
this.c = Objects.requireNonNull(c);
237+
}
238+
239+
@Override
240+
public Set<Characteristics> characteristics() {
241+
return characteristics;
242+
}
243+
244+
@Override
245+
public Supplier<List<T>> supplier() {
246+
return ArrayList::new;
247+
}
248+
249+
@Override
250+
public BiConsumer<List<T>, T> accumulator() {
251+
return List::add;
252+
}
253+
254+
@Override
255+
public BinaryOperator<List<T>> combiner() {
256+
return (left, right) -> {
257+
left.addAll(right);
258+
return left;
259+
};
260+
}
261+
262+
@Override
263+
public Function<List<T>, Page<T>> finisher() {
264+
return t -> {
265+
final List<T> data = t.stream().filter(f).sorted(c).collect(Collectors.toList());
266+
267+
final int pageNumber = p.getPageNumber();
268+
final int pageSize = p.getPageSize();
269+
final int fromIndex = Math.min(data.size(), pageNumber * pageSize);
270+
final int toIndex = Math.min(data.size(), (pageNumber + 1) * pageSize);
271+
272+
return new PageImpl<>(data.subList(fromIndex, toIndex), p, t.size());
273+
};
274+
}
275+
276+
}
277+
278+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.springframework.data.util;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import java.util.Arrays;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import java.util.Random;
12+
import java.util.stream.Collectors;
13+
import java.util.stream.IntStream;
14+
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
17+
import org.springframework.data.domain.Page;
18+
import org.springframework.data.domain.Pageable;
19+
20+
public class PageCollectorsToFilteredPageTest {
21+
22+
private List<Integer> ints;
23+
private int size;
24+
25+
@BeforeEach
26+
void init() {
27+
final Random rand = new Random();
28+
size = rand.nextInt(10000);
29+
ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList());
30+
}
31+
32+
@Test
33+
void fullPage() {
34+
final Pageable pageable = Pageable.ofSize(size);
35+
final Page<Integer> page = ints.stream().collect(PageCollectors.toFilteredPage(pageable, i -> i > 0));
36+
37+
assertEquals(size, page.getSize());
38+
assertTrue(page.getContent().size() <= size);
39+
for (final Integer element : page.getContent()) {
40+
assertTrue(element > 0);
41+
}
42+
}
43+
44+
@Test
45+
void emptyPage() {
46+
final Pageable pageable = Pageable.ofSize(size);
47+
final Page<Integer> page = Collections.<Integer>emptyList().stream()
48+
.collect(PageCollectors.toFilteredPage(pageable, i -> i > 0));
49+
50+
assertEquals(size, page.getSize());
51+
assertTrue(page.getContent().isEmpty());
52+
}
53+
54+
@Test
55+
void secondPage() {
56+
final Pageable pageable = Pageable.ofSize(size / 4).withPage(2);
57+
final Page<Integer> page = ints.stream().collect(PageCollectors.toFilteredPage(pageable, i -> i < 0));
58+
59+
assertEquals(size / 4, page.getSize());
60+
assertTrue(page.getContent().size() <= size);
61+
for (final Integer element : page.getContent()) {
62+
assertTrue(element < 0);
63+
}
64+
}
65+
66+
@Test
67+
void checkData() {
68+
final List<String> datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf",
69+
"dix");
70+
71+
final int size = datas.size();
72+
final Pageable pageable = Pageable.ofSize(size / 2).withPage(0);
73+
final Page<String> page = datas.stream().collect(PageCollectors.toFilteredPage(pageable, t -> t.contains("i")));
74+
75+
assertEquals(size / 2, page.getSize());
76+
assertEquals(size / 2, page.getContent().size());
77+
for (final String string : page.getContent()) {
78+
assertTrue(string.contains("i"));
79+
assertFalse(!string.contains("i"));
80+
}
81+
assertIterableEquals(page.getContent(), Arrays.asList("trois", "cinq", "six", "huit", "dix"));
82+
}
83+
84+
}

0 commit comments

Comments
 (0)