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