    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}