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