BUG-4158: ImmutableOffsetMap should not be Cloneable
[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 Object[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 ImmutableMap);
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         // Iterator order needs to be preserved
75         assertTrue(Iterators.elementsEqual(twoEntryMap.entrySet().iterator(), map.entrySet().iterator()));
76
77         // Should result in the same object
78         assertSame(map, ImmutableOffsetMap.copyOf(map));
79
80         final Map<String, String> mutable = map.toModifiableMap();
81         final Map<String, String> copy = ImmutableOffsetMap.copyOf(mutable);
82
83         assertEquals(mutable, copy);
84         assertEquals(map, copy);
85         assertNotSame(mutable, copy);
86         assertNotSame(map, copy);
87     }
88
89     @Test
90     public void testImmutableSimpleEquals() {
91         final Map<String, String> map = createMap();
92
93         assertTrue(map.equals(map));
94         assertFalse(map.equals(null));
95         assertFalse(map.equals("string"));
96     }
97
98     @Test
99     public void testImmutableCopyConstructor() {
100         final ImmutableOffsetMap<String, String> source = createMap();
101         final ImmutableOffsetMap<String, String> result = new ImmutableOffsetMap<>(source);
102
103         assertSame(source.offsets(), result.offsets());
104         assertSame(source.objects(), result.objects());
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         assertEquals(source, result);
331
332         // Only offsets should be shared
333         assertSame(source.offsets(), result.offsets());
334         assertNotSame(source.objects(), result.objects());
335
336         // Iterator order needs to be preserved
337         assertTrue(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator()));
338     }
339
340     @Test
341     public void testEmptyMutable() throws CloneNotSupportedException {
342         final MutableOffsetMap<String, String> map = new MutableOffsetMap<>();
343         assertTrue(map.isEmpty());
344
345         final Map<String, String> other = map.clone();
346         assertEquals(other, map);
347         assertNotSame(other, map);
348     }
349
350     @Test
351     public void testMutableWithKeyset() {
352         final MutableOffsetMap<String, String> map = new MutableOffsetMap<>(ImmutableSet.of("k1", "k2"));
353         assertTrue(map.isEmpty());
354         assertTrue(map.keySet().isEmpty());
355         assertNull(map.get("k1"));
356         assertNull(map.remove("k2"));
357     }
358
359     @Test
360     public void testMutableToEmpty() {
361         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
362
363         mutable.remove("k1");
364         mutable.remove("k2");
365
366         assertTrue(mutable.isEmpty());
367         assertSame(ImmutableMap.of(), mutable.toUnmodifiableMap());
368     }
369
370     @Test
371     public void testMutableToSingleton() {
372         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
373
374         mutable.remove("k1");
375
376         final Map<String, String> result = mutable.toUnmodifiableMap();
377
378         // Should devolve to a singleton
379         assertTrue(result instanceof ImmutableMap);
380         assertEquals(ImmutableMap.of("k2", "v2"), result);
381     }
382
383     @Test
384     public void testMutableToNewSingleton() {
385         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
386
387         mutable.remove("k1");
388         mutable.put("k3", "v3");
389
390         final Map<String, String> result = mutable.toUnmodifiableMap();
391
392         assertTrue(result instanceof ImmutableOffsetMap);
393         assertEquals(ImmutableMap.of("k2", "v2", "k3", "v3"), result);
394     }
395
396     @Test
397     public void testMutableSize() {
398         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
399         assertEquals(2, mutable.size());
400
401         mutable.put("k3", "v3");
402         assertEquals(3, mutable.size());
403         mutable.remove("k2");
404         assertEquals(2, mutable.size());
405         mutable.put("k1", "new-v1");
406         assertEquals(2, mutable.size());
407     }
408
409     @Test
410     public void testExpansionWithOrder() {
411         final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap();
412
413         mutable.remove("k1");
414         mutable.put("k3", "v3");
415         mutable.put("k1", "v1");
416
417         assertEquals(ImmutableMap.of("k3", "v3"), mutable.newKeys());
418
419         final Map<String, String> result = mutable.toUnmodifiableMap();
420
421         assertTrue(result instanceof ImmutableOffsetMap);
422         assertEquals(threeEntryMap, result);
423         assertEquals(result, threeEntryMap);
424         assertTrue(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator()));
425     }
426
427     @Test
428     public void testReplacedValue() {
429         final ImmutableOffsetMap<String, String> source = createMap();
430         final MutableOffsetMap<String, String> mutable = source.toModifiableMap();
431
432         mutable.put("k1", "replaced");
433
434         final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable.toUnmodifiableMap();
435         final Map<String, String> reference = ImmutableMap.of("k1", "replaced", "k2", "v2");
436
437         assertEquals(reference, result);
438         assertEquals(result, reference);
439         assertSame(source.offsets(), result.offsets());
440         assertNotSame(source.objects(), result.objects());
441     }
442
443     @Test
444     public void testCloneableFlipping() throws CloneNotSupportedException {
445         final MutableOffsetMap<String, String> source = createMap().toModifiableMap();
446
447         // Must clone before mutation
448         assertTrue(source.needClone());
449
450         // Non-existent entry, should not clone
451         source.remove("non-existent");
452         assertTrue(source.needClone());
453
454         // Changes the array, should clone
455         source.remove("k1");
456         assertFalse(source.needClone());
457
458         // Create a clone of the map, which shares the array
459         final MutableOffsetMap<String, String> result = source.clone();
460         assertFalse(source.needClone());
461         assertTrue(result.needClone());
462         assertSame(source.array(), result.array());
463
464         // Changes the array, should clone
465         source.put("k1", "v2");
466         assertFalse(source.needClone());
467         assertTrue(result.needClone());
468
469         // Creates a immutable view, which shares the array
470         final ImmutableOffsetMap<String, String> immutable = (ImmutableOffsetMap<String, String>) source.toUnmodifiableMap();
471         assertTrue(source.needClone());
472         assertSame(source.array(), immutable.objects());
473     }
474
475     @Test
476     public void testMutableEntrySet() {
477         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
478
479         assertTrue(map.entrySet().add(new SimpleEntry<>("k3", "v3")));
480         assertTrue(map.containsKey("k3"));
481         assertEquals("v3", map.get("k3"));
482
483         // null is not an Entry: ignore
484         assertFalse(map.entrySet().remove(null));
485
486         // non-matching value: ignore
487         assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", "other")));
488         assertTrue(map.containsKey("k1"));
489
490         // ignore null values
491         assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", null)));
492         assertTrue(map.containsKey("k1"));
493
494         assertTrue(map.entrySet().remove(new SimpleEntry<>("k1", "v1")));
495         assertFalse(map.containsKey("k1"));
496     }
497
498     private static void assertIteratorBroken(final Iterator<?> it) {
499         try {
500             it.hasNext();
501             fail();
502         } catch (ConcurrentModificationException e) {
503         }
504         try {
505             it.next();
506             fail();
507         } catch (ConcurrentModificationException e) {
508         }
509         try {
510             it.remove();
511             fail();
512         } catch (ConcurrentModificationException e) {
513         }
514     }
515
516     @Test
517     public void testMutableSimpleEquals() {
518         final ImmutableOffsetMap<String, String> source = createMap();
519         final Map<String, String> map = source.toModifiableMap();
520
521         assertTrue(map.equals(map));
522         assertFalse(map.equals(null));
523         assertFalse(map.equals("string"));
524         assertTrue(map.equals(source));
525     }
526
527     @Test
528     public void testMutableIteratorBasics() {
529         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
530         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
531
532         // Not advanced, remove should fail
533         try {
534             it.remove();
535             fail();
536         } catch (IllegalStateException e) {
537         }
538
539         assertTrue(it.hasNext());
540         assertEquals("k1", it.next().getKey());
541         assertTrue(it.hasNext());
542         assertEquals("k2", it.next().getKey());
543         assertFalse(it.hasNext());
544
545         // Check end-of-iteration throw
546         try {
547             it.next();
548             fail();
549         } catch (NoSuchElementException e) {
550         }
551     }
552
553     @Test
554     public void testMutableIteratorWithRemove() {
555         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
556         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
557
558         // Advance one element
559         assertTrue(it.hasNext());
560         assertEquals("k1", it.next().getKey());
561
562         // Remove k1
563         it.remove();
564         assertEquals(1, map.size());
565         assertFalse(map.containsKey("k1"));
566
567         // Iterator should still work
568         assertTrue(it.hasNext());
569         assertEquals("k2", it.next().getKey());
570         assertFalse(it.hasNext());
571     }
572
573     @Test
574     public void testMutableIteratorOffsetReplaceWorks() {
575         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
576         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
577         it.next();
578
579         map.put("k1", "new-v1");
580         assertTrue(it.hasNext());
581     }
582
583     @Test
584     public void testMutableIteratorNewReplaceWorks() {
585         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
586         map.put("k3", "v3");
587         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
588         it.next();
589
590         map.put("k3", "new-v3");
591         assertTrue(it.hasNext());
592     }
593
594     @Test
595     public void testMutableIteratorOffsetAddBreaks() {
596         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
597         map.put("k3", "v3");
598         map.remove("k1");
599
600         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
601         it.next();
602
603         map.put("k1", "new-v1");
604         assertIteratorBroken(it);
605     }
606
607     @Test
608     public void testMutableIteratorNewAddBreaks() {
609         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
610         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
611         it.next();
612
613         map.put("k3", "v3");
614         assertIteratorBroken(it);
615     }
616
617     @Test
618     public void testMutableIteratorOffsetRemoveBreaks() {
619         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
620         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
621         it.next();
622
623         map.remove("k1");
624         assertIteratorBroken(it);
625     }
626
627     @Test
628     public void testMutableIteratorNewRemoveBreaks() {
629         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
630         map.put("k3", "v3");
631         final Iterator<Entry<String, String>> it = map.entrySet().iterator();
632         it.next();
633
634         map.remove("k3");
635         assertIteratorBroken(it);
636     }
637
638     @Test
639     public void testMutableCrossIteratorRemove() {
640         final MutableOffsetMap<String, String> map = createMap().toModifiableMap();
641         final Set<Entry<String, String>> es = map.entrySet();
642         final Iterator<Entry<String, String>> it1 = es.iterator();
643         final Iterator<Entry<String, String>> it2 = es.iterator();
644
645         // Remove k1 via it1
646         it1.next();
647         it2.next();
648         it1.remove();
649         assertEquals(1, map.size());
650
651         // Check it2 was broken
652         assertIteratorBroken(it2);
653     }
654
655     @Test
656     public void testImmutableSerialization() throws IOException, ClassNotFoundException {
657         final Map<String, String> source = createMap();
658
659         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
660         try (final ObjectOutputStream oos = new ObjectOutputStream(bos)) {
661             oos.writeObject(source);
662         }
663
664         final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
665         @SuppressWarnings("unchecked")
666         final Map<String, String> result = (Map<String, String>) ois.readObject();
667
668         assertEquals(source, result);
669     }
670 }