package org.andork.collect; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.AbstractCollection; import java.util.AbstractMap.SimpleEntry; import java.util.AbstractSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.function.Supplier; public class WeakValueMap<K, V> implements Map<K, V> { private class EntryIterator extends HashIterator<Map.Entry<K, V>> { @Override public Map.Entry<K, V> next() { return nextEntry(); } } private class EntrySet extends AbstractSet<Map.Entry<K, V>> { @Override public void clear() { WeakValueMap.this.clear(); } @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry<?, ?> e = (Map.Entry<?, ?>) o; V value = get(e.getKey()); return Objects.equals(value, e.getValue()); } @Override public Iterator<Map.Entry<K, V>> iterator() { return new EntryIterator(); } @Override public boolean remove(Object o) { if (!(o instanceof Map.Entry)) { return false; } Map.Entry<?, ?> e = (Map.Entry<?, ?>) o; V value = get(e.getKey()); if (Objects.equals(value, e.getValue())) { remove(e.getKey()); return true; } return false; } @Override public int size() { return WeakValueMap.this.size(); } } private abstract class HashIterator<T> implements Iterator<T> { private Iterator<Entry<K, WeakReference<V>>> mIterator = m.entrySet().iterator(); private TempEntry lastReturned; private TempEntry nextEntry; HashIterator() { } @Override public boolean hasNext() { while (nextEntry == null) { if (!mIterator.hasNext()) { return false; } Entry<K, WeakReference<V>> next = mIterator.next(); V value = next.getValue().get(); if (value != null) { nextEntry = new TempEntry(next.getKey(), value); } } return true; } /** The common parts of next() across different types of iterators */ protected Entry<K, V> nextEntry() { if (nextEntry == null && !hasNext()) { throw new NoSuchElementException(); } lastReturned = nextEntry; nextEntry = null; return lastReturned; } @Override public void remove() { if (lastReturned == null) { throw new IllegalStateException(); } mIterator.remove(); lastReturned = null; } } private static class KeyedReference<K, V> extends WeakReference<V> { final K key; public KeyedReference(K key, V referent, ReferenceQueue<V> queue) { super(referent, queue); this.key = key; } } private class KeyIterator extends HashIterator<K> { @Override public K next() { return nextEntry().getKey(); } } private class KeySet extends AbstractSet<K> { @Override public void clear() { WeakValueMap.this.clear(); } @Override public boolean contains(Object o) { return containsKey(o); } @Override public Iterator<K> iterator() { return new KeyIterator(); } @Override public boolean remove(Object o) { if (containsKey(o)) { WeakValueMap.this.remove(o); return true; } else { return false; } } @Override public int size() { return WeakValueMap.this.size(); } } @SuppressWarnings("serial") private class TempEntry extends SimpleEntry<K, V> { /** * */ private static final long serialVersionUID = 4687738547274027050L; public TempEntry(K key, V value) { super(key, value); } public TempEntry(Map.Entry<K, V> entry) { super(entry); } @Override public V setValue(V value) { V result = super.setValue(value); put(getKey(), value); return result; } } private class ValueIterator extends HashIterator<V> { @Override public V next() { return nextEntry().getValue(); } } private class Values extends AbstractCollection<V> { @Override public void clear() { WeakValueMap.this.clear(); } @Override public boolean contains(Object o) { return containsValue(o); } @Override public Iterator<V> iterator() { return new ValueIterator(); } @Override public int size() { return WeakValueMap.this.size(); } } public static <K, V> WeakValueMap<K, V> newWeakValueHashMap() { return new WeakValueMap<>(() -> new HashMap<>()); } public static <K, V> WeakValueMap<K, V> newWeakValueLinkedHashMap() { return new WeakValueMap<>(() -> new LinkedHashMap<>()); } public static <K, V> WeakValueMap<K, V> newWeakValueTreeMap() { return new WeakValueMap<>(() -> new TreeMap<>()); } private final Supplier<? extends Map<K, WeakReference<V>>> mapSupplier; private final Map<K, WeakReference<V>> m; private final ReferenceQueue<V> queue = new ReferenceQueue<>(); private transient KeySet keySet = null; private transient Values values; private transient EntrySet entrySet = null; public WeakValueMap(Supplier<? extends Map<K, WeakReference<V>>> mapSupplier) { super(); this.mapSupplier = mapSupplier; this.m = mapSupplier.get(); } @Override public void clear() { // clear out ref queue. We don't need to expunge entries // since table is getting cleared. while (queue.poll() != null) { ; } m.clear(); // Allocation of array may have caused GC, which may have caused // additional entries to go stale. Removing these entries from the // reference queue will make them eligible for reclamation. while (queue.poll() != null) { ; } } @Override public boolean containsKey(Object key) { return m.containsKey(key); } @Override public boolean containsValue(Object value) { for (Object v : values()) { if (Objects.equals(v, value)) { return true; } } return false; } @Override public Set<Entry<K, V>> entrySet() { Set<Entry<K, V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } private void expungeStaleEntries() { for (Reference<? extends V> x; (x = queue.poll()) != null;) { @SuppressWarnings("unchecked") KeyedReference<K, V> r = (KeyedReference<K, V>) x; if (m.get(r.key) == r) { m.remove(r.key); } } } @Override public V get(Object key) { WeakReference<V> ref = m.get(key); return ref == null ? null : ref.get(); } @Override public boolean isEmpty() { expungeStaleEntries(); return m.isEmpty(); } @Override public Set<K> keySet() { Set<K> ks = keySet; return ks != null ? ks : (keySet = new KeySet()); } @Override public V put(K key, V value) { if (value == null) { throw new IllegalArgumentException("value must be non-null"); } V oldValue = get(key); m.put(key, new KeyedReference<K, V>(key, value, queue)); return oldValue; } @Override public void putAll(Map<? extends K, ? extends V> m) { Map<K, WeakReference<V>> tempMap = mapSupplier.get(); for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { if (entry.getValue() == null) { continue; } tempMap.put(entry.getKey(), new KeyedReference<K, V>(entry.getKey(), entry.getValue(), queue)); } this.m.putAll(tempMap); } @Override public V remove(Object key) { WeakReference<V> ref = m.remove(key); return ref == null ? null : ref.get(); } @Override public int size() { expungeStaleEntries(); return m.size(); } @Override public Collection<V> values() { Collection<V> vs = values; return vs != null ? vs : (values = new Values()); } }