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