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 * Concurrent modifications are not directly supported, and for most
035 * collection implementations will throw a
036 * ConcurrentModificationException.
037 *
038 * @param <E> the type of elements returned by this iterator.
039 * @since 3.2
040 */
041public class LoopingListIterator<E> implements ResettableListIterator<E> {
042
043    /** The list to base the iterator on */
044    private final List<E> list;
045    /** The current list iterator */
046    private ListIterator<E> iterator;
047
048    /**
049     * Constructor that wraps a list.
050     * <p>
051     * There is no way to reset a ListIterator instance without
052     * recreating it from the original source, so the List must be
053     * passed in and a reference to it held.
054     *
055     * @param list the list to wrap
056     * @throws NullPointerException if the list is null
057     */
058    public LoopingListIterator(final List<E> list) {
059        this.list = Objects.requireNonNull(list, "collection");
060        _reset();
061    }
062
063    private void _reset() {
064        iterator = list.listIterator();
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     * This feature is only supported if the underlying list's
075     * {@link List#listIterator} method returns an implementation
076     * that supports it.
077     *
078     * @param obj  the element to insert
079     * @throws UnsupportedOperationException if the add method is not
080     *  supported by the iterator implementation of the underlying list
081     */
082    @Override
083    public void add(final E obj) {
084        iterator.add(obj);
085    }
086
087    /**
088     * Returns whether this iterator has any more elements.
089     * <p>
090     * Returns false only if the list originally had zero elements, or
091     * all elements have been {@link #remove removed}.
092     *
093     * @return {@code true} if there are more elements
094     */
095    @Override
096    public boolean hasNext() {
097        return !list.isEmpty();
098    }
099
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}