package co.codewizards.cloudstore.core.collection; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import static co.codewizards.cloudstore.core.util.Util.*; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import co.codewizards.cloudstore.core.ref.IdentityWeakReference; public class WeakIdentityHashMap<K, V> implements Map<K, V>, Serializable { private static final long serialVersionUID = 1L; private final ReferenceQueue<K> keyRefQueue = new ReferenceQueue<K>(); private final HashMap<Reference<K>, V> delegate; private transient Set<Map.Entry<K, V>> entrySet; public WeakIdentityHashMap() { delegate = new HashMap<>(); } public WeakIdentityHashMap(int initialCapacity) { delegate = new HashMap<>(initialCapacity); } public WeakIdentityHashMap(final Map<? extends K, ? extends V> map) { this(assertNotNull(map, "map").size()); putAll(map); } public WeakIdentityHashMap(int initialCapacity, float loadFactor) { delegate = new HashMap<>(initialCapacity, loadFactor); } @Override public V get(final Object key) { expunge(); @SuppressWarnings("unchecked") final WeakReference<K> keyRef = createReference((K) key); return delegate.get(keyRef); } @Override public V put(final K key, final V value) { expunge(); assertNotNull(key, "key"); final WeakReference<K> keyRef = createReference(key, keyRefQueue); return delegate.put(keyRef, value); } @Override public void putAll(final Map<? extends K, ? extends V> map) { expunge(); assertNotNull(map, "map"); for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { final K key = entry.getKey(); assertNotNull(key, "entry.key"); final WeakReference<K> keyRef = createReference(key, keyRefQueue); delegate.put(keyRef, entry.getValue()); } } @Override public V remove(final Object key) { expunge(); @SuppressWarnings("unchecked") final WeakReference<K> keyref = createReference((K) key); return delegate.remove(keyref); } @Override public void clear() { expunge(); delegate.clear(); } @Override public int size() { expunge(); return delegate.size(); } @Override public boolean isEmpty() { expunge(); return delegate.isEmpty(); } @Override public boolean containsKey(final Object key) { expunge(); @SuppressWarnings("unchecked") final WeakReference<K> keyRef = createReference((K) key); return delegate.containsKey(keyRef); } @Override public boolean containsValue(final Object value) { expunge(); return delegate.containsValue(value); } @Override public Set<K> keySet() { expunge(); throw new UnsupportedOperationException("NYI"); // TODO implement this! It should be backed! Read javadoc for the proper contract! } @Override public Collection<V> values() { expunge(); return delegate.values(); } @Override public Set<Map.Entry<K,V>> entrySet() { expunge(); Set<Map.Entry<K,V>> es = entrySet; if (es != null) return es; else return entrySet = new EntrySet(); } private class EntrySet extends AbstractSet<Map.Entry<K, V>> { @Override public Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } @Override public boolean contains(Object o) { if (! (o instanceof Map.Entry)) return false; @SuppressWarnings("unchecked") final Map.Entry<K, V> entry = (Map.Entry<K, V>)o; final K keyParam = entry.getKey(); if (! WeakIdentityHashMap.this.containsKey(keyParam)) return false; final V value = WeakIdentityHashMap.this.get(keyParam); return equal(value, entry.getValue()); } @Override public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; @SuppressWarnings("unchecked") final Map.Entry<K, V> entry = (Map.Entry<K, V>)o; final K keyParam = entry.getKey(); if (! WeakIdentityHashMap.this.containsKey(keyParam)) return false; WeakIdentityHashMap.this.remove(keyParam); return true; } @Override public int size() { return WeakIdentityHashMap.this.size(); } @Override public void clear() { WeakIdentityHashMap.this.clear(); } /* * Must revert from AbstractSet's impl to AbstractCollection's, as * the former contains an optimization that results in incorrect * behavior when c is a smaller "normal" (non-identity-based) Set. */ @Override public boolean removeAll(Collection<?> c) { boolean modified = false; for (Iterator<Map.Entry<K,V>> i = iterator(); i.hasNext(); ) { if (c.contains(i.next())) { i.remove(); modified = true; } } return modified; } @Override public Object[] toArray() { int size = size(); Object[] result = new Object[size]; Iterator<Map.Entry<K,V>> it = iterator(); for (int i = 0; i < size; i++) result[i] = new AbstractMap.SimpleEntry<>(it.next()); return result; } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { int size = size(); if (a.length < size) a = (T[]) Array.newInstance(a.getClass().getComponentType(), size); Iterator<Map.Entry<K,V>> it = iterator(); for (int i = 0; i < size; i++) a[i] = (T) new AbstractMap.SimpleEntry<>(it.next()); if (a.length > size) a[size] = null; return a; } } private class EntryIterator implements Iterator<Map.Entry<K, V>> { private final Iterator<Map.Entry<Reference<K>, V>> delegateIterator = delegate.entrySet().iterator(); private Map.Entry<K, V> nextEntry; @Override public boolean hasNext() { if (nextEntry != null) return true; nextEntry = pullNext(); return nextEntry != null; } @Override public Map.Entry<K, V> next() throws NoSuchElementException { Map.Entry<K, V> result = nextEntry; nextEntry = null; if (result == null) { result = pullNext(); if (result == null) throw new NoSuchElementException(); } return result; } private Map.Entry<K, V> pullNext() { while (delegateIterator.hasNext()) { final Map.Entry<Reference<K>, V> delegateNext = delegateIterator.next(); final K key = delegateNext.getKey().get(); if (key != null) { final V value = delegateNext.getValue(); return new AbstractMap.SimpleEntry<K, V>(key, value) { private static final long serialVersionUID = 1L; @Override public V setValue(V value) { return delegateNext.setValue(value); } }; } } return null; } @Override public void remove() throws IllegalStateException { delegateIterator.remove(); } } private void expunge() { Reference<? extends K> keyRef; while ((keyRef = keyRefQueue.poll()) != null) delegate.remove(keyRef); } private WeakReference<K> createReference(K referent) { return new IdentityWeakReference<K>(referent); } private WeakReference<K> createReference(K referent, ReferenceQueue<K> q) { return new IdentityWeakReference<K>(referent, q); } }