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.assertSame;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.util.HashSet;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Set;
30  
31  import org.apache.commons.collections4.MapIterator;
32  import org.junit.jupiter.api.Test;
33  
34  /**
35   * Abstract class for testing the MapIterator interface.
36   * <p>
37   * This class provides a framework for testing an implementation of MapIterator.
38   * Concrete subclasses must provide the list iterator to be tested.
39   * They must also specify certain details of how the list iterator operates by
40   * overriding the supportsXxx() methods if necessary.
41   */
42  public abstract class AbstractMapIteratorTest<K, V> extends AbstractIteratorTest<K> {
43  
44      /**
45       * JUnit constructor.
46       *
47       * @param testName  the test class name
48       */
49      public AbstractMapIteratorTest(final String testName) {
50          super(testName);
51      }
52  
53      /**
54       * The values to be used in the add and set tests.
55       * Default is two strings.
56       */
57      @SuppressWarnings("unchecked")
58      public V[] addSetValues() {
59          return (V[]) new Object[] { "A", "B" };
60      }
61  
62      /**
63       * Implement this method to return the confirmed map which contains the same
64       * data as the iterator.
65       *
66       * @return a full map which can be updated
67       */
68      public abstract Map<K, V> getConfirmedMap();
69  
70      /**
71       * Implement this method to return the map which contains the same data as the
72       * iterator.
73       *
74       * @return a full map which can be updated
75       */
76      public abstract Map<K, V> getMap();
77  
78      /**
79       * Whether the get operation on the map structurally modifies the map,
80       * such as with LRUMap. Default is false.
81       *
82       * @return true if the get method structurally modifies the map
83       */
84      public boolean isGetStructuralModify() {
85          return false;
86      }
87  
88      /**
89       * Implement this method to return a map iterator over an empty map.
90       *
91       * @return an empty iterator
92       */
93      @Override
94      public abstract MapIterator<K, V> makeEmptyIterator();
95  
96      /**
97       * Implement this method to return a map iterator over a map with elements.
98       *
99       * @return a full iterator
100      */
101     @Override
102     public abstract MapIterator<K, V> makeObject();
103 
104     /**
105      * Whether or not we are testing an iterator that supports setValue().
106      * Default is true.
107      *
108      * @return true if Iterator supports set
109      */
110     public boolean supportsSetValue() {
111         return true;
112     }
113 
114     /**
115      * Test that the empty list iterator contract is correct.
116      */
117     @Test
118     public void testEmptyMapIterator() {
119         if (!supportsEmptyIterator()) {
120             return;
121         }
122 
123         final MapIterator<K, V> it = makeEmptyIterator();
124         assertFalse(it.hasNext());
125 
126         // next() should throw a NoSuchElementException
127         assertThrows(NoSuchElementException.class, () -> it.next());
128 
129         // getKey() should throw an IllegalStateException
130         assertThrows(IllegalStateException.class, () -> it.getKey());
131 
132         // getValue() should throw an IllegalStateException
133         assertThrows(IllegalStateException.class, () -> it.getValue());
134 
135         if (!supportsSetValue()) {
136             // setValue() should throw an UnsupportedOperationException/IllegalStateException
137             try {
138                 it.setValue(addSetValues()[0]);
139                 fail();
140             } catch (final UnsupportedOperationException | IllegalStateException ex) {
141                 // ignore
142             }
143         } else {
144             // setValue() should throw an IllegalStateException
145             assertThrows(IllegalStateException.class, () -> it.setValue(addSetValues()[0]));
146         }
147     }
148 
149     /**
150      * Test that the full list iterator contract is correct.
151      */
152     @Test
153     public void testFullMapIterator() {
154         if (!supportsFullIterator()) {
155             return;
156         }
157 
158         final MapIterator<K, V> it = makeObject();
159         final Map<K, V> map = getMap();
160         assertTrue(it.hasNext());
161 
162         assertTrue(it.hasNext());
163         final Set<K> set = new HashSet<>();
164         while (it.hasNext()) {
165             // getKey
166             final K key = it.next();
167             assertSame(key, it.getKey(), "it.next() should equals getKey()");
168             assertTrue(map.containsKey(key),  "Key must be in map");
169             assertTrue(set.add(key), "Key must be unique");
170 
171             // getValue
172             final V value = it.getValue();
173             if (!isGetStructuralModify()) {
174                 assertSame(map.get(key), value, "Value must be mapped to key");
175             }
176             assertTrue(map.containsValue(value),  "Value must be in map");
177 
178             verify();
179         }
180     }
181 
182     @Test
183     public void testMapIteratorRemoveGetKey() {
184         if (!supportsRemove()) {
185             return;
186         }
187         final MapIterator<K, V> it = makeObject();
188         final Map<K, V> confirmed = getConfirmedMap();
189 
190         assertTrue(it.hasNext());
191         final K key = it.next();
192 
193         it.remove();
194         confirmed.remove(key);
195         verify();
196 
197         assertThrows(IllegalStateException.class, () -> it.getKey());
198         verify();
199     }
200 
201     @Test
202     public void testMapIteratorRemoveGetValue() {
203         if (!supportsRemove()) {
204             return;
205         }
206         final MapIterator<K, V> it = makeObject();
207         final Map<K, V> confirmed = getConfirmedMap();
208 
209         assertTrue(it.hasNext());
210         final K key = it.next();
211 
212         it.remove();
213         confirmed.remove(key);
214         verify();
215 
216         assertThrows(IllegalStateException.class, () -> it.getValue());
217         verify();
218     }
219 
220     @Test
221     public void testMapIteratorSet() {
222         if (!supportsFullIterator()) {
223             return;
224         }
225 
226         final V newValue = addSetValues()[0];
227         final V newValue2 = addSetValues().length == 1 ? addSetValues()[0] : addSetValues()[1];
228         final MapIterator<K, V> it = makeObject();
229         final Map<K, V> map = getMap();
230         final Map<K, V> confirmed = getConfirmedMap();
231         assertTrue(it.hasNext());
232         final K key = it.next();
233         final V value = it.getValue();
234 
235         if (!supportsSetValue()) {
236             assertThrows(UnsupportedOperationException.class, () -> it.setValue(newValue));
237             return;
238         }
239         final V old = it.setValue(newValue);
240         confirmed.put(key, newValue);
241         assertSame(key, it.getKey(), "Key must not change after setValue");
242         assertSame(newValue, it.getValue(), "Value must be changed after setValue");
243         assertSame(value, old, "setValue must return old value");
244         assertTrue(map.containsKey(key), "Map must contain key");
245         // test against confirmed, as map may contain value twice
246         assertEquals(confirmed.containsValue(old), map.containsValue(old),
247             "Map must not contain old value");
248         assertTrue(map.containsValue(newValue), "Map must contain new value");
249         verify();
250 
251         it.setValue(newValue);  // same value - should be OK
252         confirmed.put(key, newValue);
253         assertSame(key, it.getKey(), "Key must not change after setValue");
254         assertSame(newValue, it.getValue(), "Value must be changed after setValue");
255         verify();
256 
257         it.setValue(newValue2);  // new value
258         confirmed.put(key, newValue2);
259         assertSame(key, it.getKey(), "Key must not change after setValue");
260         assertSame(newValue2, it.getValue(), "Value must be changed after setValue");
261         verify();
262     }
263 
264     @Test
265     public void testMapIteratorSetRemoveSet() {
266         if (!supportsSetValue() || !supportsRemove()) {
267             return;
268         }
269         final V newValue = addSetValues()[0];
270         final MapIterator<K, V> it = makeObject();
271         final Map<K, V> confirmed = getConfirmedMap();
272 
273         assertTrue(it.hasNext());
274         final K key = it.next();
275 
276         it.setValue(newValue);
277         it.remove();
278         confirmed.remove(key);
279         verify();
280 
281         assertThrows(IllegalStateException.class, () -> it.setValue(newValue));
282         verify();
283     }
284 
285     @Test
286     @Override
287     public void testRemove() { // override
288         final MapIterator<K, V> it = makeObject();
289         final Map<K, V> map = getMap();
290         final Map<K, V> confirmed = getConfirmedMap();
291         assertTrue(it.hasNext());
292         final K key = it.next();
293 
294         if (!supportsRemove()) {
295             assertThrows(UnsupportedOperationException.class, () -> it.remove());
296             return;
297         }
298 
299         it.remove();
300         confirmed.remove(key);
301         assertFalse(map.containsKey(key));
302         verify();
303 
304         try {
305             it.remove();  // second remove fails
306         } catch (final IllegalStateException ex) {
307         }
308         verify();
309     }
310 
311 }