/* * 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.Iterables; 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<String, String> twoEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2"); private final Map<String, String> threeEntryMap = ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"); private ImmutableOffsetMap<String, String> createMap() { return (ImmutableOffsetMap<String, String>) ImmutableOffsetMap.orderedCopyOf(twoEntryMap); } private ImmutableOffsetMap<String, String> unorderedMap() { return (ImmutableOffsetMap<String, String>) ImmutableOffsetMap.unorderedCopyOf(twoEntryMap); } @Before public void setup() { OffsetMapCache.invalidateCache(); } @Test(expected = IllegalArgumentException.class) public void testWrongImmutableConstruction() { new ImmutableOffsetMap.Ordered<>(Collections.<String, Integer>emptyMap(), new String[1]); } @Test public void testCopyEmptyMap() { final Map<String, String> source = Collections.emptyMap(); final Map<String, String> result = ImmutableOffsetMap.orderedCopyOf(source); assertEquals(source, result); assertTrue(result instanceof ImmutableMap); } @Test public void testCopySingletonMap() { final Map<String, String> source = Collections.singletonMap("a", "b"); final Map<String, String> result = ImmutableOffsetMap.orderedCopyOf(source); assertEquals(source, result); assertTrue(result instanceof SharedSingletonMap); } @Test public void testCopyMap() { final ImmutableOffsetMap<String, String> 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.orderedCopyOf(map)); final Map<String, String> mutable = map.toModifiableMap(); final Map<String, String> copy = ImmutableOffsetMap.orderedCopyOf(mutable); assertEquals(mutable, copy); assertEquals(map, copy); assertNotSame(mutable, copy); assertNotSame(map, copy); } @Test public void testImmutableSimpleEquals() { final Map<String, String> map = createMap(); assertTrue(map.equals(map)); assertFalse(map.equals(null)); assertFalse(map.equals("string")); } @Test public void testImmutableGet() { final Map<String, String> 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<String, String> map = createMap(); try { map.values().add("v1"); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.values().remove("v1"); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.values().clear(); fail(); } catch (UnsupportedOperationException e) { // OK } try { final Iterator<String> it = map.values().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.keySet().add("k1"); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.keySet().clear(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.keySet().remove("k1"); fail(); } catch (UnsupportedOperationException e) { // OK } try { final Iterator<String> it = map.keySet().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.entrySet().clear(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.entrySet().add(new SimpleEntry<>("k1", "v1")); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.entrySet().remove(new SimpleEntry<>("k1", "v1")); fail(); } catch (UnsupportedOperationException e) { // OK } try { final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); it.remove(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.clear(); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.put("k1", "fail"); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.putAll(ImmutableMap.of("k1", "fail")); fail(); } catch (UnsupportedOperationException e) { // OK } try { map.remove("k1"); fail(); } catch (UnsupportedOperationException e) { // OK } } @Test public void testMutableGet() { final Map<String, String> 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<String, String> map = createMap(); assertEquals(2, map.size()); } @Test public void testImmutableIsEmpty() { final Map<String, String> map = createMap(); assertFalse(map.isEmpty()); } @Test public void testImmutableContains() { final Map<String, String> 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<String, String> 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<String, String> 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<String, String> source = createMap(); final Map<String, String> 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<String, String> source = createMap(); final MutableOffsetMap<String, String> mutable = source.toModifiableMap(); // Should not affect the result mutable.remove("non-existent"); // Resulting map should be equal, but not the same object final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) 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<String, String> source = createMap(); final MutableOffsetMap<String, String> mutable = source.toModifiableMap(); mutable.remove("k1"); mutable.put("k1", "v1"); final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable .toUnmodifiableMap(); assertTrue(source.equals(result)); assertTrue(result.equals(source)); // Iterator order must not be preserved assertFalse(Iterators.elementsEqual(source.entrySet().iterator(), result.entrySet().iterator())); } @Test public void testReusedOffsetsUnordered() { final ImmutableOffsetMap<String, String> source = unorderedMap(); final MutableOffsetMap<String, String> mutable = source.toModifiableMap(); mutable.remove("k1"); mutable.put("k1", "v1"); final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) 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<String, String> map = MutableOffsetMap.ordered(); assertTrue(map.isEmpty()); final Map<String, String> other = map.clone(); assertEquals(other, map); assertNotSame(other, map); } @Test public void testMutableToEmpty() { final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.remove("k2"); assertTrue(mutable.isEmpty()); assertSame(ImmutableMap.of(), mutable.toUnmodifiableMap()); } @Test public void testMutableToSingleton() { final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap(); mutable.remove("k1"); final Map<String, String> result = mutable.toUnmodifiableMap(); // Should devolve to a singleton assertTrue(result instanceof SharedSingletonMap); assertEquals(ImmutableMap.of("k2", "v2"), result); } @Test public void testMutableToNewSingleton() { final MutableOffsetMap<String, String> mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.put("k3", "v3"); final Map<String, String> result = mutable.toUnmodifiableMap(); assertTrue(result instanceof ImmutableOffsetMap); assertEquals(ImmutableMap.of("k2", "v2", "k3", "v3"), result); } @Test public void testMutableSize() { final MutableOffsetMap<String, String> 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<String, String> mutable = createMap().toModifiableMap(); mutable.remove("k1"); mutable.put("k3", "v3"); mutable.put("k1", "v1"); assertEquals(ImmutableMap.of("k1", "v1", "k3", "v3"), mutable.newKeys()); final Map<String, String> result = mutable.toUnmodifiableMap(); assertTrue(result instanceof ImmutableOffsetMap); assertEquals(threeEntryMap, result); assertEquals(result, threeEntryMap); assertFalse(Iterators.elementsEqual(threeEntryMap.entrySet().iterator(), result.entrySet().iterator())); } @Test public void testExpansionWithoutOrder() { final MutableOffsetMap<String, String> mutable = unorderedMap().toModifiableMap(); mutable.remove("k1"); mutable.put("k3", "v3"); mutable.put("k1", "v1"); assertEquals(ImmutableMap.of("k3", "v3"), mutable.newKeys()); final Map<String, String> 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<String, String> source = createMap(); final MutableOffsetMap<String, String> mutable = source.toModifiableMap(); mutable.put("k1", "replaced"); final ImmutableOffsetMap<String, String> result = (ImmutableOffsetMap<String, String>) mutable .toUnmodifiableMap(); final Map<String, String> 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<String, String> 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<String, String> 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()); // Forced copy, no cloning needed, but maps are equal final ImmutableOffsetMap<String, String> immutable = (ImmutableOffsetMap<String, String>) source .toUnmodifiableMap(); assertFalse(source.needClone()); assertTrue(source.equals(immutable)); assertTrue(immutable.equals(source)); assertTrue(Iterables.elementsEqual(source.entrySet(), immutable.entrySet())); } @Test public void testCloneableFlippingUnordered() throws CloneNotSupportedException { final MutableOffsetMap<String, String> source = unorderedMap().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<String, String> 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<String, String> immutable = (ImmutableOffsetMap<String, String>) source .toUnmodifiableMap(); assertTrue(source.needClone()); assertSame(source.array(), immutable.objects()); } @Test public void testMutableEntrySet() { final MutableOffsetMap<String, String> 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) { // OK } try { it.next(); fail(); } catch (ConcurrentModificationException e) { // OK } try { it.remove(); fail(); } catch (ConcurrentModificationException e) { // OK } } @Test public void testMutableSimpleEquals() { final ImmutableOffsetMap<String, String> source = createMap(); final Map<String, String> 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<String, String> map = createMap().toModifiableMap(); assertEquals(twoEntryMap.hashCode(), map.hashCode()); } @Test public void testMutableIteratorBasics() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); // Not advanced, remove should fail try { it.remove(); fail(); } catch (IllegalStateException e) { // OK } 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) { // OK } } @Test public void testMutableIteratorWithRemove() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); final Iterator<Entry<String, String>> 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<String, String> map = createMap().toModifiableMap(); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.put("k1", "new-v1"); assertTrue(it.hasNext()); } @Test public void testMutableIteratorNewReplaceWorks() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); map.put("k3", "v3"); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.put("k3", "new-v3"); assertTrue(it.hasNext()); } @Test public void testMutableIteratorOffsetAddBreaks() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); map.put("k3", "v3"); map.remove("k1"); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.put("k1", "new-v1"); assertIteratorBroken(it); } @Test public void testMutableIteratorNewAddBreaks() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.put("k3", "v3"); assertIteratorBroken(it); } @Test public void testMutableIteratorOffsetRemoveBreaks() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.remove("k1"); assertIteratorBroken(it); } @Test public void testMutableIteratorNewRemoveBreaks() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); map.put("k3", "v3"); final Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.next(); map.remove("k3"); assertIteratorBroken(it); } @Test public void testMutableCrossIteratorRemove() { final MutableOffsetMap<String, String> map = createMap().toModifiableMap(); final Set<Entry<String, String>> es = map.entrySet(); final Iterator<Entry<String, String>> it1 = es.iterator(); final Iterator<Entry<String, String>> 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<String, String> 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<String, String> result = (Map<String, String>) ois.readObject(); assertEquals(source, result); } }