Skip to content

Commit

Permalink
Add utility class to interact with page sources
Browse files Browse the repository at this point in the history
  • Loading branch information
Yevhenii Voevodin committed Feb 26, 2017
1 parent 54e60a5 commit c26d255
Show file tree
Hide file tree
Showing 2 changed files with 388 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.core;

import org.eclipse.che.api.core.Page.PageRef;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
* Static utility methods to interact with page suppliers.
*
* @author Yevhenii Voevodin
*/
public final class Pages {

/** An experimental value used as default page size where necessary. */
private static final int DEFAULT_PAGE_SIZE = 50;

/**
* Defines an interface for page supplier.
*
* @param <E>
* the type of the element held by page
* @param <X>
* the type of exception thrown by page supplier
*/
@FunctionalInterface
public interface PageSupplier<E, X extends Exception> {

/**
* Gets a single page.
*
* @param maxItems
* max items to retrieve
* @param skipCount
* how many elements to skip
* @return page
* @throws X
* exception thrown by supplier
*/
Page<? extends E> getPage(int maxItems, long skipCount) throws X;
}

/**
* Eagerly fetches all the elements page by page and returns a stream of them.
*
* @param supplier
* page supplier
* @param size
* how many items to retrieve per page
* @param <E>
* the type of the element held by page
* @param <X>
* the type of exception thrown by page supplier
* @return stream of fetched elements
* @throws X
* when supplier throws exception
*/
public static <E, X extends Exception> Stream<E> stream(PageSupplier<E, X> supplier, int size) throws X {
return eagerFetch(supplier, size).stream();
}

/**
* Fetches elements like {@link #stream(PageSupplier, int)} method does
* using default page size which is equal to {@value #DEFAULT_PAGE_SIZE}.
*/
public static <E, X extends Exception> Stream<E> stream(PageSupplier<E, X> supplier) throws X {
return stream(supplier, DEFAULT_PAGE_SIZE);
}

/**
* Eagerly fetches all the elements page by page and returns an iterable of them.
*
* @param supplier
* pages supplier
* @param size
* how many items to retrieve per page
* @param <E>
* the type of the element held by page
* @param <X>
* the type of exception thrown by page supplier
* @return iterable of fetched elements
* @throws X
* when supplier throws exception
*/
public static <E, X extends Exception> Iterable<E> iterate(PageSupplier<E, X> supplier, int size) throws X {
return eagerFetch(supplier, size);
}

/**
* Fetches elements like {@link #iterate(PageSupplier, int)} method does
* using default page size which is equal to {@value #DEFAULT_PAGE_SIZE}.
*/
public static <E, X extends Exception> Iterable<E> iterate(PageSupplier<E, X> supplier) throws X {
return iterate(supplier, DEFAULT_PAGE_SIZE);
}

/**
* Returns a stream which is based on lazy fetching paged iterator.
*
* @param supplier
* pages supplier
* @param size
* how many items to retrieve per page
* @param <E>
* the type of the element held by page
* @param <X>
* exception thrown by supplier
* @return stream of elements
* @throws RuntimeException
* wraps any exception occurred during pages fetch
*/
public static <E, X extends Exception> Stream<E> streamLazily(PageSupplier<E, X> supplier, int size) {
return StreamSupport.stream(new PagedIterable<>(supplier, size).spliterator(), false);
}

/**
* Fetches elements like {@link #streamLazily(PageSupplier, int)} method does
* using default page size which is equal to {@value #DEFAULT_PAGE_SIZE}.
*/
public static <E, X extends Exception> Stream<E> streamLazily(PageSupplier<E, X> supplier) {
return streamLazily(supplier, DEFAULT_PAGE_SIZE);
}

/**
* Returns an iterable which iterator lazily fetches page by page,
* doesn't poll the next page until the last item from previous page is not processed.
* The first page is polled while iterable is created.
*
* @param supplier
* pages supplier
* @param size
* how many items to retrieve per page
* @param <E>
* the type of the element held by page
* @param <X>
* the type of exception thrown by page supplier
* @return stream of elements
* @throws RuntimeException
* wraps any exception occurred during pages fetch
*/
public static <E, X extends Exception> Iterable<E> iterateLazily(PageSupplier<E, X> supplier, int size) {
return new PagedIterable<>(supplier, size);
}

/**
* Returns an iterable like {@link #streamLazily(PageSupplier, int)} method does
* using default page size which is equal to {@value #DEFAULT_PAGE_SIZE}.
*/
public static <E, X extends Exception> Iterable<E> iterateLazily(PageSupplier<E, X> supplier) {
return iterateLazily(supplier, DEFAULT_PAGE_SIZE);
}

private static <E, X extends Exception> List<E> eagerFetch(PageSupplier<E, X> supplier, int size) throws X {
Page<? extends E> page = supplier.getPage(size, 0);
ArrayList<E> container = new ArrayList<>(page.hasNextPage() ? page.getItemsCount() * 2 : page.getItemsCount());
while (page.hasNextPage()) {
container.addAll(page.getItems());
PageRef next = page.getNextPageRef();
page = supplier.getPage(next.getPageSize(), next.getItemsBefore());
}
container.addAll(page.getItems());
return container;
}

private static class PagedIterable<E> implements Iterable<E> {

private final PageSupplier<E, ?> supplier;
private final int size;

private PagedIterable(PageSupplier<E, ?> supplier, int size) {
this.supplier = supplier;
this.size = size;
}

@Override
public Iterator<E> iterator() {
return new PagedIterator<>(supplier, size);
}
}

private static class PagedIterator<E> implements Iterator<E> {

private final PageSupplier<E, ?> supplier;
private final int size;

private Page<? extends E> page;
private Iterator<? extends E> delegate;

private PagedIterator(PageSupplier<E, ?> supplier, int size) {
this.supplier = supplier;
this.size = size;
fetchPage(0);
}

@Override
public boolean hasNext() {
if (delegate.hasNext()) {
return true;
}
if (!page.hasNextPage()) {
return false;
}
fetchPage(page.getNextPageRef().getItemsBefore());
return delegate.hasNext();
}

@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return delegate.next();
}

private void fetchPage(long skip) {
try {
page = supplier.getPage(size, skip);
delegate = page.getItems().iterator();
} catch (Exception x) {
throw new RuntimeException(x.getMessage(), x);
}
}
}

private Pages() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.core;

import com.google.common.collect.Lists;

import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

/**
* Tests {@link Pages}.
*
* @author Yevhenii Voevodin
*/
public class PagesTest {

private TestPagesSupplier testSource;

@BeforeSuite
private void setUp() {
String[] strings = new String[10];
for (int i = 0; i < strings.length; i++) {
strings[i] = "test-string-" + i;
}
testSource = new TestPagesSupplier(strings);
}

@Test
public void eagerlyStreamsAllElements() {
List<String> result = Pages.stream(testSource::getStrings, 2).collect(Collectors.toList());

assertEquals(result, testSource.strings);
}

@Test
public void eagerlyIteratesAllElements() {
ArrayList<String> result = Lists.newArrayList(Pages.iterate(testSource::getStrings, 2));

assertEquals(result, testSource.strings);
}

@Test
public void lazyStreamsAllElements() {
List<String> result = Pages.streamLazily(testSource::getStrings, 2).collect(Collectors.toList());

assertEquals(result, testSource.strings);
}

@Test
public void lazyIteratesAllElements() {
ArrayList<String> result = Lists.newArrayList(Pages.iterateLazily(testSource::getStrings, 2));

assertEquals(result, testSource.strings);
}

@Test
public void lazyStreamingDoesNotPollNextPageUntilNeeded() {
TestPagesSupplier src = spy(new TestPagesSupplier("string1", "string2", "string3"));

assertTrue(Pages.streamLazily(src::getStrings, 1).anyMatch(s -> s.equals("string2")));

verify(src, times(2)).getStrings(anyInt(), anyLong());
verify(src).getStrings(1, 0);
verify(src).getStrings(1, 1);
}

@Test
public void lazyIteratingDoesNotPollNextPageUntilNeeded() {
TestPagesSupplier src = spy(new TestPagesSupplier("string1", "string2", "string3"));

Iterator<String> it = Pages.iterateLazily(src::getStrings, 1).iterator();
it.next();
it.next();

verify(src, times(2)).getStrings(anyInt(), anyLong());
verify(src).getStrings(1, 0);
verify(src).getStrings(1, 1);
}

@Test
public void returnsEmptyStreamWhenFetchingEagerly() {
Stream<String> stream = Pages.stream(new TestPagesSupplier()::getStrings);

assertFalse(stream.findAny().isPresent());
}

@Test
public void returnsIterableWithNoElementsWhileFetchingEagerly() {
Iterator<String> it = Pages.iterate(new TestPagesSupplier()::getStrings).iterator();

assertFalse(it.hasNext());
}

@Test
public void returnsEmptyStreamWhenFetchingLazily() {
Stream<String> stream = Pages.streamLazily(new TestPagesSupplier()::getStrings);

assertFalse(stream.findAny().isPresent());
}

@Test
public void returnsIterableWithNoeElementsWhileFetchingLazily() {
Iterator<String> it = Pages.iterateLazily(new TestPagesSupplier()::getStrings).iterator();

assertFalse(it.hasNext());
}

private static class TestPagesSupplier {

private final List<String> strings;

private TestPagesSupplier(String... strings) {
this.strings = Arrays.asList(strings);
}

public Page<String> getStrings(int max, long skip) {
List<String> items = strings.stream()
.skip(skip)
.limit(max)
.collect(Collectors.toList());
return new Page<>(items, skip, max, strings.size());
}
}
}

0 comments on commit c26d255

Please sign in to comment.