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