Skip to content

Commit

Permalink
Add PageCollector
Browse files Browse the repository at this point in the history
  • Loading branch information
zorglube committed Oct 25, 2021
1 parent 69397a1 commit 816b6e8
Show file tree
Hide file tree
Showing 5 changed files with 615 additions and 0 deletions.
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

0 comments on commit 816b6e8

Please sign in to comment.