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 static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotSame;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.util.ArrayList;
26  import java.util.ListIterator;
27  import java.util.NoSuchElementException;
28  
29  import org.junit.jupiter.api.Test;
30  
31  /**
32   * Abstract class for testing the ListIterator interface.
33   * <p>
34   * This class provides a framework for testing an implementation of ListIterator.
35   * Concrete subclasses must provide the list iterator to be tested.
36   * They must also specify certain details of how the list iterator operates by
37   * overriding the supportsXxx() methods if necessary.
38   * </p>
39   *
40   * @param <E> the type of elements tested by this iterator.
41   */
42  public abstract class AbstractListIteratorTest<E> extends AbstractIteratorTest<E> {
43  
44      /**
45       * The value to be used in the add and set tests.
46       * Default is null.
47       */
48      public E addSetValue() {
49          return null;
50      }
51  
52      /**
53       * Implements the abstract superclass method to return the list iterator.
54       *
55       * @return an empty iterator
56       */
57      @Override
58      public abstract ListIterator<E> makeEmptyIterator();
59  
60      /**
61       * Implements the abstract superclass method to return the list iterator.
62       *
63       * @return a full iterator
64       */
65      @Override
66      public abstract ListIterator<E> makeObject();
67  
68      /**
69       * Tests whether or not we are testing an iterator that supports add().
70       * Default is true.
71       *
72       * @return true if Iterator supports add
73       */
74      public boolean supportsAdd() {
75          return true;
76      }
77  
78      /**
79       * Tests whether or not we are testing an iterator that supports set().
80       * Default is true.
81       *
82       * @return true if Iterator supports set
83       */
84      public boolean supportsSet() {
85          return true;
86      }
87  
88      /**
89       * Test add behavior.
90       */
91      @Test
92      public void testAdd() {
93          ListIterator<E> it = makeObject();
94  
95          final E addValue = addSetValue();
96          if (!supportsAdd()) {
97              // check for UnsupportedOperationException if not supported
98              final ListIterator<E> finalIt0 = it;
99              assertThrows(UnsupportedOperationException.class, () -> finalIt0.add(addValue),
100                     "UnsupportedOperationException must be thrown from add of " + it.getClass().getSimpleName());
101             return;
102         }
103 
104         // add at start should be OK, added should be previous
105         it = makeObject();
106         it.add(addValue);
107         assertEquals(addValue, it.previous());
108 
109         // add at start should be OK, added should not be next
110         it = makeObject();
111         it.add(addValue);
112         assertNotSame(addValue, it.next());
113 
114         // add in middle and at end should be OK
115         it = makeObject();
116         while (it.hasNext()) {
117             it.next();
118             it.add(addValue);
119             // check add OK
120             assertEquals(addValue, it.previous());
121             it.next();
122         }
123     }
124 
125     /**
126      * Test remove after add behavior.
127      */
128     @Test
129     public void testAddThenRemove() {
130         final ListIterator<E> it = makeObject();
131 
132         // add then remove
133         if (supportsAdd() && supportsRemove()) {
134             it.next();
135             it.add(addSetValue());
136             assertThrows(IllegalStateException.class, () -> it.remove(),
137                     "IllegalStateException must be thrown from remove after add");
138         }
139     }
140 
141     @Test
142     public void testAddThenSet() {
143         final ListIterator<E> it = makeObject();
144         // add then set
145         if (supportsAdd() && supportsSet()) {
146             it.next();
147             it.add(addSetValue());
148             assertThrows(IllegalStateException.class, () -> it.set(addSetValue()),
149                     "IllegalStateException must be thrown from set after add");
150         }
151     }
152 
153     /**
154      * Test that the empty list iterator contract is correct.
155      */
156     @Test
157     public void testEmptyListIteratorIsIndeedEmpty() {
158         if (!supportsEmptyIterator()) {
159             return;
160         }
161 
162         final ListIterator<E> it = makeEmptyIterator();
163 
164         assertFalse(it.hasNext());
165         assertEquals(0, it.nextIndex());
166         assertFalse(it.hasPrevious());
167         assertEquals(-1, it.previousIndex());
168 
169         // next() should throw a NoSuchElementException
170         assertThrows(NoSuchElementException.class, () -> it.next(),
171                 "NoSuchElementException must be thrown from empty ListIterator");
172 
173         // previous() should throw a NoSuchElementException
174         assertThrows(NoSuchElementException.class, () -> it.previous(),
175                 "NoSuchElementException must be thrown from empty ListIterator");
176     }
177 
178     @Test
179     public void testRemoveThenSet() {
180         final ListIterator<E> it = makeObject();
181         if (supportsRemove() && supportsSet()) {
182             it.next();
183             it.remove();
184             assertThrows(IllegalStateException.class, () -> it.set(addSetValue()),
185                     "IllegalStateException must be thrown from set after remove");
186         }
187     }
188 
189     /**
190      * Test set behavior.
191      */
192     @Test
193     public void testSet() {
194         final ListIterator<E> it = makeObject();
195 
196         if (!supportsSet()) {
197             // check for UnsupportedOperationException if not supported
198             assertThrows(UnsupportedOperationException.class, () -> it.set(addSetValue()),
199                     "UnsupportedOperationException must be thrown from set in " + it.getClass().getSimpleName());
200             return;
201         }
202 
203         // should throw IllegalStateException before next() called
204         assertThrows(IllegalStateException.class, () -> it.set(addSetValue()));
205 
206         // set after next should be fine
207         it.next();
208         it.set(addSetValue());
209 
210         // repeated set calls should be fine
211         it.set(addSetValue());
212 
213     }
214 
215     /**
216      * Test navigation through the iterator.
217      */
218     @Test
219     public void testWalkForwardAndBack() {
220         final ArrayList<E> list = new ArrayList<>();
221         final ListIterator<E> it = makeObject();
222         while (it.hasNext()) {
223             list.add(it.next());
224         }
225 
226         // check state at end
227         assertFalse(it.hasNext());
228         assertTrue(it.hasPrevious());
229         assertThrows(NoSuchElementException.class, () -> it.next(),
230                 "NoSuchElementException must be thrown from next at end of ListIterator");
231 
232         // loop back through comparing
233         for (int i = list.size() - 1; i >= 0; i--) {
234             assertEquals(i + 1, it.nextIndex());
235             assertEquals(i, it.previousIndex());
236 
237             final Object obj = list.get(i);
238             assertEquals(obj, it.previous());
239         }
240 
241         // check state at start
242         assertTrue(it.hasNext());
243         assertFalse(it.hasPrevious());
244         assertThrows(NoSuchElementException.class, () -> it.previous(),
245                 "NoSuchElementException must be thrown from previous at start of ListIterator");
246     }
247 
248 }