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 * https://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 */ 017package org.apache.commons.collections4.iterators; 018 019import java.util.List; 020import java.util.ListIterator; 021import java.util.NoSuchElementException; 022import java.util.Objects; 023 024import org.apache.commons.collections4.ResettableListIterator; 025 026/** 027 * A ListIterator that restarts when it reaches the end or when it 028 * reaches the beginning. 029 * <p> 030 * The iterator will loop continuously around the provided list, 031 * unless there are no elements in the collection to begin with, or 032 * all of the elements have been {@link #remove removed}. 033 * </p> 034 * <p> 035 * Concurrent modifications are not directly supported, and for most 036 * collection implementations will throw a 037 * ConcurrentModificationException. 038 * </p> 039 * 040 * @param <E> The type of elements returned by this iterator. 041 * @since 3.2 042 */ 043public class LoopingListIterator<E> implements ResettableListIterator<E> { 044 045 /** The list to base the iterator on */ 046 private final List<E> list; 047 048 /** The current list iterator */ 049 private ListIterator<E> iterator; 050 051 /** 052 * Constructor that wraps a list. 053 * <p> 054 * There is no way to reset a ListIterator instance without 055 * recreating it from the original source, so the List must be 056 * passed in and a reference to it held. 057 * </p> 058 * 059 * @param list The list to wrap 060 * @throws NullPointerException if the list is null 061 */ 062 public LoopingListIterator(final List<E> list) { 063 this.list = Objects.requireNonNull(list, "collection"); 064 init(); 065 } 066 067 /** 068 * Inserts the specified element into the underlying list. 069 * <p> 070 * The element is inserted before the next element that would be 071 * returned by {@link #next}, if any, and after the next element 072 * that would be returned by {@link #previous}, if any. 073 * </p> 074 * <p> 075 * This feature is only supported if the underlying list's 076 * {@link List#listIterator} method returns an implementation 077 * that supports it. 078 * </p> 079 * 080 * @param obj The element to insert 081 * @throws UnsupportedOperationException if the add method is not 082 * supported by the iterator implementation of the underlying list 083 */ 084 @Override 085 public void add(final E obj) { 086 iterator.add(obj); 087 } 088 089 /** 090 * Returns whether this iterator has any more elements. 091 * <p> 092 * Returns false only if the list originally had zero elements, or 093 * all elements have been {@link #remove removed}. 094 * </p> 095 * 096 * @return {@code true} if there are more elements 097 */ 098 @Override 099 public boolean hasNext() { 100 return !list.isEmpty(); 101 } 102 103 /** 104 * Returns whether this iterator has any more previous elements. 105 * <p> 106 * Returns false only if the list originally had zero elements, or 107 * all elements have been {@link #remove removed}. 108 * </p> 109 * 110 * @return {@code true} if there are more elements 111 */ 112 @Override 113 public boolean hasPrevious() { 114 return !list.isEmpty(); 115 } 116 117 private void init() { 118 iterator = list.listIterator(); 119 } 120 121 /** 122 * Returns the next object in the list. 123 * <p> 124 * If at the end of the list, returns the first element. 125 * </p> 126 * 127 * @return The object after the last element returned 128 * @throws NoSuchElementException if there are no elements in the list 129 */ 130 @Override 131 public E next() { 132 if (list.isEmpty()) { 133 throw new NoSuchElementException( 134 "There are no elements for this iterator to loop on"); 135 } 136 if (!iterator.hasNext()) { 137 reset(); 138 } 139 return iterator.next(); 140 } 141 142 /** 143 * Returns the index of the element that would be returned by a 144 * subsequent call to {@link #next}. 145 * <p> 146 * As would be expected, if the iterator is at the physical end of 147 * the underlying list, 0 is returned, signifying the beginning of 148 * the list. 149 * </p> 150 * 151 * @return The index of the element that would be returned if next() were called 152 * @throws NoSuchElementException if there are no elements in the list 153 */ 154 @Override 155 public int nextIndex() { 156 if (list.isEmpty()) { 157 throw new NoSuchElementException( 158 "There are no elements for this iterator to loop on"); 159 } 160 if (!iterator.hasNext()) { 161 return 0; 162 } 163 return iterator.nextIndex(); 164 } 165 166 /** 167 * Returns the previous object in the list. 168 * <p> 169 * If at the beginning of the list, return the last element. Note 170 * that in this case, traversal to find that element takes linear time. 171 * </p> 172 * 173 * @return The object before the last element returned 174 * @throws NoSuchElementException if there are no elements in the list 175 */ 176 @Override 177 public E previous() { 178 if (list.isEmpty()) { 179 throw new NoSuchElementException( 180 "There are no elements for this iterator to loop on"); 181 } 182 if (!iterator.hasPrevious()) { 183 E result = null; 184 while (iterator.hasNext()) { 185 result = iterator.next(); 186 } 187 iterator.previous(); 188 return result; 189 } 190 return iterator.previous(); 191 } 192 193 /** 194 * Returns the index of the element that would be returned by a 195 * subsequent call to {@link #previous}. 196 * <p> 197 * As would be expected, if at the iterator is at the physical 198 * beginning of the underlying list, the list's size minus one is 199 * returned, signifying the end of the list. 200 * </p> 201 * 202 * @return The index of the element that would be returned if previous() were called 203 * @throws NoSuchElementException if there are no elements in the list 204 */ 205 @Override 206 public int previousIndex() { 207 if (list.isEmpty()) { 208 throw new NoSuchElementException( 209 "There are no elements for this iterator to loop on"); 210 } 211 if (!iterator.hasPrevious()) { 212 return list.size() - 1; 213 } 214 return iterator.previousIndex(); 215 } 216 217 /** 218 * Removes the previously retrieved item from the underlying list. 219 * <p> 220 * This feature is only supported if the underlying list's 221 * {@link List#iterator()} method returns an implementation 222 * that supports it. 223 * </p> 224 * <p> 225 * This method can only be called after at least one {@link #next} 226 * or {@link #previous} method call. After a removal, the remove 227 * method may not be called again until another {@link #next} or 228 * {@link #previous} has been performed. If the {@link #reset} is 229 * called, then remove may not be called until {@link #next} or 230 * {@link #previous} is called again. 231 * </p> 232 * 233 * @throws UnsupportedOperationException if the remove method is 234 * not supported by the iterator implementation of the underlying 235 * list 236 */ 237 @Override 238 public void remove() { 239 iterator.remove(); 240 } 241 242 /** 243 * Resets the iterator back to the start of the list. 244 */ 245 @Override 246 public void reset() { 247 init(); 248 } 249 250 /** 251 * Replaces the last element that was returned by {@link #next} or 252 * {@link #previous}. 253 * <p> 254 * This feature is only supported if the underlying list's 255 * {@link List#listIterator} method returns an implementation 256 * that supports it. 257 * </p> 258 * 259 * @param obj The element with which to replace the last element returned 260 * @throws UnsupportedOperationException if the set method is not 261 * supported by the iterator implementation of the underlying list 262 */ 263 @Override 264 public void set(final E obj) { 265 iterator.set(obj); 266 } 267 268 /** 269 * Gets the size of the list underlying the iterator. 270 * 271 * @return The current list size 272 */ 273 public int size() { 274 return list.size(); 275 } 276 277}