Skip to content

Add PageCollectors into Util #2490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
Add PageCollectors
zorglube committed Oct 25, 2021
commit e654ed0c426c7801efa2b8d42930e6ebf0c31d92
278 changes: 278 additions & 0 deletions src/main/java/org/springframework/data/util/PageCollectors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
package org.springframework.data.util;

import java.util.ArrayList;
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;

public final class PageCollectors<T> {

private static final Set<Characteristics> characteristics = Collections.emptySet();

/**
* Reduce the {@link Stream} as {@link Page} based on the {@link Pageable}
* information.
*
* @param <T>
* @param p
* @return a {@link Page} containing a subset of the {@link Stream}
*/
public static <T> Collector<T, List<T>, Page<T>> toPage(final Pageable p) {
return new PageCollectorImpl<>(p);
}

private static class PageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {

Pageable p;

public PageCollectorImpl(final Pageable p) {
this.p = Objects.requireNonNull(p);
}

@Override
public Set<Characteristics> characteristics() {
return characteristics;
}

@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}

@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}

@Override
public BinaryOperator<List<T>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}

@Override
public Function<List<T>, Page<T>> 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 <T>
* @param p
* @return a {@link Page} containing a subset of the {@link Stream} sort
* following the {@link Comparator}
*/
public static <T> Collector<T, List<T>, Page<T>> toSortedPage(final Pageable p, final Comparator<T> c) {
return new SortedPageCollectorImpl<>(p, c);
}

private static class SortedPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {

Pageable p;
Comparator<T> c;

public SortedPageCollectorImpl(final Pageable p, final Comparator<T> c) {
this.p = Objects.requireNonNull(p);
this.c = Objects.requireNonNull(c);
}

@Override
public Set<Characteristics> characteristics() {
return characteristics;
}

@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}

@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}

@Override
public BinaryOperator<List<T>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}

@Override
public Function<List<T>, Page<T>> 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<T> 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. <br>
*
* <strong>The {@link Stream} is filtered before subset of data
* isolated</strong>
*
* @param <T>
* @param p
* @return a {@link Page} containing a subset of the {@link Stream}
*/
public static <T> Collector<T, List<T>, Page<T>> toFilteredPage(final Pageable p, final Predicate<T> f) {
return new FilteredPageCollectorImpl<>(p, f);
}

private static class FilteredPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {

Pageable p;
Predicate<T> f;

public FilteredPageCollectorImpl(final Pageable p, final Predicate<T> f) {
this.p = Objects.requireNonNull(p);
this.f = Objects.requireNonNull(f);
}

@Override
public Set<Characteristics> characteristics() {
return characteristics;
}

@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}

@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}

@Override
public BinaryOperator<List<T>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}

@Override
public Function<List<T>, Page<T>> finisher() {
return t -> {
final List<T> 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. <br>
*
* <strong>The {@link Stream} is filtered then sorted then the subset of data
* isolated</strong>
*
* @param <T>
* @param p
* @return a {@link Page} containing a subset of the {@link Stream}
*/
public static <T> Collector<T, List<T>, Page<T>> toFilteredSortedPage(final Pageable p, final Predicate<T> f,
final Comparator<T> c) {
return new FilteredSortedPageCollectorImpl<>(p, f, c);
}

private static class FilteredSortedPageCollectorImpl<T> implements Collector<T, List<T>, Page<T>> {

Pageable p;
Predicate<T> f;
Comparator<T> c;

public FilteredSortedPageCollectorImpl(final Pageable p, final Predicate<T> f, final Comparator<T> c) {
this.p = Objects.requireNonNull(p);
this.f = Objects.requireNonNull(f);
this.c = Objects.requireNonNull(c);
}

@Override
public Set<Characteristics> characteristics() {
return characteristics;
}

@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}

@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}

@Override
public BinaryOperator<List<T>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}

@Override
public Function<List<T>, Page<T>> finisher() {
return t -> {
final List<T> 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());
};
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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;

public class PageCollectorsToFilteredPageTest {

private List<Integer> 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<Integer> 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<Integer> page = Collections.<Integer>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<Integer> 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<String> 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<String> 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"));
}

}
Loading
Oops, something went wrong.