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 }