diff --git a/core/src/main/java/org/jclouds/collect/PaginatedSet.java b/core/src/main/java/org/jclouds/collect/PaginatedSet.java new file mode 100644 index 0000000000..fd23d63c4b --- /dev/null +++ b/core/src/main/java/org/jclouds/collect/PaginatedSet.java @@ -0,0 +1,103 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.collect.ForwardingSet; +import com.google.common.collect.ImmutableSet; + +/** + * An {@code Set} that can be continued + * + * @author Adrian Cole + */ +@Beta +public class PaginatedSet extends ForwardingSet { + + public static PaginatedSet copyOf(Iterable contents) { + return new PaginatedSet(contents, null); + } + + public static PaginatedSet copyOfWithMarker(Iterable contents, String marker) { + return new PaginatedSet(contents, marker); + } + + private final Set contents; + private final String marker; + + protected PaginatedSet(Iterable contents, @Nullable String marker) { + this.contents = ImmutableSet. copyOf(checkNotNull(contents, "contents")); + this.marker = marker; + } + + /** + * If there is a next marker, then the set is incomplete and you should issue another command to + * retrieve the rest, setting the option {@code marker} to this value + * + * @return next marker, or null if list is complete + */ + @Nullable + public String getNextMarker() { + return marker; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(contents, marker); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PaginatedSet other = PaginatedSet.class.cast(obj); + return Objects.equal(this.contents, other.contents) && Objects.equal(this.marker, other.marker); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("contents", contents).add("marker", marker).toString(); + } + + @Override + protected Set delegate() { + return contents; + } + +} diff --git a/core/src/main/java/org/jclouds/collect/PaginatedSets.java b/core/src/main/java/org/jclouds/collect/PaginatedSets.java new file mode 100644 index 0000000000..293b1a8886 --- /dev/null +++ b/core/src/main/java/org/jclouds/collect/PaginatedSets.java @@ -0,0 +1,86 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.collect; + +import java.util.Iterator; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.collect.AbstractIterator; + +/** + * Utilities for using {@link PaginatedSet}s. + * + * @author Adrian Cole, Jeremy Whitlock + */ +@Beta +public class PaginatedSets { + + /** + * + * @param initial + * the initial set of data + * @param markerToNext + * produces the next set based on the marker + * + * @return iterable of users fitting the criteria + */ + public static Iterable lazyContinue(final PaginatedSet initial, + final Function> markerToNext) { + if (initial.getNextMarker() == null) + return initial; + return new Iterable() { + @Override + public Iterator iterator() { + return new AbstractIterator() { + + private PaginatedSet response = initial; + private Iterator iterator = response.iterator(); + + /** + * {@inheritDoc} + */ + @Override + protected T computeNext() { + while (true) { + if (iterator == null) { + response = markerToNext.apply(response.getNextMarker()); + iterator = response.iterator(); + } + if (iterator.hasNext()) { + return iterator.next(); + } + if (response.getNextMarker() == null) { + return endOfData(); + } + iterator = null; + } + } + + }; + } + + @Override + public String toString() { + return "lazyContinue(" + markerToNext.toString() + ")"; + } + }; + } + +} diff --git a/core/src/test/java/org/jclouds/collect/PaginatedSetsTest.java b/core/src/test/java/org/jclouds/collect/PaginatedSetsTest.java new file mode 100644 index 0000000000..e260c1f2c2 --- /dev/null +++ b/core/src/test/java/org/jclouds/collect/PaginatedSetsTest.java @@ -0,0 +1,76 @@ +package org.jclouds.collect; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; + +import org.easymock.EasyMock; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; + +/** + * Tests behavior of {@code PaginatedSets}. + * + * @author Adrian Cole + */ +@Test(testName = "PaginatedSetsTest") +public class PaginatedSetsTest { + + @SuppressWarnings("unchecked") + @Test + public void testSinglePageResultReturnsSame() { + + PaginatedSet initial = PaginatedSet.copyOf(ImmutableSet.of("foo", "bar")); + Function> markerToNext = createMock(Function.class); + + EasyMock.replay(markerToNext); + + Assert.assertSame(PaginatedSets.lazyContinue(initial, markerToNext), initial); + + EasyMock.verify(markerToNext); + } + + @SuppressWarnings("unchecked") + @Test + public void testMultiPage2Pages() { + + PaginatedSet initial = PaginatedSet.copyOfWithMarker(ImmutableSet.of("foo", "bar"), "MARKER1"); + Function> markerToNext = createMock(Function.class); + + expect(markerToNext.apply("MARKER1")).andReturn( + PaginatedSet.copyOfWithMarker(ImmutableSet.of("boo", "baz"), null)); + + EasyMock.replay(markerToNext); + + Assert.assertEquals(ImmutableSet.copyOf(PaginatedSets.lazyContinue(initial, markerToNext)), ImmutableSet.of( + "foo", "bar", "boo", "baz")); + + EasyMock.verify(markerToNext); + + } + + @SuppressWarnings("unchecked") + @Test + public void testMultiPage3Pages() { + + PaginatedSet initial = PaginatedSet.copyOfWithMarker(ImmutableSet.of("foo", "bar"), "MARKER1"); + Function> markerToNext = createMock(Function.class); + + expect(markerToNext.apply("MARKER1")).andReturn( + PaginatedSet.copyOfWithMarker(ImmutableSet.of("boo", "baz"), "MARKER2")); + + expect(markerToNext.apply("MARKER2")).andReturn( + PaginatedSet.copyOfWithMarker(ImmutableSet.of("ham", "cheeze"), null)); + + EasyMock.replay(markerToNext); + + Assert.assertEquals(ImmutableSet.copyOf(PaginatedSets.lazyContinue(initial, markerToNext)), ImmutableSet.of( + "foo", "bar", "boo", "baz", "ham", "cheeze")); + + EasyMock.verify(markerToNext); + + } + +}