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