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 }