1   package com.atlassian.user.search.page;
2   
3   import org.apache.log4j.Category;
4   
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Iterator;
8   import java.util.List;
9   
10  public class MergedListPager<T> implements Pager<T>
11  {
12      private static final Category log = Category.getInstance(MergedListPager.class);
13      private List<? extends Pager<T>> pagers;
14      private int combinedIndex;
15      public Pager<? extends T> currentPager;
16      private List<T> currentPage;
17      private int indexOfFirstItemInCurrentPage;
18      private boolean onLastPage = false;
19  
20      MergedListPager(List<? extends Pager<T>> pagers)
21      {
22          this.pagers = pagers;
23      }
24  
25      public boolean isEmpty()
26      {
27          for (Pager<T> pager : pagers)
28          {
29              if (!pager.isEmpty())
30                  return false;
31          }
32  
33          return true;
34      }
35  
36      /**
37       * @return an iterator over all results. Note: calling this multiple times does not reset the iterator back to the first element, as DefaultPager itself does not do this.
38       */
39      public Iterator<T> iterator()
40      {
41          if (pagers == null)
42          {
43              return Collections.<T>emptyList().iterator();
44          }
45          else
46          {
47              return new MergedListIterator(pagers);
48          }
49      }
50  
51  
52      /**
53       * returns a list of results (up to the limit defined in PRELOAD_LIMIT).<br/>
54       * if there is insufficient preloaded results in the first pager, it will go to the next pager and so on, until the
55       * preload limit is reached or all pagers have been checked.
56       *
57       * Calling this method will move the position of the iterator! (like skipTo() does)
58       *
59       * Note: this will exhaust all the results in a pager before going to the next one. It does <b>not<b/> loop through the preloaded results of each each pager passed in.
60       * Note: the position in which we start pulling elements from in the iterator is set by skipTo()
61       *
62       */
63      public List<T> getCurrentPage()
64      {
65          if (currentPage == null)
66          {
67              currentPage = new ArrayList<T>();
68              indexOfFirstItemInCurrentPage = combinedIndex;
69  
70              for (Iterator<T> iterator = iterator(); iterator.hasNext();)
71              {
72                  if (currentPage.size() < PRELOAD_LIMIT)
73                  {
74                      currentPage.add(iterator.next());
75                      combinedIndex++;
76                  }
77                  else
78                      break;
79              }
80  
81              // could not fill up to preload limit. Must have exhausted pagers.
82              if (currentPage.size() < PRELOAD_LIMIT || !iterator().hasNext())
83                  onLastPage = true;
84          }
85  
86          return currentPage;
87      }
88  
89      public void nextPage()
90      {
91          try
92          {
93              // if current page has been loaded, then we have already increased the value of the pointer by PRELOAD_LIMIT
94              skipTo(combinedIndex + (currentPage == null ? PRELOAD_LIMIT : 0));
95          }
96          catch (PagerException e)
97          {
98              log.error("Erroring calling nextPage()", e);
99          }
100     }
101 
102     public boolean onLastPage()
103     {
104         return onLastPage;
105     }
106 
107     /**
108      * Will run the index up to this point. Calling {@link Pager#getCurrentPage()} will
109      * then return a page holding this index.
110      *
111      * @param index the zero-based index in the result set to skip to
112      * @throws PagerException - if the number of items in the backing data is exceeded by the index.
113      */
114     public void skipTo(int index) throws PagerException
115     {
116         if (index < combinedIndex)
117             throw new PagerException("Cannot run the index back to [" + index + "] from [" + combinedIndex + "]");
118 
119         while (combinedIndex < index)
120         {
121             iterator().next();
122             combinedIndex++;
123         }
124 
125         // reset currentPage and put in next batch of results loaded from position 'index')
126         currentPage = null;
127         getCurrentPage();
128     }
129 
130     /**
131      * @return the current index position of the pager
132      */
133     public int getIndex()
134     {
135         return combinedIndex;
136     }
137 
138     public int getIndexOfFirstItemInCurrentPage()
139     {
140         getCurrentPage(); // this call is necessary to initialise this value
141         return indexOfFirstItemInCurrentPage;
142     }
143 
144     private class MergedListIterator implements Iterator<T>
145     {
146         private List<Iterator<T>> iterators = new ArrayList<Iterator<T>>();
147 
148         public MergedListIterator(List<? extends Pager<T>> listOfPagers)
149         {
150             for (Pager<T> listOfPager : listOfPagers)
151             {
152                 this.iterators.add(listOfPager.iterator());
153             }
154         }
155 
156         private Iterator<T> getCurrentIterator()
157         {
158             for (Iterator<T> iterator : iterators)
159             {
160                 if (iterator.hasNext())
161                     return iterator;
162             }
163 
164             return Collections.<T>emptyList().iterator();
165         }
166 
167 
168         public void remove()
169         {
170             throw new UnsupportedOperationException("This iterator does not support removal");
171         }
172 
173         public boolean hasNext()
174         {
175             return getCurrentIterator().hasNext();
176         }
177 
178         public T next()
179         {
180             T nextElement = getCurrentIterator().next();
181 
182             if (!hasNext())
183                 onLastPage = true;
184 
185             return nextElement;
186         }
187     }
188 
189 }