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