/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.AbstractMap.SimpleEntry; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import org.junit.Before; import org.junit.Test; public class OffsetMapTest { private final Map twoEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2"); private final Map threeEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"); private ImmutableOffsetMap createMap() { return (ImmutableOffsetMap) ImmutableOffsetMap.copyOf(twoEntryMap); } @Before public void setup() { OffsetMapCache.invalidateCache(); } @Test(expected=IllegalArgumentException.class) public void testWrongImmutableConstruction() { new ImmutableOffsetMap.Ordered(Collections.emptyMap(), new String[1]); } @Test public void testCopyEmptyMap() { final Map source = Collections.emptyMap(); final Map result = ImmutableOffsetMap.copyOf(source); assertEquals(source, result); assertTrue(result instanceof ImmutableMap); } @Test public void testCopySingletonMap() { final Map source = Collections.singletonMap("a", "b"); final Map result = ImmutableOffsetMap.copyOf(source); assertEquals(source, result); assertTrue(result instanceof SharedSingletonMap); } @Test public void testCopyMap() { final ImmutableOffsetMap map = createMap(); // Equality in both directions assertEquals(twoEntryMap, map); assertEquals(map, twoEntryMap); // hashcode has to match assertEquals(twoEntryMap.hashCode(), map.hashCode()); // Iterator order needs to be preserved assertTrue(Iterators.elementsEqual(twoEntryMap.entrySet().iterator(), map.entrySet().iterator())); // Should result in the same object assertSame(map, ImmutableOffsetMap.copyOf(map)); final Map mutable = map.toModifiableMap(); final Map copy = ImmutableOffsetMap.copyOf(mutable); assertEquals(mutable, copy); assertEquals(map, copy); assertNotSame(mutable, copy); assertNotSame(map, copy); } @Test public void testImmutableSimpleEquals() { final Map map = createMap(); assertTrue(map.equals(map)); assertFalse(map.equals(null)); assertFalse(map.equals("string")); } @Test public void testImmutableGet() { final Map map = createMap(); assertEquals("v1", map.get("k1")); assertEquals("v2", map.get("k2")); assertNull(map.get("non-existent")); assertNull(map.get(null)); } @Test public void testImmutableGuards() { final Map map = createMap(); try { map.values().add("v1"); fail(); } catch (UnsupportedOperationException e) { } try { map.values().remove("v1"); fail(); } catch (UnsupportedOperationException e) { } try { map.values().clear(); fail(); } catch (UnsupportedOperationException e) { } try { final Iterator it = map.values().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { } try { map.keySet().add("k1"); fail(); } catch (UnsupportedOperationException e) { } try { map.keySet().clear(); fail(); } catch (UnsupportedOperationException e) { } try { map.keySet().remove("k1"); fail(); } catch (UnsupportedOperationException e) { } try { final Iterator it = map.keySet().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { } try { map.entrySet().clear(); fail(); } catch (UnsupportedOperationException e) { } try { map.entrySet().add(new SimpleEntry<>("k1", "v1")); fail(); } catch (UnsupportedOperationException e) { } try { map.entrySet().remove(new SimpleEntry<>("k1", "v1")); fail(); } catch (UnsupportedOperationException e) { } try { final Iterator> it = map.entrySet().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { } try { map.clear(); fail(); } catch (UnsupportedOperationException e) { } try { map.put("k1", "fail"); fail(); } catch (UnsupportedOperationException e) { } try { map.putAll(ImmutableMap.of("k1", "fail")); fail(); } catch (UnsupportedOperationException e) { } try { map.remove("k1"); fail(); } catch (UnsupportedOperationException e) { } } @Test public void testMutableGet() { final Map map = createMap().toModifiableMap(); map.put("k3", "v3"); assertEquals("v1", map.get("k1")); assertEquals("v2", map.get("k2")); assertEquals("v3", map.get("k3")); assertNull(map.get("non-existent")); assertNull(map.get(null)); } @Test public void testImmutableSize() { final Map map = createMap(); assertEquals(2, map.size()); } @Test public void testImmutableIsEmpty() { final Map map = createMap(); assertFalse(map.isEmpty()); } @Test public void testImmutableContains() { final Map map = createMap(); assertTrue(map.containsKey("k1")); assertTrue(map.containsKey("k2")); assertFalse(map.containsKey("non-existent")); assertFalse(map.containsKey(null)); assertTrue(map.containsValue("v1")); assertFalse(map.containsValue("non-existent")); } @Test public void testImmutableEquals() { final Map map = createMap(); assertFalse(map.equals(threeEntryMap)); assertFalse(map.equals(ImmutableMap.of("k1", "v1", "k3", "v3"))); assertFalse(map.equals(ImmutableMap.of("k1", "v1", "k2", "different-value"))); } @Test public void testMutableContains() { final Map map = createMap().toModifiableMap(); map.put("k3", "v3"); assertTrue(map.containsKey("k1")); assertTrue(map.containsKey("k2")); assertTrue(map.containsKey("k3")); assertFalse(map.containsKey("non-existent")); assertFalse(map.containsKey(null)); } @Test public void testtoModifiableMap() { final ImmutableOffsetMap source = createMap(); final Map result = source.toModifiableMap(); // The two maps should be equal, but isolated assertTrue(result instanceof MutableOffsetMap); assertEquals(source, result); assertEquals(result, source); // Quick test for clearing MutableOffsetMap result.clear(); assertEquals(0, result.size()); assertEquals(Collections.emptyMap(), result); // The two maps should differ now assertFalse(source.equals(result)); assertFalse(result.equals(source)); // The source map should still equal the template assertEquals(twoEntryMap, source); assertEquals(source, twoEntryMap); } @Test public void testReusedFields() { final ImmutableOffsetMap source = createMap(); final MutableOffsetMap mutable = source.toModifiableMap(); // Should not affect the result mutable.remove("non-existent"); // Resulting map should be equal, but not the same object final ImmutableOffsetMap result = (ImmutableOffsetMap) mutable.toUnmodifiableMap(); assertNotSame(source, result); assertEquals(source, result); // Internal fields should be reused assertSame(source.offsets(), result.offsets()); assertSame(source.objects(), result.objects()); } @Test public void testReusedOffsets() { final ImmutableOffsetMap source = createMap(); final MutableOffsetMap mutable = source.toModifiableMap(); mutable.remove("k1"); mutable.put("k1", "v1"); final ImmutableOffsetMap result = (ImmutableOffsetMap) mutable.toUnmodifiableMap(); assertEquals(source, result); // Only offsets should be shared assertSame(source.offsets(), result.offsets()); assertNotSame(source.objects(), result.objects()); // Iterator order needs to be preserved assertTrue(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator())); } @Test public void testEmptyMutable() throws CloneNotSupportedException { final MutableOffsetMap map = MutableOffsetMap.of(); assertTrue(map.isEmpty()); final Map other = map.clone(); assertEquals(other, map); assertNotSame(other, map); } @Test public void testMutableToEmpty() { final MutableOffsetMap mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.remove("k2"); assertTrue(mutable.isEmpty()); assertSame(ImmutableMap.of(), mutable.toUnmodifiableMap()); } @Test public void testMutableToSingleton() { final MutableOffsetMap mutable = createMap().toModifiableMap(); mutable.remove("k1"); final Map result = mutable.toUnmodifiableMap(); // Should devolve to a singleton assertTrue(result instanceof ImmutableMap); assertEquals(ImmutableMap.of("k2", "v2"), result); } @Test public void testMutableToNewSingleton() { final MutableOffsetMap mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.put("k3", "v3"); final Map result = mutable.toUnmodifiableMap(); assertTrue(result instanceof ImmutableOffsetMap); assertEquals(ImmutableMap.of("k2", "v2", "k3", "v3"), result); } @Test public void testMutableSize() { final MutableOffsetMap mutable = createMap().toModifiableMap(); assertEquals(2, mutable.size()); mutable.put("k3", "v3"); assertEquals(3, mutable.size()); mutable.remove("k2"); assertEquals(2, mutable.size()); mutable.put("k1", "new-v1"); assertEquals(2, mutable.size()); } @Test public void testExpansionWithOrder() { final MutableOffsetMap mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.put("k3", "v3"); mutable.put("k1", "v1"); assertEquals(ImmutableMap.of("k3", "v3"), mutable.newKeys()); final Map result = mutable.toUnmodifiableMap(); assertTrue(result instanceof ImmutableOffsetMap); assertEquals(threeEntryMap, result); assertEquals(result, threeEntryMap); assertTrue(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator())); } @Test public void testReplacedValue() { final ImmutableOffsetMap source = createMap(); final MutableOffsetMap mutable = source.toModifiableMap(); mutable.put("k1", "replaced"); final ImmutableOffsetMap result = (ImmutableOffsetMap) mutable.toUnmodifiableMap(); final Map reference = ImmutableMap.of("k1", "replaced", "k2", "v2"); assertEquals(reference, result); assertEquals(result, reference); assertSame(source.offsets(), result.offsets()); assertNotSame(source.objects(), result.objects()); } @Test public void testCloneableFlipping() throws CloneNotSupportedException { final MutableOffsetMap source = createMap().toModifiableMap(); // Must clone before mutation assertTrue(source.needClone()); // Non-existent entry, should not clone source.remove("non-existent"); assertTrue(source.needClone()); // Changes the array, should clone source.remove("k1"); assertFalse(source.needClone()); // Create a clone of the map, which shares the array final MutableOffsetMap result = source.clone(); assertFalse(source.needClone()); assertTrue(result.needClone()); assertSame(source.array(), result.array()); // Changes the array, should clone source.put("k1", "v2"); assertFalse(source.needClone()); assertTrue(result.needClone()); // Creates a immutable view, which shares the array final ImmutableOffsetMap immutable = (ImmutableOffsetMap) source.toUnmodifiableMap(); assertTrue(source.needClone()); assertSame(source.array(), immutable.objects()); } @Test public void testMutableEntrySet() { final MutableOffsetMap map = createMap().toModifiableMap(); assertTrue(map.entrySet().add(new SimpleEntry<>("k3", "v3"))); assertTrue(map.containsKey("k3")); assertEquals("v3", map.get("k3")); // null is not an Entry: ignore assertFalse(map.entrySet().remove(null)); // non-matching value: ignore assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", "other"))); assertTrue(map.containsKey("k1")); // ignore null values assertFalse(map.entrySet().remove(new SimpleEntry<>("k1", null))); assertTrue(map.containsKey("k1")); assertTrue(map.entrySet().remove(new SimpleEntry<>("k1", "v1"))); assertFalse(map.containsKey("k1")); } private static void assertIteratorBroken(final Iterator it) { try { it.hasNext(); fail(); } catch (ConcurrentModificationException e) { } try { it.next(); fail(); } catch (ConcurrentModificationException e) { } try { it.remove(); fail(); } catch (ConcurrentModificationException e) { } } @Test public void testMutableSimpleEquals() { final ImmutableOffsetMap source = createMap(); final Map map = source.toModifiableMap(); assertTrue(map.equals(map)); assertFalse(map.equals(null)); assertFalse(map.equals("string")); assertTrue(map.equals(source)); } @Test public void testMutableSimpleHashCode() { final Map map = createMap().toModifiableMap(); assertEquals(twoEntryMap.hashCode(), map.hashCode()); } @Test public void testMutableIteratorBasics() { final MutableOffsetMap map = createMap().toModifiableMap(); final Iterator> it = map.entrySet().iterator(); // Not advanced, remove should fail try { it.remove(); fail(); } catch (IllegalStateException e) { } assertTrue(it.hasNext()); assertEquals("k1", it.next().getKey()); assertTrue(it.hasNext()); assertEquals("k2", it.next().getKey()); assertFalse(it.hasNext()); // Check end-of-iteration throw try { it.next(); fail(); } catch (NoSuchElementException e) { } } @Test public void testMutableIteratorWithRemove() { final MutableOffsetMap map = createMap().toModifiableMap(); final Iterator> it = map.entrySet().iterator(); // Advance one element assertTrue(it.hasNext()); assertEquals("k1", it.next().getKey()); // Remove k1 it.remove(); assertEquals(1, map.size()); assertFalse(map.containsKey("k1")); // Iterator should still work assertTrue(it.hasNext()); assertEquals("k2", it.next().getKey()); assertFalse(it.hasNext()); } @Test public void testMutableIteratorOffsetReplaceWorks() { final MutableOffsetMap map = createMap().toModifiableMap(); final Iterator> it = map.entrySet().iterator(); it.next(); map.put("k1", "new-v1"); assertTrue(it.hasNext()); } @Test public void testMutableIteratorNewReplaceWorks() { final MutableOffsetMap map = createMap().toModifiableMap(); map.put("k3", "v3"); final Iterator> it = map.entrySet().iterator(); it.next(); map.put("k3", "new-v3"); assertTrue(it.hasNext()); } @Test public void testMutableIteratorOffsetAddBreaks() { final MutableOffsetMap map = createMap().toModifiableMap(); map.put("k3", "v3"); map.remove("k1"); final Iterator> it = map.entrySet().iterator(); it.next(); map.put("k1", "new-v1"); assertIteratorBroken(it); } @Test public void testMutableIteratorNewAddBreaks() { final MutableOffsetMap map = createMap().toModifiableMap(); final Iterator> it = map.entrySet().iterator(); it.next(); map.put("k3", "v3"); assertIteratorBroken(it); } @Test public void testMutableIteratorOffsetRemoveBreaks() { final MutableOffsetMap map = createMap().toModifiableMap(); final Iterator> it = map.entrySet().iterator(); it.next(); map.remove("k1"); assertIteratorBroken(it); } @Test public void testMutableIteratorNewRemoveBreaks() { final MutableOffsetMap map = createMap().toModifiableMap(); map.put("k3", "v3"); final Iterator> it = map.entrySet().iterator(); it.next(); map.remove("k3"); assertIteratorBroken(it); } @Test public void testMutableCrossIteratorRemove() { final MutableOffsetMap map = createMap().toModifiableMap(); final Set> es = map.entrySet(); final Iterator> it1 = es.iterator(); final Iterator> it2 = es.iterator(); // Remove k1 via it1 it1.next(); it2.next(); it1.remove(); assertEquals(1, map.size()); // Check it2 was broken assertIteratorBroken(it2); } @Test public void testImmutableSerialization() throws IOException, ClassNotFoundException { final Map source = createMap(); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (final ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(source); } final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); @SuppressWarnings("unchecked") final Map result = (Map) ois.readObject(); assertEquals(source, result); } }