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