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.text.MessageFormat;
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023import java.util.ListIterator;
024import java.util.NoSuchElementException;
025import java.util.Objects;
026
027import org.apache.commons.collections4.ResettableListIterator;
028
029/**
030 * Converts an {@link Iterator} into a {@link ResettableListIterator}.
031 * For plain {@code Iterator}s this is accomplished by caching the returned
032 * elements.  This class can also be used to simply add
033 * {@link org.apache.commons.collections4.ResettableIterator ResettableIterator}
034 * functionality to a given {@link ListIterator}.
035 * <p>
036 * The {@code ListIterator} interface has additional useful methods
037 * for navigation - {@code previous()} and the index methods.
038 * This class allows a regular {@code Iterator} to behave as a
039 * {@code ListIterator}. It achieves this by building a list internally
040 * of as the underlying iterator is traversed.
041 * </p>
042 * <p>
043 * The optional operations of {@code ListIterator} are not supported for plain {@code Iterator}s.
044 * </p>
045 * <p>
046 * This class implements ResettableListIterator from Commons Collections 3.2.
047 * </p>
048 *
049 * @param <E> the type of elements in this iterator.
050 * @since 2.1
051 */
052public class ListIteratorWrapper<E> implements ResettableListIterator<E> {
053
054    /** Message used when set or add are called. */
055    private static final String UNSUPPORTED_OPERATION_MESSAGE =
056        "ListIteratorWrapper does not support optional operations of ListIterator.";
057
058    /** Message used when set or add are called. */
059    private static final String CANNOT_REMOVE_MESSAGE = "Cannot remove element at index {0}.";
060
061    /** The underlying iterator being decorated. */
062    private final Iterator<? extends E> iterator;
063    /** The list being used to cache the iterator. */
064    private final List<E> list = new ArrayList<>();
065
066    /** The current index of this iterator. */
067    private int currentIndex;
068    /** The current index of the wrapped iterator. */
069    private int wrappedIteratorIndex;
070    /** Recall whether the wrapped iterator's "cursor" is in such a state as to allow remove() to be called */
071    private boolean removeState;
072
073    /**
074     * Constructs a new {@code ListIteratorWrapper} that will wrap
075     * the given iterator.
076     *
077     * @param iterator  the iterator to wrap
078     * @throws NullPointerException if the iterator is null
079     */
080    public ListIteratorWrapper(final Iterator<? extends E> iterator) {
081        this.iterator = Objects.requireNonNull(iterator, "iterator");
082    }
083
084    /**
085     * Throws {@link UnsupportedOperationException}
086     * unless the underlying {@code Iterator} is a {@code ListIterator}.
087     *
088     * @param obj  the object to add
089     * @throws UnsupportedOperationException if the underlying iterator is not of
090     * type {@link ListIterator}
091     */
092    @Override
093    public void add(final E obj) throws UnsupportedOperationException {
094        if (iterator instanceof ListIterator) {
095            @SuppressWarnings("unchecked")
096            final ListIterator<E> li = (ListIterator<E>) iterator;
097            li.add(obj);
098            return;
099        }
100        throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
101    }
102
103    /**
104     * Returns true if there are more elements in the iterator.
105     *
106     * @return true if there are more elements
107     */
108    @Override
109    public boolean hasNext() {
110        if (currentIndex == wrappedIteratorIndex || iterator instanceof ListIterator) {
111            return iterator.hasNext();
112        }
113        return true;
114    }
115
116    /**
117     * Returns true if there are previous elements in the iterator.
118     *
119     * @return true if there are previous elements
120     */
121    @Override
122    public boolean hasPrevious() {
123        if (iterator instanceof ListIterator) {
124            final ListIterator<?> li = (ListIterator<?>) iterator;
125            return li.hasPrevious();
126        }
127        return currentIndex > 0;
128    }
129
130    /**
131     * Returns the next element from the iterator.
132     *
133     * @return the next element from the iterator
134     * @throws NoSuchElementException if there are no more elements
135     */
136    @Override
137    public E next() throws NoSuchElementException {
138        if (iterator instanceof ListIterator) {
139            return iterator.next();
140        }
141
142        if (currentIndex < wrappedIteratorIndex) {
143            ++currentIndex;
144            return list.get(currentIndex - 1);
145        }
146
147        final E retval = iterator.next();
148        list.add(retval);
149        ++currentIndex;
150        ++wrappedIteratorIndex;
151        removeState = true;
152        return retval;
153    }
154
155    /**
156     * Returns the index of the next element.
157     *
158     * @return the index of the next element
159     */
160    @Override
161    public int nextIndex() {
162        if (iterator instanceof ListIterator) {
163            final ListIterator<?> li = (ListIterator<?>) iterator;
164            return li.nextIndex();
165        }
166        return currentIndex;
167    }
168
169    /**
170     * Returns the previous element.
171     *
172     * @return the previous element
173     * @throws NoSuchElementException  if there are no previous elements
174     */
175    @Override
176    public E previous() throws NoSuchElementException {
177        if (iterator instanceof ListIterator) {
178            @SuppressWarnings("unchecked")
179            final ListIterator<E> li = (ListIterator<E>) iterator;
180            return li.previous();
181        }
182
183        if (currentIndex == 0) {
184            throw new NoSuchElementException();
185        }
186        removeState = wrappedIteratorIndex == currentIndex;
187        return list.get(--currentIndex);
188    }
189
190    /**
191     * Returns the index of the previous element.
192     *
193     * @return  the index of the previous element
194     */
195    @Override
196    public int previousIndex() {
197        if (iterator instanceof ListIterator) {
198            final ListIterator<?> li = (ListIterator<?>) iterator;
199            return li.previousIndex();
200        }
201        return currentIndex - 1;
202    }
203
204    /**
205     * Removes the last element that was returned by {@link #next()} or {@link #previous()} from the underlying collection.
206     * This call can only be made once per call to {@code next} or {@code previous} and only if {@link #add(Object)} was not called in between.
207     *
208     * @throws IllegalStateException if {@code next} or {@code previous} have not been called before, or if {@code remove} or {@code add} have been called after the last call to {@code next} or {@code previous}
209     */
210    @Override
211    public void remove() throws IllegalStateException {
212        if (iterator instanceof ListIterator) {
213            iterator.remove();
214            return;
215        }
216        int removeIndex = currentIndex;
217        if (currentIndex == wrappedIteratorIndex) {
218            --removeIndex;
219        }
220        if (!removeState || wrappedIteratorIndex - currentIndex > 1) {
221            throw new IllegalStateException(MessageFormat.format(CANNOT_REMOVE_MESSAGE, Integer.valueOf(removeIndex)));
222        }
223        iterator.remove();
224        list.remove(removeIndex);
225        currentIndex = removeIndex;
226        wrappedIteratorIndex--;
227        removeState = false;
228    }
229
230    /**
231     * Resets this iterator back to the position at which the iterator
232     * was created.
233     *
234     * @since 3.2
235     */
236    @Override
237    public void reset()  {
238        if (iterator instanceof ListIterator) {
239            final ListIterator<?> li = (ListIterator<?>) iterator;
240            while (li.previousIndex() >= 0) {
241                li.previous();
242            }
243            return;
244        }
245        currentIndex = 0;
246    }
247
248    /**
249     * Throws {@link UnsupportedOperationException}
250     * unless the underlying {@code Iterator} is a {@code ListIterator}.
251     *
252     * @param obj  the object to set
253     * @throws UnsupportedOperationException if the underlying iterator is not of
254     * type {@link ListIterator}
255     */
256    @Override
257    public void set(final E obj) throws UnsupportedOperationException {
258        if (iterator instanceof ListIterator) {
259            @SuppressWarnings("unchecked")
260            final ListIterator<E> li = (ListIterator<E>) iterator;
261            li.set(obj);
262            return;
263        }
264        throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
265    }
266
267}