package org.ovirt.engine.core.utils.collections; import static org.junit.Assert.assertEquals; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import org.junit.Before; import org.junit.Test; public class CopyOnAccessMapTest { private static final int VALUE2_LENGTH = 50; private static final int VALUE2_WIDTH = 25; private static final int VALUE1_LENGTH = 10; private static final int VALUE1_WIDTH = 5; public static class MyKey implements Serializable { private static final long serialVersionUID = 3675790292994230887L; private String name; private int age; public MyKey() { } @Override public int hashCode() { return Objects.hash( age, name ); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MyKey)) { return false; } MyKey other = (MyKey) obj; return age == other.age && Objects.equals(name, other.name); } public MyKey(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public static class MyValue implements Serializable { private static final long serialVersionUID = 2509582906973648615L; private int width; private int length; public MyValue() { } @Override public int hashCode() { return Objects.hash( length, width ); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof MyValue)) { return false; } MyValue other = (MyValue) obj; return length == other.length && width == other.width; } public MyValue(int width, int length) { super(); this.width = width; this.length = length; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } } private CopyOnAccessMap<MyKey, MyValue> testedMap = null; private MyKey key1; private MyValue value1; private MyKey key2; private MyValue value2; private Map<MyKey, MyValue> innerMap = null; @Before public void setup() { innerMap = new HashMap<>(); testedMap = new CopyOnAccessMap<>(innerMap); key1 = new MyKey("RHEL", 0); value1 = new MyValue(VALUE1_WIDTH, VALUE1_LENGTH); key2 = new MyKey("Fedora", 0); value2 = new MyValue(VALUE2_WIDTH, VALUE2_LENGTH); } /** * Changes the data of value1 after put is used to insert it to the map. If this was a regular HashMap, the data of * the value held by the map would have also changed. */ @Test public void testPut() { testedMap.put(key1, value1); value1.setWidth(10); assertSingleMapEntryUnchanged(); } private void assertSingleMapEntryUnchanged() { MyValue newValue = innerMap.get(key1); assertEquals(VALUE1_WIDTH, newValue.getWidth()); assertEquals(VALUE1_LENGTH, newValue.getLength()); } /** * Getting data from map and changing data on the retrieved object. If this was a hash map - the change would effect * the referenced object held by the map */ @Test public void testGet() { innerMap.put(key1, value1); MyValue newValue = testedMap.get(key1); newValue.setWidth(10); // Changing the data assertSingleMapEntryUnchanged(); } /** * Changes the data of value1 and value2, after putAll is used to put them in the map If this was a regular HashMap, * the data of the value held by the map would change */ @Test public void testPutAll() { Map<MyKey, MyValue> map = new HashMap<>(); map.put(key1, value1); map.put(key2, value2); testedMap.putAll(map); value1.setLength(10); value2.setLength(30); assertEntireMapContentsUnchanged(); } /** * Tests activating the "values" method, changing some of the values, and activating the method again. If this was a * regular hashmap, the 2nd activating of the values method would return values with altered data the map were * effected */ @Test public void testValues() { fillMapValues(); Collection<MyValue> values = testedMap.values(); for (MyValue val : values) { val.setLength(val.getLength() + 5); } assertEntireMapContentsUnchanged(); } @Test public void testKeySet() { fillMapValues(); Set<MyKey> keySet = testedMap.keySet(); // If this was a regular map - updating the keys data would have effected them for (MyKey key : keySet) { key.setAge(key.getAge() + 5); } assertEntireMapContentsUnchanged(); } /** * Tests activating "entrySet" method, changing some values and keys and activating this method again. If this was a * regular map, the keys and values obtained from the 2nd entrySet would contain the altered data. */ @Test public void testEntrySet() { fillMapValues(); Set<Entry<MyKey, MyValue>> entrySet = testedMap.entrySet(); for (Map.Entry<MyKey, MyValue> entry : entrySet) { MyKey key = entry.getKey(); key.setAge(key.getAge() + 5); MyValue value = entry.getValue(); value.setLength(value.getLength() + 5); } assertEntireMapContentsUnchanged(); } public void assertEntireMapContentsUnchanged() { MyValue newVal1 = innerMap.get(key1); MyValue newVal2 = innerMap.get(key2); assertEquals(VALUE1_WIDTH, newVal1.getWidth()); assertEquals(VALUE1_LENGTH, newVal1.getLength()); assertEquals(VALUE2_WIDTH, newVal2.getWidth()); assertEquals(VALUE2_LENGTH, newVal2.getLength()); } private void fillMapValues() { innerMap.put(key1, value1); innerMap.put(key2, value2); } }