View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.iterators;
18  
19  import java.text.MessageFormat;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.ListIterator;
24  import java.util.NoSuchElementException;
25  import java.util.Objects;
26  
27  import org.apache.commons.collections4.ResettableListIterator;
28  
29  /**
30   * Converts an {@link Iterator} into a {@link ResettableListIterator}.
31   * For plain {@code Iterator}s this is accomplished by caching the returned
32   * elements.  This class can also be used to simply add
33   * {@link org.apache.commons.collections4.ResettableIterator ResettableIterator}
34   * functionality to a given {@link ListIterator}.
35   * <p>
36   * The {@code ListIterator} interface has additional useful methods
37   * for navigation - {@code previous()} and the index methods.
38   * This class allows a regular {@code Iterator} to behave as a
39   * {@code ListIterator}. It achieves this by building a list internally
40   * of as the underlying iterator is traversed.
41   * </p>
42   * <p>
43   * The optional operations of {@code ListIterator} are not supported for plain {@code Iterator}s.
44   * </p>
45   * <p>
46   * This class implements ResettableListIterator from Commons Collections 3.2.
47   * </p>
48   *
49   * @param <E> the type of elements in this iterator.
50   * @since 2.1
51   */
52  public class ListIteratorWrapper<E> implements ResettableListIterator<E> {
53  
54      /** Message used when set or add are called. */
55      private static final String UNSUPPORTED_OPERATION_MESSAGE =
56          "ListIteratorWrapper does not support optional operations of ListIterator.";
57  
58      /** Message used when set or add are called. */
59      private static final String CANNOT_REMOVE_MESSAGE = "Cannot remove element at index {0}.";
60  
61      /** The underlying iterator being decorated. */
62      private final Iterator<? extends E> iterator;
63      /** The list being used to cache the iterator. */
64      private final List<E> list = new ArrayList<>();
65  
66      /** The current index of this iterator. */
67      private int currentIndex;
68      /** The current index of the wrapped iterator. */
69      private int wrappedIteratorIndex;
70      /** Recall whether the wrapped iterator's "cursor" is in such a state as to allow remove() to be called */
71      private boolean removeState;
72  
73      /**
74       * Constructs a new {@code ListIteratorWrapper} that will wrap
75       * the given iterator.
76       *
77       * @param iterator  the iterator to wrap
78       * @throws NullPointerException if the iterator is null
79       */
80      public ListIteratorWrapper(final Iterator<? extends E> iterator) {
81          this.iterator = Objects.requireNonNull(iterator, "iterator");
82      }
83  
84      /**
85       * Throws {@link UnsupportedOperationException}
86       * unless the underlying {@code Iterator} is a {@code ListIterator}.
87       *
88       * @param obj  the object to add
89       * @throws UnsupportedOperationException if the underlying iterator is not of
90       * type {@link ListIterator}
91       */
92      @Override
93      public void add(final E obj) throws UnsupportedOperationException {
94          if (iterator instanceof ListIterator) {
95              @SuppressWarnings("unchecked")
96              final ListIterator<E> li = (ListIterator<E>) iterator;
97              li.add(obj);
98              return;
99          }
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 }