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