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