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