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 }