diff --git a/src/main/java/org/springframework/data/util/PageCollectors.java b/src/main/java/org/springframework/data/util/PageCollectors.java new file mode 100644 index 0000000000..7cfc6ffb14 --- /dev/null +++ b/src/main/java/org/springframework/data/util/PageCollectors.java @@ -0,0 +1,350 @@ +package org.springframework.data.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collector.Characteristics; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +/** + * Utility methods to work with {@link Stream} and {@link Page}s.
+ * It has been thought to provide some writing like that:
+ *
+ * + * class DatasProvider {
+ * public Page getDatasPage() {
+ * Collection items = [FILL THE ITEMS];
+ * return items.stream().map(...).filter(...).collector(PageCollectors.toSimplePage());
+ * }
+ * }
+ *

+ * or
+ * + * class DatasProvider {
+ * public Page getDatasPage() {
+ * Page page = repo.[ANY PAGED SEARCH];
+ * return page.stream().map(...).filter(...).collector(PageCollectors.toSimplePage());
+ * }
+ * }
+ *

+ * or
+ * + * class DatasProvider {
+ * public Page getDatasPage(Pageable pageable) {
+ * Collection items = [FILL THE ITEMS];
+ * return items.stream().map(...).filter(...).collector(PageCollectors.toPage(pageable));
+ * }
+ * }
+ *
+ * + * @author Bertrand Moreau + */ +public final class PageCollectors { + + private static final Set characteristics = Collections.emptySet(); + + /** + * Simply put all the {@link Stream} data into a {@link Page}.Use is IF the {@link Page} is already returned + * by the repo BUT processed after + * + * @param p + * @param + * @return a {@link Page} containing all the {@link Stream} data and the {@link Pageable} informations. + */ + public static Collector, Page> toSimplePage(final Pageable p) { + return new SimplePageCollectorImpl<>(p); + } + + /** + * Simply put all the {@link Stream} data into a {@link Page}. + * + * @param + * @return a {@link Page} containing all the {@link Stream} datas + */ + public static Collector, Page> toSimplePage() { + return new SimplePageCollectorImpl<>(); + } + + private static class SimplePageCollectorImpl implements Collector, Page> { + + private Pageable p = null; + + public SimplePageCollectorImpl() {} + + public SimplePageCollectorImpl(final Pageable p) { + this.p = Objects.requireNonNull(p); + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.of(left, right).flatMap(Collection::stream).collect(Collectors.toList()); + } + + @Override + public Function, Page> finisher() { + return t -> p == null ? new PageImpl<>(t) : new PageImpl<>(t, p, t.size()); + } + + } + + /** + * Reduce the {@link Stream} as {@link Page} based on the {@link Pageable} information. + * + * @param + * @param p + * @return a {@link Page} containing a subset of the {@link Stream} + */ + public static Collector, Page> toPage(final Pageable p) { + return new PageCollectorImpl<>(p); + } + + private static class PageCollectorImpl implements Collector, Page> { + + private final Pageable p; + + public PageCollectorImpl(final Pageable p) { + this.p = Objects.requireNonNull(p); + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.of(left, right).flatMap(Collection::stream).collect(Collectors.toList()); + } + + @Override + public Function, Page> finisher() { + return t -> { + final int pageNumber = p.getPageNumber(); + final int pageSize = p.getPageSize(); + final int fromIndex = Math.min(t.size(), pageNumber * pageSize); + final int toIndex = Math.min(t.size(), (pageNumber + 1) * pageSize); + + return new PageImpl<>(t.subList(fromIndex, toIndex), p, t.size()); + }; + } + + } + + /** + * Reduce the {@link Stream} as {@link Page} based on the {@link Pageable} information. + * + * @param + * @param p + * @return a {@link Page} containing a subset of the {@link Stream} sort following the {@link Comparator} + */ + public static Collector, Page> toSortedPage(final Pageable p, final Comparator c) { + return new SortedPageCollectorImpl<>(p, c); + } + + private static class SortedPageCollectorImpl implements Collector, Page> { + + private final Pageable p; + private final Comparator c; + + public SortedPageCollectorImpl(final Pageable p, final Comparator c) { + this.p = Objects.requireNonNull(p); + this.c = Objects.requireNonNull(c); + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.of(left, right).flatMap(Collection::stream).collect(Collectors.toList()); + } + + @Override + public Function, Page> finisher() { + return t -> { + final int pageNumber = p.getPageNumber(); + final int pageSize = p.getPageSize(); + final int fromIndex = Math.min(t.size(), pageNumber * pageSize); + final int toIndex = Math.min(t.size(), (pageNumber + 1) * pageSize); + + final List data = t.subList(fromIndex, toIndex); + data.sort(c); + + return new PageImpl<>(data, p, t.size()); + }; + } + + } + + /** + * Reduce the {@link Stream} as {@link Page} based on the {@link Pageable} information.
+ * The {@link Stream} is filtered before subset of data isolated + * + * @param + * @param p + * @return a {@link Page} containing a subset of the {@link Stream} + */ + public static Collector, Page> toFilteredPage(final Pageable p, final Predicate f) { + return new FilteredPageCollectorImpl<>(p, f); + } + + private static class FilteredPageCollectorImpl implements Collector, Page> { + + private final Pageable p; + private final Predicate f; + + public FilteredPageCollectorImpl(final Pageable p, final Predicate f) { + this.p = Objects.requireNonNull(p); + this.f = Objects.requireNonNull(f); + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.of(left, right).flatMap(Collection::stream).collect(Collectors.toList()); + } + + @Override + public Function, Page> finisher() { + return t -> { + final List data = t.stream().filter(f).collect(Collectors.toList()); + + final int pageNumber = p.getPageNumber(); + final int pageSize = p.getPageSize(); + final int fromIndex = Math.min(data.size(), pageNumber * pageSize); + final int toIndex = Math.min(data.size(), (pageNumber + 1) * pageSize); + + return new PageImpl<>(data.subList(fromIndex, toIndex), p, t.size()); + }; + } + + } + + /** + * Reduce the {@link Stream} as {@link Page} based on the {@link Pageable} information.
+ * The {@link Stream} is filtered then sorted then the subset of data isolated + * + * @param + * @param p + * @return a {@link Page} containing a subset of the {@link Stream} + */ + public static Collector, Page> toFilteredSortedPage(final Pageable p, final Predicate f, + final Comparator c) { + return new FilteredSortedPageCollectorImpl<>(p, f, c); + } + + private static class FilteredSortedPageCollectorImpl implements Collector, Page> { + + private final Pageable p; + private final Predicate f; + private final Comparator c; + + public FilteredSortedPageCollectorImpl(final Pageable p, final Predicate f, final Comparator c) { + this.p = Objects.requireNonNull(p); + this.f = Objects.requireNonNull(f); + this.c = Objects.requireNonNull(c); + } + + @Override + public Set characteristics() { + return characteristics; + } + + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.of(left, right).flatMap(Collection::stream).collect(Collectors.toList()); + } + + @Override + public Function, Page> finisher() { + return t -> { + final List data = t.stream().filter(f).sorted(c).collect(Collectors.toList()); + + final int pageNumber = p.getPageNumber(); + final int pageSize = p.getPageSize(); + final int fromIndex = Math.min(data.size(), pageNumber * pageSize); + final int toIndex = Math.min(data.size(), (pageNumber + 1) * pageSize); + + return new PageImpl<>(data.subList(fromIndex, toIndex), p, t.size()); + }; + } + + } + +} diff --git a/src/test/java/org/springframework/data/util/PageCollectorToSimplePageTest.java b/src/test/java/org/springframework/data/util/PageCollectorToSimplePageTest.java new file mode 100644 index 0000000000..76d0ad6b0a --- /dev/null +++ b/src/test/java/org/springframework/data/util/PageCollectorToSimplePageTest.java @@ -0,0 +1,77 @@ +package org.springframework.data.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Test methods for {@link PageCollectors}. + * + * @author Bertrand Moreau + */ +public class PageCollectorToSimplePageTest { + + private List ints; + private int size; + + @BeforeEach + void init() { + final Random rand = new Random(); + size = rand.nextInt(10000); + ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList()); + } + + @Test + void fullPage() { + final Page page = ints.stream().collect(PageCollectors.toSimplePage()); + + assertEquals(size, page.getSize()); + assertEquals(size, page.getContent().size()); + assertEquals(0, page.getNumber()); + } + + @Test + void fullPageWithPageable() { + final Pageable p = Pageable.ofSize(size).withPage(3); + final Page page = ints.stream().collect(PageCollectors.toSimplePage(p)); + + assertEquals(size, page.getSize()); + assertEquals(size, page.getContent().size()); + assertEquals(3, page.getNumber()); + } + + @Test + void emptyPage() { + final Page page = Collections.emptyList().stream().collect(PageCollectors.toSimplePage()); + + assertEquals(0, page.getSize()); + assertEquals(0, page.getContent().size()); + assertEquals(0, page.getNumber()); + } + + @Test + void checkData() { + final List datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", + "dix"); + + final int size = datas.size(); + final Page page = datas.stream().collect(PageCollectors.toSimplePage()); + + assertEquals(size, page.getSize()); + assertEquals(size, page.getContent().size()); + assertArrayEquals(datas.toArray(), page.getContent().toArray()); + assertEquals(0, page.getNumber()); + } + +} diff --git a/src/test/java/org/springframework/data/util/PageCollectorsToFilteredPageTest.java b/src/test/java/org/springframework/data/util/PageCollectorsToFilteredPageTest.java new file mode 100644 index 0000000000..6b95965a3c --- /dev/null +++ b/src/test/java/org/springframework/data/util/PageCollectorsToFilteredPageTest.java @@ -0,0 +1,89 @@ +package org.springframework.data.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Test methods for {@link PageCollectors}. + * + * @author Bertrand Moreau + */ +public class PageCollectorsToFilteredPageTest { + + private List ints; + private int size; + + @BeforeEach + void init() { + final Random rand = new Random(); + size = rand.nextInt(10000); + ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList()); + } + + @Test + void fullPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = ints.stream().collect(PageCollectors.toFilteredPage(pageable, i -> i > 0)); + + assertEquals(size, page.getSize()); + assertTrue(page.getContent().size() <= size); + for (final Integer element : page.getContent()) { + assertTrue(element > 0); + } + } + + @Test + void emptyPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = Collections. emptyList().stream() + .collect(PageCollectors.toFilteredPage(pageable, i -> i > 0)); + + assertEquals(size, page.getSize()); + assertTrue(page.getContent().isEmpty()); + } + + @Test + void secondPage() { + final Pageable pageable = Pageable.ofSize(size / 4).withPage(2); + final Page page = ints.stream().collect(PageCollectors.toFilteredPage(pageable, i -> i < 0)); + + assertEquals(size / 4, page.getSize()); + assertTrue(page.getContent().size() <= size); + for (final Integer element : page.getContent()) { + assertTrue(element < 0); + } + } + + @Test + void checkData() { + final List datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", + "dix"); + + final int size = datas.size(); + final Pageable pageable = Pageable.ofSize(size / 2).withPage(0); + final Page page = datas.stream().collect(PageCollectors.toFilteredPage(pageable, t -> t.contains("i"))); + + assertEquals(size / 2, page.getSize()); + assertEquals(size / 2, page.getContent().size()); + for (final String string : page.getContent()) { + assertTrue(string.contains("i")); + assertFalse(!string.contains("i")); + } + assertIterableEquals(page.getContent(), Arrays.asList("trois", "cinq", "six", "huit", "dix")); + } + +} diff --git a/src/test/java/org/springframework/data/util/PageCollectorsToFilteredSortedPageTest.java b/src/test/java/org/springframework/data/util/PageCollectorsToFilteredSortedPageTest.java new file mode 100644 index 0000000000..085e61dabe --- /dev/null +++ b/src/test/java/org/springframework/data/util/PageCollectorsToFilteredSortedPageTest.java @@ -0,0 +1,104 @@ +package org.springframework.data.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Test methods for {@link PageCollectors}. + * + * @author Bertrand Moreau + */ +public class PageCollectorsToFilteredSortedPageTest { + + private List ints; + private int size; + + @BeforeEach + void init() { + final Random rand = new Random(); + size = rand.nextInt(10000); + ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList()); + } + + @Test + void fullPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = ints.stream() + .collect(PageCollectors.toFilteredSortedPage(pageable, i -> i > 0, Integer::compare)); + + assertEquals(size, page.getSize()); + assertTrue(page.getContent().size() <= size); + + final List content = page.getContent(); + for (final Integer element : content) { + assertTrue(element > 0); + } + + for (int j = 1; j < content.size(); j++) { + assertTrue(Integer.compare(content.get(j - 1), content.get(j)) < 0); + } + } + + @Test + void emptyPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = Collections. emptyList().stream() + .collect(PageCollectors.toFilteredSortedPage(pageable, i -> i > 0, Integer::compare)); + + assertEquals(size, page.getSize()); + assertTrue(page.getContent().isEmpty()); + } + + @Test + void secondPage() { + final Pageable pageable = Pageable.ofSize(size / 4).withPage(2); + final Page page = ints.stream() + .collect(PageCollectors.toFilteredSortedPage(pageable, i -> i < 0, Integer::compare)); + + assertEquals(size / 4, page.getSize()); + assertTrue(page.getContent().size() <= size); + + final List content = page.getContent(); + for (final Integer element : content) { + assertTrue(element < 0); + } + + for (int j = 1; j < content.size(); j++) { + assertTrue(Integer.compare(content.get(j - 1), content.get(j)) < 0); + } + } + + @Test + void checkData() { + final List datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", + "dix"); + + final int size = datas.size(); + final Pageable pageable = Pageable.ofSize(size / 2).withPage(0); + final Page page = datas.stream() + .collect(PageCollectors.toFilteredSortedPage(pageable, t -> t.contains("i"), String::compareTo)); + + assertEquals(size / 2, page.getSize()); + assertEquals(size / 2, page.getContent().size()); + for (final String string : page.getContent()) { + assertTrue(string.contains("i")); + assertFalse(!string.contains("i")); + } + assertIterableEquals(page.getContent(), Arrays.asList("cinq", "dix", "huit", "six", "trois")); + } + +} diff --git a/src/test/java/org/springframework/data/util/PageCollectorsToPageTest.java b/src/test/java/org/springframework/data/util/PageCollectorsToPageTest.java new file mode 100644 index 0000000000..638e4f9b9e --- /dev/null +++ b/src/test/java/org/springframework/data/util/PageCollectorsToPageTest.java @@ -0,0 +1,77 @@ +package org.springframework.data.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Test methods for {@link PageCollectors}. + * + * @author Bertrand Moreau + */ +public class PageCollectorsToPageTest { + + private List ints; + private int size; + + @BeforeEach + void init() { + final Random rand = new Random(); + size = rand.nextInt(10000); + ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList()); + } + + @Test + void fullPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = ints.stream().collect(PageCollectors.toPage(pageable)); + + assertEquals(size, page.getSize()); + assertEquals(size, page.getContent().size()); + } + + @Test + void emptyPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = Collections.emptyList().stream().collect(PageCollectors.toPage(pageable)); + + assertEquals(size, page.getSize()); + assertEquals(0, page.getContent().size()); + } + + @Test + void secondPage() { + final Pageable pageable = Pageable.ofSize(size / 4).withPage(2); + final Page page = ints.stream().collect(PageCollectors.toPage(pageable)); + + assertEquals(size / 4, page.getSize()); + assertEquals(size / 4, page.getContent().size()); + } + + @Test + void checkData() { + final List datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", + "dix"); + + final int size = datas.size(); + final Pageable pageable = Pageable.ofSize(size / 5).withPage(2); + final Page page = datas.stream().collect(PageCollectors.toPage(pageable)); + + assertEquals(size / 4, page.getSize()); + assertEquals(size / 4, page.getContent().size()); + assertEquals("cinq", page.getContent().get(0)); + assertEquals("six", page.getContent().get(1)); + + } + +} diff --git a/src/test/java/org/springframework/data/util/PageCollectorsToSortedPageTest.java b/src/test/java/org/springframework/data/util/PageCollectorsToSortedPageTest.java new file mode 100644 index 0000000000..49b5f78262 --- /dev/null +++ b/src/test/java/org/springframework/data/util/PageCollectorsToSortedPageTest.java @@ -0,0 +1,87 @@ +package org.springframework.data.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +/** + * Test methods for {@link PageCollectors}. + * + * @author Bertrand Moreau + */ +public class PageCollectorsToSortedPageTest { + + private List ints; + private int size; + + @BeforeEach + void init() { + final Random rand = new Random(); + size = rand.nextInt(10000); + ints = IntStream.range(0, size).mapToObj(i -> rand.nextInt()).collect(Collectors.toList()); + } + + @Test + void fullPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = ints.stream().collect(PageCollectors.toSortedPage(pageable, Integer::compare)); + + assertEquals(size, page.getSize()); + assertEquals(size, page.getContent().size()); + + final List content = page.getContent(); + for (int i = 1; i < size; i++) { + assertTrue(Integer.compare(content.get(i - 1), content.get(i)) < 0); + } + } + + @Test + void emptyPage() { + final Pageable pageable = Pageable.ofSize(size); + final Page page = Collections. emptyList().stream() + .collect(PageCollectors.toSortedPage(pageable, Integer::compare)); + + assertEquals(size, page.getSize()); + assertTrue(page.getContent().isEmpty()); + } + + @Test + void secondPage() { + final Pageable pageable = Pageable.ofSize(size / 4).withPage(2); + final Page page = ints.stream().collect(PageCollectors.toSortedPage(pageable, Integer::compare)); + + assertEquals(size / 4, page.getSize()); + assertEquals(size / 4, page.getContent().size()); + final List content = page.getContent(); + for (int i = 1; i < content.size(); i++) { + assertTrue(Integer.compare(content.get(i - 1), content.get(i)) < 0); + } + } + + @Test + void checkData() { + final List datas = Arrays.asList("un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", + "dix"); + + final int size = datas.size(); + final Pageable pageable = Pageable.ofSize(size / 2).withPage(1); + final Page page = datas.stream().collect(PageCollectors.toSortedPage(pageable, String::compareTo)); + + assertEquals(size / 2, page.getSize()); + assertEquals(size / 2, page.getContent().size()); + assertIterableEquals(page.getContent(), Arrays.asList("dix", "huit", "neuf", "sept", "six")); + } + +}