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 static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotNull;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24 import static org.junit.jupiter.api.Assertions.assertTrue;
25
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.NoSuchElementException;
30
31 import org.apache.commons.collections4.AbstractObjectTest;
32 import org.apache.commons.collections4.IteratorUtils;
33 import org.junit.jupiter.api.Test;
34
35 /**
36 * Abstract class for testing the Iterator interface.
37 * <p>
38 * This class provides a framework for testing an implementation of Iterator.
39 * Concrete subclasses must provide the iterator to be tested.
40 * They must also specify certain details of how the iterator operates by
41 * overriding the supportsXxx() methods if necessary.
42 * </p>
43 *
44 * @param <E> the type of elements tested by this iterator.
45 */
46 public abstract class AbstractIteratorTest<E> extends AbstractObjectTest {
47
48 /**
49 * Implement this method to return an iterator over an empty collection.
50 *
51 * @return an empty iterator
52 */
53 public abstract Iterator<E> makeEmptyIterator();
54
55 /**
56 * Implements the abstract superclass method to return the full iterator.
57 *
58 * @return a full iterator
59 */
60 @Override
61 public abstract Iterator<E> makeObject();
62
63 /**
64 * Tests whether or not we are testing an iterator that can be empty.
65 * Default is true.
66 *
67 * @return true if Iterator can be empty
68 */
69 public boolean supportsEmptyIterator() {
70 return true;
71 }
72
73 /**
74 * Tests whether or not we are testing an iterator that can contain elements.
75 * Default is true.
76 *
77 * @return true if Iterator can be full
78 */
79 public boolean supportsFullIterator() {
80 return true;
81 }
82
83 /**
84 * Tests whether or not we are testing an iterator that supports remove().
85 * Default is true.
86 *
87 * @return true if Iterator supports remove
88 */
89 public boolean supportsRemove() {
90 return true;
91 }
92
93 /**
94 * Test the empty iterator.
95 */
96 @Test
97 public void testEmptyIterator() {
98 if (!supportsEmptyIterator()) {
99 return;
100 }
101
102 final Iterator<E> it = makeEmptyIterator();
103
104 // hasNext() should return false
105 assertFalse(it.hasNext(), "hasNext() should return false for empty iterators");
106
107 // next() should throw a NoSuchElementException
108 assertThrows(NoSuchElementException.class, () -> it.next(),
109 "NoSuchElementException must be thrown when Iterator is exhausted");
110 verify();
111
112 assertNotNull(it.toString());
113 }
114
115 /**
116 * Tests {@link Iterator#forEachRemaining(java.util.function.Consumer)}.
117 */
118 @Test
119 public void testForEachRemaining() {
120 final List<E> expected = IteratorUtils.toList(makeObject());
121 final Iterator<E> it = makeObject();
122 final List<E> actual = new ArrayList<>();
123 it.forEachRemaining(actual::add);
124 assertEquals(expected, actual);
125 }
126
127 /**
128 * Test normal iteration behavior.
129 */
130 @Test
131 public void testFullIterator() {
132 if (!supportsFullIterator()) {
133 return;
134 }
135
136 final Iterator<E> it = makeObject();
137
138 // hasNext() must be true (ensure makeFullIterator is correct!)
139 assertTrue(it.hasNext(), "hasNext() should return true for at least one element");
140
141 // next() must not throw exception (ensure makeFullIterator is correct!)
142 assertDoesNotThrow(it::next, "Full iterators must have at least one element");
143
144 // iterate through
145 while (it.hasNext()) {
146 it.next();
147 verify();
148 }
149
150 // next() must throw NoSuchElementException now
151 assertThrows(NoSuchElementException.class, () -> it.next(),
152 "NoSuchElementException must be thrown when Iterator is exhausted");
153
154 assertNotNull(it.toString());
155 }
156
157 /**
158 * Test remove behavior.
159 */
160 @Test
161 public void testRemove() {
162 final Iterator<E> it = makeObject();
163
164 if (!supportsRemove()) {
165 // check for UnsupportedOperationException if not supported
166 assertThrows(UnsupportedOperationException.class, it::remove);
167 return;
168 }
169
170 // should throw IllegalStateException before next() called
171 assertThrows(IllegalStateException.class, () -> it.remove());
172 verify();
173
174 // remove after next should be fine
175 it.next();
176 it.remove();
177
178 // should throw IllegalStateException for second remove()
179 assertThrows(IllegalStateException.class, () -> it.remove());
180 }
181
182 /**
183 * Allows subclasses to add complex cross verification
184 */
185 public void verify() {
186 // do nothing
187 }
188
189 }