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    *      https://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.ResettableIterator;
28  import org.apache.commons.collections4.ResettableListIterator;
29  
30  /**
31   * Converts an {@link Iterator} into a {@link ResettableListIterator}.
32   * For plain {@code Iterator}s this is accomplished by caching the returned
33   * elements.  This class can also be used to simply add
34   * {@link ResettableIterator ResettableIterator}
35   * functionality to a given {@link ListIterator}.
36   * <p>
37   * The {@code ListIterator} interface has additional useful methods
38   * for navigation - {@code previous()} and the index methods.
39   * This class allows a regular {@code Iterator} to behave as a
40   * {@code ListIterator}. It achieves this by building a list internally
41   * of as the underlying iterator is traversed.
42   * </p>
43   * <p>
44   * The optional operations of {@code ListIterator} are not supported for plain {@code Iterator}s.
45   * </p>
46   * <p>
47   * This class implements ResettableListIterator from Commons Collections 3.2.
48   * </p>
49   *
50   * @param <E> The type of elements in this iterator.
51   * @since 2.1
52   */
53  public class ListIteratorWrapper<E> implements ResettableListIterator<E> {
54  
55      /** Message used when set or add are called. */
56      private static final String UNSUPPORTED_OPERATION_MESSAGE =
57          "ListIteratorWrapper does not support optional operations of ListIterator.";
58  
59      /** Message used when set or add are called. */
60      private static final String CANNOT_REMOVE_MESSAGE = "Cannot remove element at index {0}.";
61  
62      /** The underlying iterator being decorated. */
63      private final Iterator<? extends E> iterator;
64  
65      /** The list being used to cache the iterator. */
66      private final List<E> list = new ArrayList<>();
67  
68      /** The current index of this iterator. */
69      private int currentIndex;
70  
71      /** The current index of the wrapped iterator. */
72      private int wrappedIteratorIndex;
73  
74      /** Recall whether the wrapped iterator's "cursor" is in such a state as to allow remove() to be called */
75      private boolean removeState;
76  
77      /**
78       * Constructs a new {@code ListIteratorWrapper} that will wrap
79       * the given iterator.
80       *
81       * @param iterator  The iterator to wrap
82       * @throws NullPointerException if the iterator is null
83       */
84      public ListIteratorWrapper(final Iterator<? extends E> iterator) {
85          this.iterator = Objects.requireNonNull(iterator, "iterator");
86      }
87  
88      /**
89       * Throws {@link UnsupportedOperationException}
90       * unless the underlying {@code Iterator} is a {@code ListIterator}.
91       *
92       * @param obj  The object to add
93       * @throws UnsupportedOperationException if the underlying iterator is not of
94       * type {@link ListIterator}
95       */
96      @Override
97      public void add(final E obj) throws UnsupportedOperationException {
98          if (iterator instanceof ListIterator) {
99              @SuppressWarnings("unchecked")
100             final ListIterator<E> li = (ListIterator<E>) iterator;
101             li.add(obj);
102             return;
103         }
104         throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
105     }
106 
107     /**
108      * Returns true if there are more elements in the iterator.
109      *
110      * @return true if there are more elements
111      */
112     @Override
113     public boolean hasNext() {
114         if (currentIndex == wrappedIteratorIndex || iterator instanceof ListIterator) {
115             return iterator.hasNext();
116         }
117         return true;
118     }
119 
120     /**
121      * Returns true if there are previous elements in the iterator.
122      *
123      * @return true if there are previous elements
124      */
125     @Override
126     public boolean hasPrevious() {
127         if (iterator instanceof ListIterator) {
128             final ListIterator<?> li = (ListIterator<?>) iterator;
129             return li.hasPrevious();
130         }
131         return currentIndex > 0;
132     }
133 
134     /**
135      * Returns the next element from the iterator.
136      *
137      * @return The next element from the iterator
138      * @throws NoSuchElementException if there are no more elements
139      */
140     @Override
141     public E next() throws NoSuchElementException {
142         if (iterator instanceof ListIterator) {
143             return iterator.next();
144         }
145 
146         if (currentIndex < wrappedIteratorIndex) {
147             ++currentIndex;
148             return list.get(currentIndex - 1);
149         }
150 
151         final E retval = iterator.next();
152         list.add(retval);
153         ++currentIndex;
154         ++wrappedIteratorIndex;
155         removeState = true;
156         return retval;
157     }
158 
159     /**
160      * Returns the index of the next element.
161      *
162      * @return The index of the next element
163      */
164     @Override
165     public int nextIndex() {
166         if (iterator instanceof ListIterator) {
167             final ListIterator<?> li = (ListIterator<?>) iterator;
168             return li.nextIndex();
169         }
170         return currentIndex;
171     }
172 
173     /**
174      * Returns the previous element.
175      *
176      * @return The previous element
177      * @throws NoSuchElementException  if there are no previous elements
178      */
179     @Override
180     public E previous() throws NoSuchElementException {
181         if (iterator instanceof ListIterator) {
182             @SuppressWarnings("unchecked")
183             final ListIterator<E> li = (ListIterator<E>) iterator;
184             return li.previous();
185         }
186 
187         if (currentIndex == 0) {
188             throw new NoSuchElementException();
189         }
190         removeState = wrappedIteratorIndex == currentIndex;
191         return list.get(--currentIndex);
192     }
193 
194     /**
195      * Returns the index of the previous element.
196      *
197      * @return  the index of the previous element
198      */
199     @Override
200     public int previousIndex() {
201         if (iterator instanceof ListIterator) {
202             final ListIterator<?> li = (ListIterator<?>) iterator;
203             return li.previousIndex();
204         }
205         return currentIndex - 1;
206     }
207 
208     /**
209      * Removes the last element that was returned by {@link #next()} or {@link #previous()} from the underlying collection.
210      * 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.
211      *
212      * @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}
213      */
214     @Override
215     public void remove() throws IllegalStateException {
216         if (iterator instanceof ListIterator) {
217             iterator.remove();
218             return;
219         }
220         int removeIndex = currentIndex;
221         if (currentIndex == wrappedIteratorIndex) {
222             --removeIndex;
223         }
224         if (!removeState || wrappedIteratorIndex - currentIndex > 1) {
225             throw new IllegalStateException(MessageFormat.format(CANNOT_REMOVE_MESSAGE, Integer.valueOf(removeIndex)));
226         }
227         iterator.remove();
228         list.remove(removeIndex);
229         currentIndex = removeIndex;
230         wrappedIteratorIndex--;
231         removeState = false;
232     }
233 
234     /**
235      * Resets this iterator back to the position at which the iterator
236      * was created.
237      *
238      * @since 3.2
239      */
240     @Override
241     public void reset()  {
242         if (iterator instanceof ListIterator) {
243             final ListIterator<?> li = (ListIterator<?>) iterator;
244             while (li.previousIndex() >= 0) {
245                 li.previous();
246             }
247             return;
248         }
249         currentIndex = 0;
250     }
251 
252     /**
253      * Throws {@link UnsupportedOperationException}
254      * unless the underlying {@code Iterator} is a {@code ListIterator}.
255      *
256      * @param obj  The object to set
257      * @throws UnsupportedOperationException if the underlying iterator is not of
258      * type {@link ListIterator}
259      */
260     @Override
261     public void set(final E obj) throws UnsupportedOperationException {
262         if (iterator instanceof ListIterator) {
263             @SuppressWarnings("unchecked")
264             final ListIterator<E> li = (ListIterator<E>) iterator;
265             li.set(obj);
266             return;
267         }
268         throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_MESSAGE);
269     }
270 
271 }