View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.iterators;
18  
19  import java.util.List;
20  import java.util.ListIterator;
21  import java.util.NoSuchElementException;
22  
23  import org.apache.commons.collections4.ResettableListIterator;
24  
25  /**
26   * A ListIterator that restarts when it reaches the end or when it
27   * reaches the beginning.
28   * <p>
29   * The iterator will loop continuously around the provided list,
30   * unless there are no elements in the collection to begin with, or
31   * all of the elements have been {@link #remove removed}.
32   * <p>
33   * Concurrent modifications are not directly supported, and for most
34   * collection implementations will throw a
35   * ConcurrentModificationException.
36   *
37   * @since 3.2
38   */
39  public class LoopingListIterator<E> implements ResettableListIterator<E> {
40  
41      /** The list to base the iterator on */
42      private final List<E> list;
43      /** The current list iterator */
44      private ListIterator<E> iterator;
45  
46      /**
47       * Constructor that wraps a list.
48       * <p>
49       * There is no way to reset a ListIterator instance without
50       * recreating it from the original source, so the List must be
51       * passed in and a reference to it held.
52       *
53       * @param list the list to wrap
54       * @throws NullPointerException if the list it null
55       */
56      public LoopingListIterator(final List<E> list) {
57          if (list == null) {
58              throw new NullPointerException("The list must not be null");
59          }
60          this.list = list;
61          _reset();
62      }
63  
64      /**
65       * Returns whether this iterator has any more elements.
66       * <p>
67       * Returns false only if the list originally had zero elements, or
68       * all elements have been {@link #remove removed}.
69       *
70       * @return <code>true</code> if there are more elements
71       */
72      @Override
73      public boolean hasNext() {
74          return !list.isEmpty();
75      }
76  
77      /**
78       * Returns the next object in the list.
79       * <p>
80       * If at the end of the list, returns the first element.
81       *
82       * @return the object after the last element returned
83       * @throws NoSuchElementException if there are no elements in the list
84       */
85      @Override
86      public E next() {
87          if (list.isEmpty()) {
88              throw new NoSuchElementException(
89                  "There are no elements for this iterator to loop on");
90          }
91          if (iterator.hasNext() == false) {
92              reset();
93          }
94          return iterator.next();
95      }
96  
97      /**
98       * Returns the index of the element that would be returned by a
99       * subsequent call to {@link #next}.
100      * <p>
101      * As would be expected, if the iterator is at the physical end of
102      * the underlying list, 0 is returned, signifying the beginning of
103      * the list.
104      *
105      * @return the index of the element that would be returned if next() were called
106      * @throws NoSuchElementException if there are no elements in the list
107      */
108     @Override
109     public int nextIndex() {
110         if (list.isEmpty()) {
111             throw new NoSuchElementException(
112                 "There are no elements for this iterator to loop on");
113         }
114         if (iterator.hasNext() == false) {
115             return 0;
116         }
117         return iterator.nextIndex();
118     }
119 
120     /**
121      * Returns whether this iterator has any more previous elements.
122      * <p>
123      * Returns false only if the list originally had zero elements, or
124      * all elements have been {@link #remove removed}.
125      *
126      * @return <code>true</code> if there are more elements
127      */
128     @Override
129     public boolean hasPrevious() {
130         return !list.isEmpty();
131     }
132 
133     /**
134      * Returns the previous object in the list.
135      * <p>
136      * If at the beginning of the list, return the last element. Note
137      * that in this case, traversal to find that element takes linear time.
138      *
139      * @return the object before the last element returned
140      * @throws NoSuchElementException if there are no elements in the list
141      */
142     @Override
143     public E previous() {
144         if (list.isEmpty()) {
145             throw new NoSuchElementException(
146                 "There are no elements for this iterator to loop on");
147         }
148         if (iterator.hasPrevious() == false) {
149             E result = null;
150             while (iterator.hasNext()) {
151                 result = iterator.next();
152             }
153             iterator.previous();
154             return result;
155         }
156         return iterator.previous();
157     }
158 
159     /**
160      * Returns the index of the element that would be returned by a
161      * subsequent call to {@link #previous}.
162      * <p>
163      * As would be expected, if at the iterator is at the physical
164      * beginning of the underlying list, the list's size minus one is
165      * returned, signifying the end of the list.
166      *
167      * @return the index of the element that would be returned if previous() were called
168      * @throws NoSuchElementException if there are no elements in the list
169      */
170     @Override
171     public int previousIndex() {
172         if (list.isEmpty()) {
173             throw new NoSuchElementException(
174                 "There are no elements for this iterator to loop on");
175         }
176         if (iterator.hasPrevious() == false) {
177             return list.size() - 1;
178         }
179         return iterator.previousIndex();
180     }
181 
182     /**
183      * Removes the previously retrieved item from the underlying list.
184      * <p>
185      * This feature is only supported if the underlying list's
186      * {@link List#iterator iterator} method returns an implementation
187      * that supports it.
188      * <p>
189      * This method can only be called after at least one {@link #next}
190      * or {@link #previous} method call. After a removal, the remove
191      * method may not be called again until another {@link #next} or
192      * {@link #previous} has been performed. If the {@link #reset} is
193      * called, then remove may not be called until {@link #next} or
194      * {@link #previous} is called again.
195      *
196      * @throws UnsupportedOperationException if the remove method is
197      * not supported by the iterator implementation of the underlying
198      * list
199      */
200     @Override
201     public void remove() {
202         iterator.remove();
203     }
204 
205     /**
206      * Inserts the specified element into the underlying list.
207      * <p>
208      * The element is inserted before the next element that would be
209      * returned by {@link #next}, if any, and after the next element
210      * that would be returned by {@link #previous}, if any.
211      * <p>
212      * This feature is only supported if the underlying list's
213      * {@link List#listIterator} method returns an implementation
214      * that supports it.
215      *
216      * @param obj  the element to insert
217      * @throws UnsupportedOperationException if the add method is not
218      *  supported by the iterator implementation of the underlying list
219      */
220     @Override
221     public void add(final E obj) {
222         iterator.add(obj);
223     }
224 
225     /**
226      * Replaces the last element that was returned by {@link #next} or
227      * {@link #previous}.
228      * <p>
229      * This feature is only supported if the underlying list's
230      * {@link List#listIterator} method returns an implementation
231      * that supports it.
232      *
233      * @param obj  the element with which to replace the last element returned
234      * @throws UnsupportedOperationException if the set method is not
235      *  supported by the iterator implementation of the underlying list
236      */
237     @Override
238     public void set(final E obj) {
239         iterator.set(obj);
240     }
241 
242     /**
243      * Resets the iterator back to the start of the list.
244      */
245     @Override
246     public void reset() {
247         _reset();
248     }
249 
250     private void _reset() {
251         iterator = list.listIterator();
252     }
253 
254     /**
255      * Gets the size of the list underlying the iterator.
256      *
257      * @return the current list size
258      */
259     public int size() {
260         return list.size();
261     }
262 
263 }