package org.ovirt.engine.core.utils.collections; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A map decorator for providing copies of keys and values upon invocation of accessor methods. With this decorator the * caller will be able to safely update the retrieved objects without worrying that the objects held by the map are * altered (as he will get copies and not references to the objects held by the map). */ public class CopyOnAccessMap<K, V> implements Map<K, V> { private static final int REALLOCATION_FACTOR = 2; private Map<K, V> innerMap; public CopyOnAccessMap(Map<K, V> innerMap) { this.innerMap = innerMap; } @Override public int size() { return innerMap.size(); } @Override public boolean isEmpty() { return innerMap.isEmpty(); } @Override public boolean containsKey(Object key) { return innerMap.containsKey(key); } @Override public boolean containsValue(Object value) { return innerMap.containsValue(value); } @Override public V get(Object key) { return clone(innerMap.get(key)); } @SuppressWarnings("unchecked") private <O> O clone(O originalKey) { // We use an intermediate buffer to hold the serialized form of the // object: byte[] buffer = null; // Serialize the object to an array of bytes: ByteArrayOutputStream bufferOut = null; ObjectOutputStream objectOut = null; try { bufferOut = new ByteArrayOutputStream(512); objectOut = new ObjectOutputStream(bufferOut); objectOut.writeObject(originalKey); buffer = bufferOut.toByteArray(); } catch (IOException exception) { throw new RuntimeException(exception); } finally { if (objectOut != null) { try { objectOut.close(); } catch (IOException exception) { // Ignored. } } } // Create a new instance of the object deserializing it from the // buffer: ByteArrayInputStream bufferIn = null; ObjectInputStream objectIn = null; try { bufferIn = new ByteArrayInputStream(buffer); objectIn = new ObjectInputStream(bufferIn) { @Override protected Class<?> resolveClass(ObjectStreamClass description) throws IOException, ClassNotFoundException { // First try with the context class loader, if that fails // then just call the overridden method: try { return Class.forName(description.getName(), false, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException exception) { return super.resolveClass(description); } } }; return (O) objectIn.readObject(); } catch (IOException | ClassNotFoundException exception) { throw new RuntimeException(exception); } finally { if (objectIn != null) { try { objectIn.close(); } catch (IOException exception) { // Ignored. } } } } @Override public V put(K key, V value) { //The old value is no longer in the map, so no need to protect it from external modifiers, so it is not cloned return innerMap.put(clone(key), clone(value)); } @Override public V remove(Object key) { return innerMap.remove(key); } @Override public void putAll(Map<? extends K, ? extends V> m) { for (Entry<? extends K, ? extends V> entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public void clear() { innerMap.clear(); } @Override public Set<K> keySet() { Set<K> newSet = new HashSet<>(innerMap.size() * REALLOCATION_FACTOR); for (K key : innerMap.keySet()) { newSet.add(clone(key)); } return newSet; } @Override public Collection<V> values() { List<V> newValues = new ArrayList<>(innerMap.size() * REALLOCATION_FACTOR); for (V value : innerMap.values()) { newValues.add(clone(value)); } return newValues; } @Override public Set<Entry<K, V>> entrySet() { Map<K, V> newHashMap = new HashMap<>(innerMap.size() * REALLOCATION_FACTOR); for (Entry<K, V> entry : innerMap.entrySet()) { newHashMap.put(clone(entry.getKey()), clone(entry.getValue())); } return newHashMap.entrySet(); } }