diff --git a/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java b/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java new file mode 100644 index 0000000000000..8c42851d4e851 --- /dev/null +++ b/azure-client-runtime/src/main/java/com/microsoft/azure/PagedList.java @@ -0,0 +1,308 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure; + +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import javax.xml.bind.DataBindingException; +import javax.xml.ws.WebServiceException; + +/** + * Defines a list response from a paging operation. The pages are + * lazy initialized when an instance of this class is iterated. + * + * @param the element type. + */ +public abstract class PagedList implements List { + /** The actual items in the list. */ + private List items; + /** Stores the link to get the next page of items. */ + private String nextPageLink; + + /** + * Creates an instance of PagedList from a {@link Page} response. + * + * @param page the {@link Page} object. + */ + public PagedList(Page page) { + items = page.getItems(); + nextPageLink = page.getNextPageLink(); + } + + /** + * Override this method to load the next page of items from a next page link. + * + * @param nextPageLink the link to get the next page of items. + * @return the {@link Page} object storing a page of items and a link to the next page. + * @throws CloudException thrown if an error is raised from Azure. + * @throws IOException thrown if there's any failure in deserialization. + */ + public abstract Page nextPage(String nextPageLink) throws CloudException, IOException; + + /** + * If there are more pages available. + * + * @return true if there are more pages to load. False otherwise. + */ + public boolean hasNextPage() { + return this.nextPageLink != null; + } + + /** + * Loads a page from next page link. + * The exceptions are wrapped into Java Runtime exceptions. + */ + public void loadNextPage() { + try { + Page nextPage = nextPage(this.nextPageLink); + this.nextPageLink = nextPage.getNextPageLink(); + this.items.addAll(nextPage.getItems()); + } catch (CloudException e) { + throw new WebServiceException(e.toString(), e); + } catch (IOException e) { + throw new DataBindingException(e.getMessage(), e); + } + + } + + /** + * Keep loading the next page from the next page link until all items are loaded. + */ + public void loadAll() { + while (hasNextPage()) { + loadNextPage(); + } + } + + /** + * The implementation of {@link ListIterator} for PagedList. + */ + private class ListItr implements ListIterator { + /** The list iterator for the actual list of items. */ + private ListIterator itemsListItr; + + /** + * Creates an instance of the ListIterator. + * + * @param index the position in the list to start. + */ + public ListItr(int index) { + itemsListItr = items.listIterator(index); + } + + @Override + public boolean hasNext() { + return itemsListItr.hasNext() || nextPageLink != null; + } + + @Override + public E next() { + if (!itemsListItr.hasNext()) { + if (!hasNextPage()) { + throw new NoSuchElementException(); + } else { + int size = items.size(); + loadNextPage(); + itemsListItr = items.listIterator(size); + } + } + return itemsListItr.next(); + } + + @Override + public void remove() { + itemsListItr.remove(); + } + + @Override + public boolean hasPrevious() { + return itemsListItr.hasPrevious(); + } + + @Override + public E previous() { + return itemsListItr.previous(); + } + + @Override + public int nextIndex() { + return itemsListItr.nextIndex(); + } + + @Override + public int previousIndex() { + return itemsListItr.previousIndex(); + } + + @Override + public void set(E e) { + itemsListItr.set(e); + } + + @Override + public void add(E e) { + itemsListItr.add(e); + } + } + + @Override + public int size() { + loadAll(); + return items.size(); + } + + @Override + public boolean isEmpty() { + return items.isEmpty() && nextPageLink == null; + } + + @Override + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + @Override + public Iterator iterator() { + return new ListItr(0); + } + + @Override + public Object[] toArray() { + loadAll(); + return items.toArray(); + } + + @Override + public T[] toArray(T[] a) { + loadAll(); + return items.toArray(a); + } + + @Override + public boolean add(E e) { + return items.add(e); + } + + @Override + public boolean remove(Object o) { + return items.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + for (Object e : c) { + if (!contains(e)) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + return items.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return items.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return items.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return items.retainAll(c); + } + + @Override + public void clear() { + items.clear(); + } + + @Override + public E get(int index) { + while (index >= items.size() && hasNextPage()) { + loadNextPage(); + } + return items.get(index); + } + + @Override + public E set(int index, E element) { + return items.set(index, element); + } + + @Override + public void add(int index, E element) { + items.add(index, element); + } + + @Override + public E remove(int index) { + return items.remove(index); + } + + @Override + public int indexOf(Object o) { + int index = 0; + if (o == null) { + for (E item : this) { + if (item == null) { + return index; + } + ++index; + } + } else { + for (E item : this) { + if (item == o) { + return index; + } + ++index; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object o) { + loadAll(); + return items.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return new ListItr(0); + } + + @Override + public ListIterator listIterator(int index) { + while (index >= items.size() && hasNextPage()) { + loadNextPage(); + } + return new ListItr(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + while ((fromIndex >= items.size() + || toIndex >= items.size()) + && hasNextPage()) { + loadNextPage(); + } + return items.subList(fromIndex, toIndex); + } +} diff --git a/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java b/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java new file mode 100644 index 0000000000000..b82c8d0628787 --- /dev/null +++ b/azure-client-runtime/src/test/java/com/microsoft/azure/PagedListTests.java @@ -0,0 +1,127 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PagedListTests { + private PagedList list; + + @Before + public void setupList() { + list = new PagedList(new TestPage(0, 20)) { + @Override + public Page nextPage(String nextPageLink) throws CloudException, IOException { + int pageNum = Integer.parseInt(nextPageLink); + return new TestPage(pageNum, 20); + } + }; + } + + @Test + public void sizeTest() { + Assert.assertEquals(20, list.size()); + } + + @Test + public void getTest() { + Assert.assertEquals(15, (int) list.get(15)); + } + + @Test + public void iterateTest() { + int j = 0; + for (int i : list) { + Assert.assertEquals(i, j++); + } + } + + @Test + public void removeTest() { + Integer i = list.get(10); + list.remove(10); + Assert.assertEquals(19, list.size()); + Assert.assertEquals(19, (int) list.get(18)); + } + + @Test + public void addTest() { + Integer i = list.get(10); + list.add(100); + Assert.assertEquals(21, list.size()); + Assert.assertEquals(100, (int) list.get(11)); + Assert.assertEquals(19, (int) list.get(20)); + } + + @Test + public void containsTest() { + Assert.assertTrue(list.contains(0)); + Assert.assertTrue(list.contains(3)); + Assert.assertTrue(list.contains(19)); + Assert.assertFalse(list.contains(20)); + } + + @Test + public void containsAllTest() { + List subList = new ArrayList<>(); + subList.addAll(Arrays.asList(0, 3, 19)); + Assert.assertTrue(list.containsAll(subList)); + subList.add(20); + Assert.assertFalse(list.containsAll(subList)); + } + + @Test + public void subListTest() { + List subList = list.subList(5, 15); + Assert.assertEquals(10, subList.size()); + Assert.assertTrue(list.containsAll(subList)); + Assert.assertEquals(7, (int) subList.get(2)); + } + + @Test + public void testIndexOf() { + Assert.assertEquals(15, list.indexOf(15)); + } + + @Test + public void testLastIndexOf() { + Assert.assertEquals(15, list.lastIndexOf(15)); + } + + public static class TestPage implements Page { + private int page; + private int max; + + public TestPage(int page, int max) { + this.page = page; + this.max = max; + } + + @Override + public String getNextPageLink() { + if (page + 1 == max) { + return null; + } + return Integer.toString(page + 1); + } + + @Override + public List getItems() { + List items = new ArrayList<>(); + items.add(page); + return items; + } + } +}