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 }