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 23 import org.apache.commons.collections4.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 */ 39 public class LoopingListIterator<E> implements ResettableListIterator<E> { 40 41 /** The list to base the iterator on */ 42 private final List<E> list; 43 /** The current list iterator */ 44 private ListIterator<E> iterator; 45 46 /** 47 * Constructor that wraps a list. 48 * <p> 49 * There is no way to reset a ListIterator instance without 50 * recreating it from the original source, so the List must be 51 * passed in and a reference to it held. 52 * 53 * @param list the list to wrap 54 * @throws NullPointerException if the list it null 55 */ 56 public LoopingListIterator(final List<E> list) { 57 if (list == null) { 58 throw new NullPointerException("The list must not be null"); 59 } 60 this.list = list; 61 _reset(); 62 } 63 64 /** 65 * Returns whether this iterator has any more elements. 66 * <p> 67 * Returns false only if the list originally had zero elements, or 68 * all elements have been {@link #remove removed}. 69 * 70 * @return <code>true</code> if there are more elements 71 */ 72 @Override 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 @Override 86 public E next() { 87 if (list.isEmpty()) { 88 throw new NoSuchElementException( 89 "There are no elements for this iterator to loop on"); 90 } 91 if (iterator.hasNext() == false) { 92 reset(); 93 } 94 return iterator.next(); 95 } 96 97 /** 98 * Returns the index of the element that would be returned by a 99 * subsequent call to {@link #next}. 100 * <p> 101 * As would be expected, if the iterator is at the physical end of 102 * the underlying list, 0 is returned, signifying the beginning of 103 * the list. 104 * 105 * @return the index of the element that would be returned if next() were called 106 * @throws NoSuchElementException if there are no elements in the list 107 */ 108 @Override 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 } 117 return iterator.nextIndex(); 118 } 119 120 /** 121 * Returns whether this iterator has any more previous elements. 122 * <p> 123 * Returns false only if the list originally had zero elements, or 124 * all elements have been {@link #remove removed}. 125 * 126 * @return <code>true</code> if there are more elements 127 */ 128 @Override 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 @Override 143 public E previous() { 144 if (list.isEmpty()) { 145 throw new NoSuchElementException( 146 "There are no elements for this iterator to loop on"); 147 } 148 if (iterator.hasPrevious() == false) { 149 E result = null; 150 while (iterator.hasNext()) { 151 result = iterator.next(); 152 } 153 iterator.previous(); 154 return result; 155 } 156 return iterator.previous(); 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 @Override 171 public int previousIndex() { 172 if (list.isEmpty()) { 173 throw new NoSuchElementException( 174 "There are no elements for this iterator to loop on"); 175 } 176 if (iterator.hasPrevious() == false) { 177 return list.size() - 1; 178 } 179 return iterator.previousIndex(); 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 @Override 201 public void remove() { 202 iterator.remove(); 203 } 204 205 /** 206 * Inserts the specified element into the underlying list. 207 * <p> 208 * The element is inserted before the next element that would be 209 * returned by {@link #next}, if any, and after the next element 210 * that would be returned by {@link #previous}, if any. 211 * <p> 212 * This feature is only supported if the underlying list's 213 * {@link List#listIterator} method returns an implementation 214 * that supports it. 215 * 216 * @param obj the element to insert 217 * @throws UnsupportedOperationException if the add method is not 218 * supported by the iterator implementation of the underlying list 219 */ 220 @Override 221 public void add(final E obj) { 222 iterator.add(obj); 223 } 224 225 /** 226 * Replaces the last element that was returned by {@link #next} or 227 * {@link #previous}. 228 * <p> 229 * This feature is only supported if the underlying list's 230 * {@link List#listIterator} method returns an implementation 231 * that supports it. 232 * 233 * @param obj the element with which to replace the last element returned 234 * @throws UnsupportedOperationException if the set method is not 235 * supported by the iterator implementation of the underlying list 236 */ 237 @Override 238 public void set(final E obj) { 239 iterator.set(obj); 240 } 241 242 /** 243 * Resets the iterator back to the start of the list. 244 */ 245 @Override 246 public void reset() { 247 _reset(); 248 } 249 250 private void _reset() { 251 iterator = list.listIterator(); 252 } 253 254 /** 255 * Gets the size of the list underlying the iterator. 256 * 257 * @return the current list size 258 */ 259 public int size() { 260 return list.size(); 261 } 262 263 }