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