Skip to content
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

Add utility class to interact with page sources #4232

Merged
merged 1 commit into from
Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
}
}