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 }