-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add utility class to interact with page sources
- Loading branch information
Yevhenii Voevodin
committed
Feb 26, 2017
1 parent
54e60a5
commit c26d255
Showing
2 changed files
with
388 additions
and
0 deletions.
There are no files selected for viewing
240 changes: 240 additions & 0 deletions
240
core/che-core-api-core/src/main/java/org/eclipse/che/api/core/Pages.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,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() {} | ||
} |
148 changes: 148 additions & 0 deletions
148
core/che-core-api-core/src/test/java/org/eclipse/che/api/core/PagesTest.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,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()); | ||
} | ||
} | ||
} |