2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.util;
10 import static org.junit.jupiter.api.Assertions.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertFalse;
12 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
13 import static org.junit.jupiter.api.Assertions.assertNotEquals;
14 import static org.junit.jupiter.api.Assertions.assertNotSame;
15 import static org.junit.jupiter.api.Assertions.assertNull;
16 import static org.junit.jupiter.api.Assertions.assertSame;
17 import static org.junit.jupiter.api.Assertions.assertThrows;
18 import static org.junit.jupiter.api.Assertions.assertTrue;
20 import com.google.common.collect.ImmutableMap;
21 import com.google.common.collect.Iterables;
22 import com.google.common.collect.Iterators;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.util.AbstractMap.SimpleEntry;
29 import java.util.ConcurrentModificationException;
30 import java.util.Iterator;
32 import java.util.NoSuchElementException;
33 import org.junit.jupiter.api.BeforeEach;
34 import org.junit.jupiter.api.Test;
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");
40 private ImmutableOffsetMap<String, String> createMap() {
41 return (ImmutableOffsetMap<String, String>) ImmutableOffsetMap.orderedCopyOf(twoEntryMap);
44 private ImmutableOffsetMap<String, String> unorderedMap() {
45 return (ImmutableOffsetMap<String, String>) ImmutableOffsetMap.unorderedCopyOf(twoEntryMap);
50 OffsetMapCache.invalidateCache();
53 public void testWrongImmutableConstruction() {
54 assertThrows(IllegalArgumentException.class,
55 () -> new ImmutableOffsetMap.Ordered<>(ImmutableMap.of(), new String[1]));
59 void testCopyEmptyMap() {
60 final var source = Map.of();
61 final var result = ImmutableOffsetMap.orderedCopyOf(source);
63 assertEquals(source, result);
64 assertInstanceOf(ImmutableMap.class, result);
68 void testCopySingletonMap() {
69 final var source = Map.of("a", "b");
70 final var result = ImmutableOffsetMap.orderedCopyOf(source);
72 assertEquals(source, result);
73 assertInstanceOf(SharedSingletonMap.class, result);
78 final var map = createMap();
80 // Equality in both directions
81 assertEquals(twoEntryMap, map);
82 assertEquals(map, twoEntryMap);
84 // hashcode has to match
85 assertEquals(twoEntryMap.hashCode(), map.hashCode());
87 // Iterator order needs to be preserved
88 assertTrue(Iterators.elementsEqual(twoEntryMap.entrySet().iterator(), map.entrySet().iterator()));
90 // Should result in the same object
91 assertSame(map, ImmutableOffsetMap.orderedCopyOf(map));
93 final var mutable = map.toModifiableMap();
94 final var copy = ImmutableOffsetMap.orderedCopyOf(mutable);
96 assertEquals(mutable, copy);
97 assertEquals(map, copy);
98 assertNotSame(mutable, copy);
99 assertNotSame(map, copy);
103 void testImmutableSimpleEquals() {
104 final var map = createMap();
106 assertEquals(map, map);
107 assertNotEquals(null, map);
108 assertNotEquals("string", map);
112 void testImmutableGet() {
113 final var map = createMap();
115 assertEquals("v1", map.get("k1"));
116 assertEquals("v2", map.get("k2"));
117 assertNull(map.get("non-existent"));
118 assertNull(map.get(null));
122 void testImmutableGuards() {
123 final var map = createMap();
125 final var values = map.values();
126 assertThrows(UnsupportedOperationException.class, () -> values.add("v1"));
127 assertThrows(UnsupportedOperationException.class, () -> values.remove("v1"));
128 assertThrows(UnsupportedOperationException.class, () -> values.clear());
130 final var vit = values.iterator();
132 assertThrows(UnsupportedOperationException.class, () -> vit.remove());
134 final var keySet = map.keySet();
135 assertThrows(UnsupportedOperationException.class, () -> keySet.add("k1"));
136 assertThrows(UnsupportedOperationException.class, () -> keySet.clear());
137 assertThrows(UnsupportedOperationException.class, () -> keySet.remove("k1"));
139 final var kit = keySet.iterator();
141 assertThrows(UnsupportedOperationException.class, () -> kit.remove());
143 final var entrySet = map.entrySet();
144 assertThrows(UnsupportedOperationException.class, () -> entrySet.clear());
145 assertThrows(UnsupportedOperationException.class, () -> entrySet.add(new SimpleEntry<>("k1", "v1")));
146 assertThrows(UnsupportedOperationException.class, () -> entrySet.remove(new SimpleEntry<>("k1", "v1")));
148 final var eit = entrySet.iterator();
150 assertThrows(UnsupportedOperationException.class, () -> eit.remove());
152 assertThrows(UnsupportedOperationException.class, () -> map.clear());
153 assertThrows(UnsupportedOperationException.class, () -> map.put("k1", "fail"));
154 assertThrows(UnsupportedOperationException.class, () -> map.putAll(ImmutableMap.of("k1", "fail")));
155 assertThrows(UnsupportedOperationException.class, () -> map.remove("k1"));
159 void testMutableGet() {
160 final var map = createMap().toModifiableMap();
163 assertEquals("v1", map.get("k1"));
164 assertEquals("v2", map.get("k2"));
165 assertEquals("v3", map.get("k3"));
166 assertNull(map.get("non-existent"));
167 assertNull(map.get(null));
171 void testImmutableSize() {
172 final var map = createMap();
173 assertEquals(2, map.size());
177 void testImmutableIsEmpty() {
178 final var map = createMap();
179 assertFalse(map.isEmpty());
183 void testImmutableContains() {
184 final var map = createMap();
185 assertTrue(map.containsKey("k1"));
186 assertTrue(map.containsKey("k2"));
187 assertFalse(map.containsKey("non-existent"));
188 assertFalse(map.containsKey(null));
189 assertTrue(map.containsValue("v1"));
190 assertFalse(map.containsValue("non-existent"));
194 void testImmutableEquals() {
195 final var map = createMap();
197 assertNotEquals(map, threeEntryMap);
198 assertNotEquals(map, ImmutableMap.of("k1", "v1", "k3", "v3"));
199 assertNotEquals(map, ImmutableMap.of("k1", "v1", "k2", "different-value"));
203 void testMutableContains() {
204 final var map = createMap().toModifiableMap();
206 assertTrue(map.containsKey("k1"));
207 assertTrue(map.containsKey("k2"));
208 assertTrue(map.containsKey("k3"));
209 assertFalse(map.containsKey("non-existent"));
210 assertFalse(map.containsKey(null));
214 void testtoModifiableMap() {
215 final var source = createMap();
216 final var result = source.toModifiableMap();
218 // The two maps should be equal, but isolated
219 assertInstanceOf(MutableOffsetMap.class, result);
220 assertEquals(source, result);
221 assertEquals(result, source);
223 // Quick test for clearing MutableOffsetMap
225 assertEquals(0, result.size());
226 assertEquals(Map.of(), result);
228 // The two maps should differ now
229 assertNotEquals(source, result);
230 assertNotEquals(result, source);
232 // The source map should still equal the template
233 assertEquals(twoEntryMap, source);
234 assertEquals(source, twoEntryMap);
238 void testReusedFields() {
239 final var source = createMap();
240 final var mutable = source.toModifiableMap();
242 // Should not affect the result
243 mutable.remove("non-existent");
245 // Resulting map should be equal, but not the same object
246 final var result = (ImmutableOffsetMap<String, String>) mutable
247 .toUnmodifiableMap();
248 assertNotSame(source, result);
249 assertEquals(source, result);
251 // Internal fields should be reused
252 assertSame(source.offsets(), result.offsets());
253 assertSame(source.objects(), result.objects());
257 void testReusedOffsets() {
258 final var source = createMap();
259 final var mutable = source.toModifiableMap();
261 mutable.remove("k1");
262 mutable.put("k1", "v1");
264 final var result = (ImmutableOffsetMap<String, String>) mutable
265 .toUnmodifiableMap();
266 assertEquals(source, result);
267 assertEquals(result, source);
269 // Iterator order must not be preserved
270 assertFalse(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator()));
274 void testReusedOffsetsUnordered() {
275 final var source = unorderedMap();
276 final var mutable = source.toModifiableMap();
278 mutable.remove("k1");
279 mutable.put("k1", "v1");
281 final var result = (ImmutableOffsetMap<String, String>) mutable
282 .toUnmodifiableMap();
283 assertEquals(source, result);
285 // Only offsets should be shared
286 assertSame(source.offsets(), result.offsets());
287 assertNotSame(source.objects(), result.objects());
289 // Iterator order needs to be preserved
290 assertTrue(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator()));
294 void testEmptyMutable() throws CloneNotSupportedException {
295 final var map = MutableOffsetMap.ordered();
296 assertTrue(map.isEmpty());
298 final var other = map.clone();
299 assertEquals(other, map);
300 assertNotSame(other, map);
304 void testMutableToEmpty() {
305 final var mutable = createMap().toModifiableMap();
307 mutable.remove("k1");
308 mutable.remove("k2");
310 assertTrue(mutable.isEmpty());
311 assertSame(ImmutableMap.of(), mutable.toUnmodifiableMap());
315 void testMutableToSingleton() {
316 final var mutable = createMap().toModifiableMap();
318 mutable.remove("k1");
320 final var result = mutable.toUnmodifiableMap();
322 // Should devolve to a singleton
323 assertInstanceOf(SharedSingletonMap.class, result);
324 assertEquals(ImmutableMap.of("k2", "v2"), result);
328 void testMutableToNewSingleton() {
329 final var mutable = createMap().toModifiableMap();
331 mutable.remove("k1");
332 mutable.put("k3", "v3");
334 final var result = mutable.toUnmodifiableMap();
336 assertInstanceOf(ImmutableOffsetMap.class, result);
337 assertEquals(ImmutableMap.of("k2", "v2", "k3", "v3"), result);
341 void testMutableSize() {
342 final var mutable = createMap().toModifiableMap();
343 assertEquals(2, mutable.size());
345 mutable.put("k3", "v3");
346 assertEquals(3, mutable.size());
347 mutable.remove("k2");
348 assertEquals(2, mutable.size());
349 mutable.put("k1", "new-v1");
350 assertEquals(2, mutable.size());
354 void testExpansionWithOrder() {
355 final var mutable = createMap().toModifiableMap();
357 mutable.remove("k1");
358 mutable.put("k3", "v3");
359 mutable.put("k1", "v1");
361 assertEquals(ImmutableMap.of("k1", "v1", "k3", "v3"), mutable.newKeys());
363 final var result = mutable.toUnmodifiableMap();
365 assertInstanceOf(ImmutableOffsetMap.class, result);
366 assertEquals(threeEntryMap, result);
367 assertEquals(result, threeEntryMap);
368 assertFalse(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator()));
372 void testExpansionWithoutOrder() {
373 final var mutable = unorderedMap().toModifiableMap();
375 mutable.remove("k1");
376 mutable.put("k3", "v3");
377 mutable.put("k1", "v1");
379 assertEquals(ImmutableMap.of("k3", "v3"), mutable.newKeys());
381 final var result = mutable.toUnmodifiableMap();
383 assertInstanceOf(ImmutableOffsetMap.class, result);
384 assertEquals(threeEntryMap, result);
385 assertEquals(result, threeEntryMap);
386 assertTrue(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator()));
390 void testReplacedValue() {
391 final var source = createMap();
392 final var mutable = source.toModifiableMap();
394 mutable.put("k1", "replaced");
396 final var result = (ImmutableOffsetMap<String, String>) mutable
397 .toUnmodifiableMap();
398 final var reference = ImmutableMap.of("k1", "replaced", "k2", "v2");
400 assertEquals(reference, result);
401 assertEquals(result, reference);
402 assertSame(source.offsets(), result.offsets());
403 assertNotSame(source.objects(), result.objects());
407 void testCloneableFlipping() throws CloneNotSupportedException {
408 final var source = createMap().toModifiableMap();
410 // Must clone before mutation
411 assertTrue(source.needClone());
413 // Non-existent entry, should not clone
414 source.remove("non-existent");
415 assertTrue(source.needClone());
417 // Changes the array, should clone
419 assertFalse(source.needClone());
421 // Create a clone of the map, which shares the array
422 final var result = source.clone();
423 assertFalse(source.needClone());
424 assertTrue(result.needClone());
425 assertSame(source.array(), result.array());
427 // Changes the array, should clone
428 source.put("k1", "v2");
429 assertFalse(source.needClone());
430 assertTrue(result.needClone());
432 // Forced copy, no cloning needed, but maps are equal
433 final var immutable = (ImmutableOffsetMap<String, String>) source
434 .toUnmodifiableMap();
435 assertFalse(source.needClone());
436 assertEquals(source, immutable);
437 assertEquals(immutable, source);
438 assertTrue(Iterables.elementsEqual(source.entrySet(), immutable.entrySet()));
442 void testCloneableFlippingUnordered() throws CloneNotSupportedException {
443 final var source = unorderedMap().toModifiableMap();
445 // Must clone before mutation
446 assertTrue(source.needClone());
448 // Non-existent entry, should not clone
449 source.remove("non-existent");
450 assertTrue(source.needClone());
452 // Changes the array, should clone
454 assertFalse(source.needClone());
456 // Create a clone of the map, which shares the array
457 final var result = source.clone();
458 assertFalse(source.needClone());
459 assertTrue(result.needClone());
460 assertSame(source.array(), result.array());
462 // Changes the array, should clone
463 source.put("k1", "v2");
464 assertFalse(source.needClone());
465 assertTrue(result.needClone());
467 // Creates a immutable view, which shares the array
468 final var immutable = (ImmutableOffsetMap<String, String>) source.toUnmodifiableMap();
469 assertTrue(source.needClone());
470 assertSame(source.array(), immutable.objects());
474 void testMutableEntrySet() {
475 final var map = createMap().toModifiableMap();
477 assertTrue(map.entrySet().add(new SimpleEntry<>("k3", "v3")));
478 assertTrue(map.containsKey("k3"));
479 assertEquals("v3", map.get("k3"));
481 // null is not an Entry: ignore
482 assertFalse(map.entrySet().remove(null));
484 // non-matching value: ignore
485 assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", "other")));
486 assertTrue(map.containsKey("k1"));
488 // ignore null values
489 assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", null)));
490 assertTrue(map.containsKey("k1"));
492 assertTrue(map.entrySet().remove(new SimpleEntry<>("k1", "v1")));
493 assertFalse(map.containsKey("k1"));
496 private static void assertIteratorBroken(final Iterator<?> it) {
497 assertThrows(ConcurrentModificationException.class, () -> it.hasNext());
498 assertThrows(ConcurrentModificationException.class, () -> it.next());
499 assertThrows(ConcurrentModificationException.class, () -> it.remove());
503 void testMutableSimpleEquals() {
504 final var source = createMap();
505 final var map = source.toModifiableMap();
507 assertEquals(map, map);
508 assertNotEquals(null, map);
509 assertNotEquals("string", map);
510 assertEquals(map, source);
514 void testMutableSimpleHashCode() {
515 final var map = createMap().toModifiableMap();
517 assertEquals(twoEntryMap.hashCode(), map.hashCode());
521 void testMutableIteratorBasics() {
522 final var map = createMap().toModifiableMap();
523 final var it = map.entrySet().iterator();
525 // Not advanced, remove should fail
526 assertThrows(IllegalStateException.class, () -> it.remove());
528 assertTrue(it.hasNext());
529 assertEquals("k1", it.next().getKey());
530 assertTrue(it.hasNext());
531 assertEquals("k2", it.next().getKey());
532 assertFalse(it.hasNext());
534 // Check end-of-iteration throw
535 assertThrows(NoSuchElementException.class, () -> it.next());
539 void testMutableIteratorWithRemove() {
540 final var map = createMap().toModifiableMap();
541 final var it = map.entrySet().iterator();
543 // Advance one element
544 assertTrue(it.hasNext());
545 assertEquals("k1", it.next().getKey());
549 assertEquals(1, map.size());
550 assertFalse(map.containsKey("k1"));
552 // Iterator should still work
553 assertTrue(it.hasNext());
554 assertEquals("k2", it.next().getKey());
555 assertFalse(it.hasNext());
559 void testMutableIteratorOffsetReplaceWorks() {
560 final var map = createMap().toModifiableMap();
561 final var it = map.entrySet().iterator();
564 map.put("k1", "new-v1");
565 assertTrue(it.hasNext());
569 void testMutableIteratorNewReplaceWorks() {
570 final var map = createMap().toModifiableMap();
572 final var it = map.entrySet().iterator();
575 map.put("k3", "new-v3");
576 assertTrue(it.hasNext());
580 void testMutableIteratorOffsetAddBreaks() {
581 final var map = createMap().toModifiableMap();
585 final var it = map.entrySet().iterator();
588 map.put("k1", "new-v1");
589 assertIteratorBroken(it);
593 void testMutableIteratorNewAddBreaks() {
594 final var map = createMap().toModifiableMap();
595 final var it = map.entrySet().iterator();
599 assertIteratorBroken(it);
603 void testMutableIteratorOffsetRemoveBreaks() {
604 final var map = createMap().toModifiableMap();
605 final var it = map.entrySet().iterator();
609 assertIteratorBroken(it);
613 void testMutableIteratorNewRemoveBreaks() {
614 final var map = createMap().toModifiableMap();
616 final var it = map.entrySet().iterator();
620 assertIteratorBroken(it);
624 void testMutableCrossIteratorRemove() {
625 final var map = createMap().toModifiableMap();
626 final var es = map.entrySet();
627 final var it1 = es.iterator();
628 final var it2 = es.iterator();
634 assertEquals(1, map.size());
636 // Check it2 was broken
637 assertIteratorBroken(it2);
641 void testImmutableSerialization() throws IOException, ClassNotFoundException {
642 final var source = createMap();
644 final var bos = new ByteArrayOutputStream();
645 try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
646 oos.writeObject(source);
649 final var ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
650 @SuppressWarnings("unchecked")
651 final var result = (Map<String, String>) ois.readObject();
653 assertEquals(source, result);