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 */
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    /** The current list iterator */
048    private ListIterator<E> iterator;
049
050    /**
051     * Constructor that wraps a list.
052     * <p>
053     * There is no way to reset a ListIterator instance without
054     * recreating it from the original source, so the List must be
055     * passed in and a reference to it held.
056     * </p>
057     *
058     * @param list the list to wrap
059     * @throws NullPointerException if the list is null
060     */
061    public LoopingListIterator(final List<E> list) {
062        this.list = Objects.requireNonNull(list, "collection");
063        init();
064    }
065
066    /**
067     * Inserts the specified element into the underlying list.
068     * <p>
069     * The element is inserted before the next element that would be
070     * returned by {@link #next}, if any, and after the next element
071     * that would be returned by {@link #previous}, if any.
072     * </p>
073     * <p>
074     * This feature is only supported if the underlying list's
075     * {@link List#listIterator} method returns an implementation
076     * that supports it.
077     * </p>
078     *
079     * @param obj  the element to insert
080     * @throws UnsupportedOperationException if the add method is not
081     *  supported by the iterator implementation of the underlying list
082     */
083    @Override
084    public void add(final E obj) {
085        iterator.add(obj);
086    }
087
088    /**
089     * Returns whether this iterator has any more elements.
090     * <p>
091     * Returns false only if the list originally had zero elements, or
092     * all elements have been {@link #remove removed}.
093     * </p>
094     *
095     * @return {@code true} if there are more elements
096     */
097    @Override
098    public boolean hasNext() {
099        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}