BUG-4803: fix MutableOffsetMap's insertion order
[yangtools.git] / common / util / src / test / java / org / opendaylight / yangtools / util / OffsetMapTest.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotSame;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertSame;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.Iterables;
19 import com.google.common.collect.Iterators;
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.util.AbstractMap.SimpleEntry;
26 import java.util.Collections;
27 import java.util.ConcurrentModificationException;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.NoSuchElementException;
32 import java.util.Set;
33 import org.junit.Before;
34 import org.junit.Test;
35
36 public class OffsetMapTest {
37     private final Map<String, String> twoEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2");
38     private final Map<String, String> threeEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3");
39
40     private ImmutableOffsetMap<String, String> createMap() {
41         return (ImmutableOffsetMap<String, String>) ImmutableOffsetMap.copyOf(twoEntryMap);
42     }
43
44     @Before
45     public void setup() {
46         OffsetMapCache.invalidateCache();
47     }
48
49     @Test(expected=IllegalArgumentException.class)
50     public void testWrongImmutableConstruction() {
51         new ImmutableOffsetMap.Ordered<String, String>(Collections.<String, Integer>emptyMap(), new String[1]);
52     }
53
54     @Test
55     public void testCopyEmptyMap() {
56         final Map<String, String> source = Collections.emptyMap();
57         final Map<String, String> result = ImmutableOffsetMap.copyOf(source);
58
59         assertEquals(source, result);
60         assertTrue(result instanceof ImmutableMap);
61     }
62
63     @Test
64     public void testCopySingletonMap() {
65         final Map<String, String> source = Collections.singletonMap("a", "b");
66         final Map<String, String> result = ImmutableOffsetMap.copyOf(source);
67
68         assertEquals(source, result);
69         assertTrue(result instanceof SharedSingletonMap);
70     }
71
72     @Test
73     public void testCopyMap() {
74         final ImmutableOffsetMap<String, String> map = createMap();
75
76         // Equality in both directions
77         assertEquals(twoEntryMap, map);
78         assertEquals(map, twoEntryMap);
79
80         // hashcode has to match
81         assertEquals(twoEntryMap.hashCode(), map.hashCode());
82
83         // Iterator order needs to be preserved
84         assertTrue(Iterators.elementsEqual(twoEntryMap.entrySet().iterator(), map.entrySet().iterator()));
85
86         // Should result in the same object
87         assertSame(map, ImmutableOffsetMap.copyOf(map));
88
89         final Map<String, String> mutable = map.toModifiableMap();
90         final Map<String, String> copy = ImmutableOffsetMap.copyOf(mutable);
91
92         assertEquals(mutable, copy);
93         assertEquals(map, copy);
94         assertNotSame(mutable, copy);
95         assertNotSame(map, copy);
96     }
97
98     @Test
99     public void testImmutableSimpleEquals() {
100         final Map<String, String> map = createMap();
101
102         assertTrue(map.equals(map));
103         assertFalse(map.equals(null));
104         assertFalse(map.equals("string"));
105     }
106
107     @Test
108     public void testImmutableGet() {
109         final Map<String, String> map = createMap();
110
111         assertEquals("v1", map.get("k1"));
112         assertEquals("v2", map.get("k2"));
113         assertNull(map.get("non-existent"));
114         assertNull(map.get(null));
115     }
116
117     @Test
118     public void testImmutableGuards() {
119         final Map<String, String> map = createMap();
120
121         try {
122             map.values().add("v1");
123             fail();
124         } catch (UnsupportedOperationException e) {
125         }
126
127         try {
128             map.values().remove("v1");
129             fail();
130         } catch (UnsupportedOperationException e) {
131         }
132
133         try {
134             map.values().clear();
135             fail();
136         } catch (UnsupportedOperationException e) {
137         }
138
139         try {
140             final Iterator<String> it = map.values().iterator();
141             it.next();
142             it.remove();
143             fail();
144         } catch (UnsupportedOperationException e) {
145         }
146
147         try {
148             map.keySet().add("k1");
149             fail();
150         } catch (UnsupportedOperationException e) {
151         }
152
153         try {
154             map.keySet().clear();
155             fail();
156         } catch (UnsupportedOperationException e) {
157         }
158
159         try {
160             map.keySet().remove("k1");
161             fail();
162         } catch (UnsupportedOperationException e) {
163         }
164
165         try {
166             final Iterator<String> it = map.keySet().iterator();
167             it.next();
168             it.remove();
169             fail();
170         } catch (UnsupportedOperationException e) {
171         }
172
173         try {
174             map.entrySet().clear();
175             fail();
176         } catch (UnsupportedOperationException e) {
177         }
178
179         try {
180             map.entrySet().add(new SimpleEntry<>("k1", "v1"));
181             fail();
182         } catch (UnsupportedOperationException e) {
183         }
184
185         try {
186             map.entrySet().remove(new SimpleEntry<>("k1", "v1"));
187             fail();
188         } catch (UnsupportedOperationException e) {
189         }
190
191         try {
192             final Iterator<Entry<String, String>> it = map.entrySet().iterator();
193             it.next();
194             it.remove();
195             fail();
196         } catch (UnsupportedOperationException e) {
197         }
198
199         try {
200             map.clear();
201             fail();
202         } catch (UnsupportedOperationException e) {
203         }
204
205         try {
206             map.put("k1", "fail");
207             fail();
208         } catch (UnsupportedOperationException e) {
209         }
210
211         try {
212             map.putAll(ImmutableMap.of("k1", "fail"));
213             fail();
214         } catch (UnsupportedOperationException e) {
215         }
216
217         try {
218             map.remove("k1");
219             fail();
220         } catch (UnsupportedOperationException e) {
221         }
222     }
223
224     @Test
225     public void testMutableGet() {
226         final Map<String, String> map = createMap().toModifiableMap();
227
228         map.put("k3", "v3");
229         assertEquals("v1", map.get("k1"));
230         assertEquals("v2", map.get("k2"));
231         assertEquals("v3", map.get("k3"));
232         assertNull(map.get("non-existent"));
233         assertNull(map.get(null));
234     }
235
236     @Test
237     public void testImmutableSize() {
238         final Map<String, String> map = createMap();
239         assertEquals(2, map.size());
240     }
241
242     @Test
243     public void testImmutableIsEmpty() {
244         final Map<String, String> map = createMap();
245         assertFalse(map.isEmpty());
246     }
247
248     @Test
249     public void testImmutableContains() {
250         final Map<String, String> map = createMap();
251         assertTrue(map.containsKey("k1"));
252         assertTrue(map.containsKey("k2"));
253         assertFalse(map.containsKey("non-existent"));
254         assertFalse(map.containsKey(null));
255         assertTrue(map.containsValue("v1"));
256         assertFalse(map.containsValue("non-existent"));
257     }
258
259     @Test
260     public void testImmutableEquals() {
261         final Map<String, String> map = createMap();
262
263         assertFalse(map.equals(threeEntryMap));
264         assertFalse(map.equals(ImmutableMap.of("k1", "v1", "k3", "v3")));
265         assertFalse(map.equals(ImmutableMap.of("k1", "v1", "k2", "different-value")));
266     }
267
268     @Test
269     public void testMutableContains() {
270         final Map<String, String> map = createMap().toModifiableMap();
271         map.put("k3", "v3");
272         assertTrue(map.containsKey("k1"));
273         assertTrue(map.containsKey("k2"));
274         assertTrue(map.containsKey("k3"));
275         assertFalse(map.containsKey("non-existent"));
276         assertFalse(map.containsKey(null));
277     }
278
279     @Test
280     public void testtoModifiableMap() {
281         final ImmutableOffsetMap<String, String> source = createMap();
282         final Map<String, String> result = source.toModifiableMap();
283
284         // The two maps should be equal, but isolated
285         assertTrue(result instanceof MutableOffsetMap);
286         assertEquals(source, result);
287         assertEquals(result, source);
288
289         // Quick test for clearing MutableOffsetMap
290         result.clear();
291         assertEquals(0, result.size());
292         assertEquals(Collections.emptyMap(), result);
293
294         // The two maps should differ now
295         assertFalse(source.equals(result));
296         assertFalse(result.equals(source));
297
298         // The source map should still equal the template
299         assertEquals(twoEntryMap, source);
300         assertEquals(source, twoEntryMap);
301     }
302
303     @Test
304     public void testReusedFields() {
305         final ImmutableOffsetMap<String, String> source = createMap();
306         final MutableOffsetMap<String, String> mutable = source.toModifiableMap();
307
308         // Should not affect the result
309         mutable.remove("non-existent");
310
311         // Resulting map should be equal, but not the same object
312         final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable.toUnmodifiableMap();
313         assertNotSame(source, result);
314         assertEquals(source, result);
315
316         // Internal fields should be reused
317         assertSame(source.offsets(), result.offsets());
318         assertSame(source.objects(), result.objects());
319     }
320
321     @Test
322     public void testReusedOffsets() {
323         final ImmutableOffsetMap<String, String> source = createMap();
324         final MutableOffsetMap<String, String> mutable = source.toModifiableMap();
325
326         mutable.remove("k1");
327         mutable.put("k1", "v1");
328
329         final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable.toUnmodifiableMap();
330         assertTrue(source.equals(result));
331         assertTrue(result.equals(source));
332
333         // Iterator order must not be preserved
334         assertFalse(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator()));
335     }
336
337     @Test
338     public void testEmptyMutable() throws CloneNotSupportedException {
339         final MutableOffsetMap<String, String> map = MutableOffsetMap.of();
340         assertTrue(map.isEmpty());
341
342         final Map<String, String> other = map.clone();
343         assertEquals(other, map);
344         assertNotSame(other, map);
345     }
346
347     @Test
348     public void testMutableToEmpty() {
349         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
350
351         mutable.remove("k1");
352         mutable.remove("k2");
353
354         assertTrue(mutable.isEmpty());
355         assertSame(ImmutableMap.of(), mutable.toUnmodifiableMap());
356     }
357
358     @Test
359     public void testMutableToSingleton() {
360         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
361
362         mutable.remove("k1");
363
364         final Map<String, String> result = mutable.toUnmodifiableMap();
365
366         // Should devolve to a singleton
367         assertTrue(result instanceof SharedSingletonMap);
368         assertEquals(ImmutableMap.of("k2", "v2"), result);
369     }
370
371     @Test
372     public void testMutableToNewSingleton() {
373         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
374
375         mutable.remove("k1");
376         mutable.put("k3", "v3");
377
378         final Map<String, String> result = mutable.toUnmodifiableMap();
379
380         assertTrue(result instanceof ImmutableOffsetMap);
381         assertEquals(ImmutableMap.of("k2", "v2", "k3", "v3"), result);
382     }
383
384     @Test
385     public void testMutableSize() {
386         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
387         assertEquals(2, mutable.size());
388
389         mutable.put("k3", "v3");
390         assertEquals(3, mutable.size());
391         mutable.remove("k2");
392         assertEquals(2, mutable.size());
393         mutable.put("k1", "new-v1");
394         assertEquals(2, mutable.size());
395     }
396
397     @Test
398     public void testExpansionWithOrder() {
399         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
400
401         mutable.remove("k1");
402         mutable.put("k3", "v3");
403         mutable.put("k1", "v1");
404
405         assertEquals(ImmutableMap.of("k1", "v1", "k3", "v3"), mutable.newKeys());
406
407         final Map<String, String> result = mutable.toUnmodifiableMap();
408
409         assertTrue(result instanceof ImmutableOffsetMap);
410         assertEquals(threeEntryMap, result);
411         assertEquals(result, threeEntryMap);
412         assertFalse(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator()));
413     }
414
415     @Test
416     public void testReplacedValue() {
417         final ImmutableOffsetMap<String, String> source = createMap();
418         final MutableOffsetMap<String, String> mutable = source.toModifiableMap();
419
420         mutable.put("k1", "replaced");
421
422         final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable.toUnmodifiableMap();
423         final Map<String, String> reference = ImmutableMap.of("k1", "replaced", "k2", "v2");
424
425         assertEquals(reference, result);
426         assertEquals(result, reference);
427         assertSame(source.offsets(), result.offsets());
428         assertNotSame(source.objects(), result.objects());
429     }
430
431     @Test
432     public void testCloneableFlipping() throws CloneNotSupportedException {
433         final MutableOffsetMap<String, String> source = createMap().toModifiableMap();
434
435         // Must clone before mutation
436         assertTrue(source.needClone());
437
438         // Non-existent entry, should not clone
439         source.remove("non-existent");
440         assertTrue(source.needClone());
441
442         // Changes the array, should clone
443         source.remove("k1");
444         assertFalse(source.needClone());
445
446         // Create a clone of the map, which shares the array
447         final MutableOffsetMap<String, String> result = source.clone();
448         assertFalse(source.needClone());
449         assertTrue(result.needClone());
450         assertSame(source.array(), result.array());
451
452         // Changes the array, should clone
453         source.put("k1", "v2");
454         assertFalse(source.needClone());
455         assertTrue(result.needClone());
456
457         // Forced copy, no cloning needed, but maps are equal
458         final ImmutableOffsetMap<String, String> immutable = (ImmutableOffsetMap<String, String>) source.toUnmodifiableMap();
459         assertFalse(source.needClone());
460         assertTrue(source.equals(immutable));
461         assertTrue(immutable.equals(source));
462         assertTrue(Iterables.elementsEqual(source.entrySet(), immutable.entrySet()));
463     }
464
465     @Test
466     public void testMutableEntrySet() {
467         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
468
469         assertTrue(map.entrySet().add(new SimpleEntry<>("k3", "v3")));
470         assertTrue(map.containsKey("k3"));
471         assertEquals("v3", map.get("k3"));
472
473         // null is not an Entry: ignore
474         assertFalse(map.entrySet().remove(null));
475
476         // non-matching value: ignore
477         assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", "other")));
478         assertTrue(map.containsKey("k1"));
479
480         // ignore null values
481         assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", null)));
482         assertTrue(map.containsKey("k1"));
483
484         assertTrue(map.entrySet().remove(new SimpleEntry<>("k1", "v1")));
485         assertFalse(map.containsKey("k1"));
486     }
487
488     private static void assertIteratorBroken(final Iterator<?> it) {
489         try {
490             it.hasNext();
491             fail();
492         } catch (ConcurrentModificationException e) {
493         }
494         try {
495             it.next();
496             fail();
497         } catch (ConcurrentModificationException e) {
498         }
499         try {
500             it.remove();
501             fail();
502         } catch (ConcurrentModificationException e) {
503         }
504     }
505
506     @Test
507     public void testMutableSimpleEquals() {
508         final ImmutableOffsetMap<String, String> source = createMap();
509         final Map<String, String> map = source.toModifiableMap();
510
511         assertTrue(map.equals(map));
512         assertFalse(map.equals(null));
513         assertFalse(map.equals("string"));
514         assertTrue(map.equals(source));
515     }
516
517     @Test
518     public void testMutableSimpleHashCode() {
519         final Map<String, String> map = createMap().toModifiableMap();
520
521         assertEquals(twoEntryMap.hashCode(), map.hashCode());
522     }
523
524     @Test
525     public void testMutableIteratorBasics() {
526         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
527         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
528
529         // Not advanced, remove should fail
530         try {
531             it.remove();
532             fail();
533         } catch (IllegalStateException e) {
534         }
535
536         assertTrue(it.hasNext());
537         assertEquals("k1", it.next().getKey());
538         assertTrue(it.hasNext());
539         assertEquals("k2", it.next().getKey());
540         assertFalse(it.hasNext());
541
542         // Check end-of-iteration throw
543         try {
544             it.next();
545             fail();
546         } catch (NoSuchElementException e) {
547         }
548     }
549
550     @Test
551     public void testMutableIteratorWithRemove() {
552         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
553         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
554
555         // Advance one element
556         assertTrue(it.hasNext());
557         assertEquals("k1", it.next().getKey());
558
559         // Remove k1
560         it.remove();
561         assertEquals(1, map.size());
562         assertFalse(map.containsKey("k1"));
563
564         // Iterator should still work
565         assertTrue(it.hasNext());
566         assertEquals("k2", it.next().getKey());
567         assertFalse(it.hasNext());
568     }
569
570     @Test
571     public void testMutableIteratorOffsetReplaceWorks() {
572         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
573         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
574         it.next();
575
576         map.put("k1", "new-v1");
577         assertTrue(it.hasNext());
578     }
579
580     @Test
581     public void testMutableIteratorNewReplaceWorks() {
582         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
583         map.put("k3", "v3");
584         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
585         it.next();
586
587         map.put("k3", "new-v3");
588         assertTrue(it.hasNext());
589     }
590
591     @Test
592     public void testMutableIteratorOffsetAddBreaks() {
593         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
594         map.put("k3", "v3");
595         map.remove("k1");
596
597         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
598         it.next();
599
600         map.put("k1", "new-v1");
601         assertIteratorBroken(it);
602     }
603
604     @Test
605     public void testMutableIteratorNewAddBreaks() {
606         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
607         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
608         it.next();
609
610         map.put("k3", "v3");
611         assertIteratorBroken(it);
612     }
613
614     @Test
615     public void testMutableIteratorOffsetRemoveBreaks() {
616         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
617         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
618         it.next();
619
620         map.remove("k1");
621         assertIteratorBroken(it);
622     }
623
624     @Test
625     public void testMutableIteratorNewRemoveBreaks() {
626         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
627         map.put("k3", "v3");
628         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
629         it.next();
630
631         map.remove("k3");
632         assertIteratorBroken(it);
633     }
634
635     @Test
636     public void testMutableCrossIteratorRemove() {
637         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
638         final Set<Entry<String, String>> es = map.entrySet();
639         final Iterator<Entry<String, String>> it1 = es.iterator();
640         final Iterator<Entry<String, String>> it2 = es.iterator();
641
642         // Remove k1 via it1
643         it1.next();
644         it2.next();
645         it1.remove();
646         assertEquals(1, map.size());
647
648         // Check it2 was broken
649         assertIteratorBroken(it2);
650     }
651
652     @Test
653     public void testImmutableSerialization() throws IOException, ClassNotFoundException {
654         final Map<String, String> source = createMap();
655
656         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
657         try (final ObjectOutputStream oos = new ObjectOutputStream(bos)) {
658             oos.writeObject(source);
659         }
660
661         final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
662         @SuppressWarnings("unchecked")
663         final Map<String, String> result = (Map<String, String>) ois.readObject();
664
665         assertEquals(source, result);
666     }
667 }