package io.akarin.server.misc; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; /** * A thread-safe version of {@link Map} in which all operations that change the * Map are implemented by making a new copy of the underlying Map. *

* While the creation of a new Map can be expensive, this class is designed for * cases in which the primary function is to read data from the Map, not to * modify the Map. Therefore the operations that do not cause a change to this * class happen quickly and concurrently. * * @author Kuzma Deretuke */ public class CopyOnWriteHashMap implements Map, Serializable, Cloneable { private static final long serialVersionUID = 5481095911554321115L; private AtomicReference> internalMap = new AtomicReference>(); /** * Creates a new instance of CopyOnWriteHashMap. */ public CopyOnWriteHashMap() { internalMap.set(new HashMap()); } /** * Creates a new instance of CopyOnWriteHashMap with the specified initial size. * * @param initialCapacity The initial size of the Map. */ public CopyOnWriteHashMap(int initialCapacity) { internalMap.set(new HashMap(initialCapacity)); } /** * Creates a new instance of CopyOnWriteHashMap in which the initial data, * being held by this map, is contained in the supplied map. * * @param data A Map containing the initial contents to be placed into this class. */ public CopyOnWriteHashMap(Map data) { internalMap.set(new HashMap(data)); } @Override public V put(K key, V value) { Map oldMap; Map newMap; V val; do { oldMap = internalMap.get(); newMap = new HashMap(oldMap); val = newMap.put(key, value); } while (!internalMap.compareAndSet(oldMap, newMap)); return val; } @Override public V remove(Object key) { Map oldMap; Map newMap; V val; do { oldMap = internalMap.get(); newMap = new HashMap(oldMap); val = newMap.remove(key); } while (!internalMap.compareAndSet(oldMap, newMap)); return val; } @Override public void putAll(Map newData) { Map oldMap; Map newMap; do { oldMap = internalMap.get(); newMap = new HashMap(oldMap); newMap.putAll(newData); } while (!internalMap.compareAndSet(oldMap, newMap)); } @Override public void clear() { internalMap.set(new HashMap()); } // // Below are methods that do not modify the internal map // @Override public int size() { return internalMap.get().size(); } @Override public boolean isEmpty() { return internalMap.get().isEmpty(); } @Override public boolean containsKey(Object key) { return internalMap.get().containsKey(key); } @Override public boolean containsValue(Object value) { return internalMap.get().containsValue(value); } @Override public V get(Object key) { return internalMap.get().get(key); } @Override public Set keySet() { return internalMap.get().keySet(); } @Override public Collection values() { return internalMap.get().values(); } @Override public Set> entrySet() { return internalMap.get().entrySet(); } @Override public int hashCode() { return internalMap.get().hashCode(); } @Override public boolean equals(Object o) { return internalMap.get().equals(o); } @Override public String toString() { Map map = internalMap.get(); Iterator> i = map.entrySet().iterator(); if (!i.hasNext()) return "{}"; StringBuilder sb = new StringBuilder(); sb.append('{'); for (; ; ) { Entry e = i.next(); K key = e.getKey(); V value = e.getValue(); sb.append(key == this ? "(this Map)" : (key == map ? "(internal Map)" : key)); sb.append('='); sb.append(value == this ? "(this Map)" : (value == map ? "(internal Map)" : value)); if (!i.hasNext()) return sb.append('}').toString(); sb.append(',').append(' '); } } @Override public Object clone() { try { CopyOnWriteHashMap clone = (CopyOnWriteHashMap) super.clone(); clone.internalMap = new AtomicReference>(new HashMap(internalMap.get())); return clone; } catch (CloneNotSupportedException e) { throw new InternalError(); } } }