diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/Page.java b/gcloud-java-core/src/main/java/com/google/gcloud/Page.java index 1b7754562716..2819b56a17a0 100644 --- a/gcloud-java-core/src/main/java/com/google/gcloud/Page.java +++ b/gcloud-java-core/src/main/java/com/google/gcloud/Page.java @@ -16,11 +16,22 @@ package com.google.gcloud; +import java.util.Iterator; + /** * Interface for Google Cloud paginated results. * *

- * A typical {@code Page} usage: + * Use {@code Page} to iterate through all values (also in next pages): + *

 {@code
+ * Page page = ...; // get a Page instance
+ * Iterator iterator = page.iterateAll();
+ * while (iterator.hasNext()) {
+ *   T value = iterator.next();
+ *   // do something with value
+ * }}
+ *

+ * Or handle pagination explicitly: *

 {@code
  * Page page = ...; // get a Page instance
  * while (page != null) {
@@ -28,8 +39,7 @@
  *     // do something with value
  *   }
  *   page = page.nextPage();
- * }
- * }
+ * }} */ public interface Page { @@ -38,6 +48,12 @@ public interface Page { */ Iterable values(); + /** + * Returns an iterator for all values, possibly also in the next pages. Once current page's values + * are traversed the iterator fetches next page, if any. + */ + Iterator iterateAll(); + /** * Returns the cursor for the nextPage or {@code null} if no more results. */ diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/PageImpl.java b/gcloud-java-core/src/main/java/com/google/gcloud/PageImpl.java index 3925079c8d4b..a3fcb26b30cd 100644 --- a/gcloud-java-core/src/main/java/com/google/gcloud/PageImpl.java +++ b/gcloud-java-core/src/main/java/com/google/gcloud/PageImpl.java @@ -18,6 +18,8 @@ import java.io.Serializable; import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Objects; /** @@ -35,6 +37,50 @@ public interface NextPageFetcher extends Serializable { Page nextPage(); } + static class PageIterator implements java.util.Iterator { + + private Iterator currentPageIterator; + private Page currentPage; + + PageIterator(Page currentPage) { + this.currentPageIterator = currentPage.values().iterator(); + this.currentPage = currentPage; + } + + @Override + public boolean hasNext() { + if (currentPageIterator.hasNext()) { + return true; + } + Page nextPage = currentPage.nextPage(); + if (nextPage != null) { + currentPage = nextPage; + currentPageIterator = currentPage.values().iterator(); + return currentPageIterator.hasNext(); + } + return false; + } + + @Override + public T next() { + if (currentPageIterator.hasNext()) { + return currentPageIterator.next(); + } + Page nextPage = currentPage.nextPage(); + if (nextPage != null) { + currentPage = nextPage; + currentPageIterator = currentPage.values().iterator(); + return currentPageIterator.next(); + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Can not call remove on page iterator"); + } + } + /** * Creates a {@code PageImpl} object. In order for the object to be serializable the {@code * results} parameter must be serializable. @@ -50,6 +96,11 @@ public Iterable values() { return results == null ? Collections.EMPTY_LIST : results; } + @Override + public Iterator iterateAll() { + return new PageIterator(this); + } + @Override public String nextPageCursor() { return cursor; diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/PageImplTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/PageImplTest.java index 78aa3feaa281..fb289186de8d 100644 --- a/gcloud-java-core/src/test/java/com/google/gcloud/PageImplTest.java +++ b/gcloud-java-core/src/test/java/com/google/gcloud/PageImplTest.java @@ -26,21 +26,38 @@ public class PageImplTest { + private static final ImmutableList VALUES = ImmutableList.of("1", "2"); + private static final ImmutableList NEXT_VALUES = ImmutableList.of("3", "4"); + private static final ImmutableList ALL_VALUES = ImmutableList.builder() + .addAll(VALUES) + .addAll(NEXT_VALUES) + .build(); + @Test - public void testPage() throws Exception { - ImmutableList values = ImmutableList.of("1", "2"); - final PageImpl nextResult = - new PageImpl<>(null, "c", Collections.emptyList()); + public void testPage() { + final PageImpl nextResult = new PageImpl<>(null, "c", NEXT_VALUES); PageImpl.NextPageFetcher fetcher = new PageImpl.NextPageFetcher() { - @Override public PageImpl nextPage() { return nextResult; } }; - PageImpl result = new PageImpl<>(fetcher, "c", values); + PageImpl result = new PageImpl<>(fetcher, "c", VALUES); assertEquals(nextResult, result.nextPage()); assertEquals("c", result.nextPageCursor()); - assertEquals(values, ImmutableList.copyOf(result.values().iterator())); + assertEquals(VALUES, result.values()); + } + + @Test + public void testIterateAll() { + final PageImpl nextResult = new PageImpl<>(null, "c", NEXT_VALUES); + PageImpl.NextPageFetcher fetcher = new PageImpl.NextPageFetcher() { + @Override + public PageImpl nextPage() { + return nextResult; + } + }; + PageImpl result = new PageImpl<>(fetcher, "c", VALUES); + assertEquals(ALL_VALUES, ImmutableList.copyOf(result.iterateAll())); } } diff --git a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java index 7cf7fe2454fc..b7a36de7589e 100644 --- a/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java +++ b/gcloud-java-examples/src/main/java/com/google/gcloud/examples/StorageExample.java @@ -53,6 +53,7 @@ import java.security.cert.CertificateException; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -214,12 +215,9 @@ String parse(String... args) { public void run(Storage storage, String bucketName) { if (bucketName == null) { // list buckets - Page bucketPage = storage.list(); - while (bucketPage != null) { - for (BucketInfo b : bucketPage.values()) { - System.out.println(b); - } - bucketPage = bucketPage.nextPage(); + Iterator bucketInfoIterator = storage.list().iterateAll(); + while (bucketInfoIterator.hasNext()) { + System.out.println(bucketInfoIterator.next()); } } else { // list a bucket's blobs @@ -228,12 +226,9 @@ public void run(Storage storage, String bucketName) { System.out.println("No such bucket"); return; } - Page blobPage = bucket.list(); - while (blobPage != null) { - for (Blob b : blobPage.values()) { - System.out.println(b.info()); - } - blobPage = blobPage.nextPage(); + Iterator blobIterator = bucket.list().iterateAll(); + while (blobIterator.hasNext()) { + System.out.println(blobIterator.next().info()); } } } diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java index ff6fd68fd1eb..47d8e8d433d2 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/RemoteGcsHelperTest.java @@ -33,6 +33,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -90,6 +91,11 @@ public Page nextPage() { public Iterable values() { return BLOB_LIST; } + + @Override + public Iterator iterateAll() { + return null; + } }; private static String keyPath = "/does/not/exist/key." + UUID.randomUUID().toString() + ".json";