-
Notifications
You must be signed in to change notification settings - Fork 678
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
615 additions
and
0 deletions.
There are no files selected for viewing
278 changes: 278 additions & 0 deletions
278
src/main/java/org/springframework/data/util/PageCollectors.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
}; | ||
} | ||
|
||
} | ||
|
||
} |
84 changes: 84 additions & 0 deletions
84
src/test/java/org/springframework/data/util/PageCollectorsToFilteredPageTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | ||
} | ||
|
||
} |
Oops, something went wrong.