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