001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections.iterators;
018
019 import java.util.List;
020 import java.util.ListIterator;
021 import java.util.NoSuchElementException;
022
023 import org.apache.commons.collections.ResettableListIterator;
024
025 /**
026 * A ListIterator that restarts when it reaches the end or when it
027 * reaches the beginning.
028 * <p>
029 * The iterator will loop continuously around the provided list,
030 * unless there are no elements in the collection to begin with, or
031 * all of the elements have been {@link #remove removed}.
032 * <p>
033 * Concurrent modifications are not directly supported, and for most
034 * collection implementations will throw a
035 * ConcurrentModificationException.
036 *
037 * @since Commons Collections 3.2
038 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
039 *
040 * @author Eric Crampton <ccesc@eonomine.com>
041 */
042 public class LoopingListIterator implements ResettableListIterator {
043
044 /** The list to base the iterator on */
045 private List list;
046 /** The current list iterator */
047 private ListIterator iterator;
048
049 /**
050 * Constructor that wraps a list.
051 * <p>
052 * There is no way to reset a ListIterator instance without
053 * recreating it from the original source, so the List must be
054 * passed in and a reference to it held.
055 *
056 * @param list the list to wrap
057 * @throws NullPointerException if the list it null
058 */
059 public LoopingListIterator(List list) {
060 if (list == null) {
061 throw new NullPointerException("The list must not be null");
062 }
063 this.list = list;
064 reset();
065 }
066
067 /**
068 * Returns whether this iterator has any more elements.
069 * <p>
070 * Returns false only if the list originally had zero elements, or
071 * all elements have been {@link #remove removed}.
072 *
073 * @return <code>true</code> if there are more elements
074 */
075 public boolean hasNext() {
076 return !list.isEmpty();
077 }
078
079 /**
080 * Returns the next object in the list.
081 * <p>
082 * If at the end of the list, returns the first element.
083 *
084 * @return the object after the last element returned
085 * @throws NoSuchElementException if there are no elements in the list
086 */
087 public Object next() {
088 if (list.isEmpty()) {
089 throw new NoSuchElementException(
090 "There are no elements for this iterator to loop on");
091 }
092 if (iterator.hasNext() == false) {
093 reset();
094 }
095 return iterator.next();
096 }
097
098 /**
099 * Returns the index of the element that would be returned by a
100 * subsequent call to {@link #next}.
101 * <p>
102 * As would be expected, if the iterator is at the physical end of
103 * the underlying list, 0 is returned, signifying the beginning of
104 * the list.
105 *
106 * @return the index of the element that would be returned if next() were called
107 * @throws NoSuchElementException if there are no elements in the list
108 */
109 public int nextIndex() {
110 if (list.isEmpty()) {
111 throw new NoSuchElementException(
112 "There are no elements for this iterator to loop on");
113 }
114 if (iterator.hasNext() == false) {
115 return 0;
116 } else {
117 return iterator.nextIndex();
118 }
119 }
120
121 /**
122 * Returns whether this iterator has any more previous elements.
123 * <p>
124 * Returns false only if the list originally had zero elements, or
125 * all elements have been {@link #remove removed}.
126 *
127 * @return <code>true</code> if there are more elements
128 */
129 public boolean hasPrevious() {
130 return !list.isEmpty();
131 }
132
133 /**
134 * Returns the previous object in the list.
135 * <p>
136 * If at the beginning of the list, return the last element. Note
137 * that in this case, traversal to find that element takes linear time.
138 *
139 * @return the object before the last element returned
140 * @throws NoSuchElementException if there are no elements in the list
141 */
142 public Object previous() {
143 if (list.isEmpty()) {
144 throw new NoSuchElementException(
145 "There are no elements for this iterator to loop on");
146 }
147 if (iterator.hasPrevious() == false) {
148 Object result = null;
149 while (iterator.hasNext()) {
150 result = iterator.next();
151 }
152 iterator.previous();
153 return result;
154 } else {
155 return iterator.previous();
156 }
157 }
158
159 /**
160 * Returns the index of the element that would be returned by a
161 * subsequent call to {@link #previous}.
162 * <p>
163 * As would be expected, if at the iterator is at the physical
164 * beginning of the underlying list, the list's size minus one is
165 * returned, signifying the end of the list.
166 *
167 * @return the index of the element that would be returned if previous() were called
168 * @throws NoSuchElementException if there are no elements in the list
169 */
170 public int previousIndex() {
171 if (list.isEmpty()) {
172 throw new NoSuchElementException(
173 "There are no elements for this iterator to loop on");
174 }
175 if (iterator.hasPrevious() == false) {
176 return list.size() - 1;
177 } else {
178 return iterator.previousIndex();
179 }
180 }
181
182 /**
183 * Removes the previously retrieved item from the underlying list.
184 * <p>
185 * This feature is only supported if the underlying list's
186 * {@link List#iterator iterator} method returns an implementation
187 * that supports it.
188 * <p>
189 * This method can only be called after at least one {@link #next}
190 * or {@link #previous} method call. After a removal, the remove
191 * method may not be called again until another {@link #next} or
192 * {@link #previous} has been performed. If the {@link #reset} is
193 * called, then remove may not be called until {@link #next} or
194 * {@link #previous} is called again.
195 *
196 * @throws UnsupportedOperationException if the remove method is
197 * not supported by the iterator implementation of the underlying
198 * list
199 */
200 public void remove() {
201 iterator.remove();
202 }
203
204 /**
205 * Inserts the specified element into the underlying list.
206 * <p>
207 * The element is inserted before the next element that would be
208 * returned by {@link #next}, if any, and after the next element
209 * that would be returned by {@link #previous}, if any.
210 * <p>
211 * This feature is only supported if the underlying list's
212 * {@link List#listIterator} method returns an implementation
213 * that supports it.
214 *
215 * @param obj the element to insert
216 * @throws UnsupportedOperationException if the add method is not
217 * supported by the iterator implementation of the underlying list
218 */
219 public void add(Object obj) {
220 iterator.add(obj);
221 }
222
223 /**
224 * Replaces the last element that was returned by {@link #next} or
225 * {@link #previous}.
226 * <p>
227 * This feature is only supported if the underlying list's
228 * {@link List#listIterator} method returns an implementation
229 * that supports it.
230 *
231 * @param obj the element with which to replace the last element returned
232 * @throws UnsupportedOperationException if the set method is not
233 * supported by the iterator implementation of the underlying list
234 */
235 public void set(Object obj) {
236 iterator.set(obj);
237 }
238
239 /**
240 * Resets the iterator back to the start of the list.
241 */
242 public void reset() {
243 iterator = list.listIterator();
244 }
245
246 /**
247 * Gets the size of the list underlying the iterator.
248 *
249 * @return the current list size
250 */
251 public int size() {
252 return list.size();
253 }
254
255 }