package com.hwlcn.security.util; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; public class SoftHashMap<K, V> implements Map<K, V> { private static final int DEFAULT_RETENTION_SIZE = 100; private final Map<K, SoftValue<V, K>> map; private final int RETENTION_SIZE; private final Queue<V> strongReferences; //guarded by 'strongReferencesLock' private final ReentrantLock strongReferencesLock; private final ReferenceQueue<? super V> queue; public SoftHashMap() { this(DEFAULT_RETENTION_SIZE); } @SuppressWarnings({"unchecked"}) public SoftHashMap(int retentionSize) { super(); RETENTION_SIZE = Math.max(0, retentionSize); queue = new ReferenceQueue<V>(); strongReferencesLock = new ReentrantLock(); map = new ConcurrentHashMap<K, SoftValue<V, K>>(); strongReferences = new ConcurrentLinkedQueue<V>(); } public SoftHashMap(Map<K, V> source) { this(DEFAULT_RETENTION_SIZE); putAll(source); } public SoftHashMap(Map<K, V> source, int retentionSize) { this(retentionSize); putAll(source); } public V get(Object key) { processQueue(); V result = null; SoftValue<V, K> value = map.get(key); if (value != null) { //unwrap the 'real' value from the SoftReference result = value.get(); if (result == null) { //The wrapped value was garbage collected, so remove this entry from the backing map: //noinspection SuspiciousMethodCalls map.remove(key); } else { //Add this value to the beginning of the strong reference queue (FIFO). addToStrongReferences(result); } } return result; } private void addToStrongReferences(V result) { strongReferencesLock.lock(); try { strongReferences.add(result); trimStrongReferencesIfNecessary(); } finally { strongReferencesLock.unlock(); } } private void trimStrongReferencesIfNecessary() { while (strongReferences.size() > RETENTION_SIZE) { strongReferences.poll(); } } private void processQueue() { SoftValue sv; while ((sv = (SoftValue) queue.poll()) != null) { map.remove(sv.key); // we can access private data! } } public boolean isEmpty() { processQueue(); return map.isEmpty(); } public boolean containsKey(Object key) { processQueue(); return map.containsKey(key); } public boolean containsValue(Object value) { processQueue(); Collection values = values(); return values != null && values.contains(value); } public void putAll(Map<? extends K, ? extends V> m) { if (m == null || m.isEmpty()) { processQueue(); return; } for (Entry<? extends K, ? extends V> entry : m.entrySet()) { put(entry.getKey(), entry.getValue()); } } public Set<K> keySet() { processQueue(); return map.keySet(); } public Collection<V> values() { processQueue(); Collection<K> keys = map.keySet(); if (keys.isEmpty()) { //noinspection unchecked return Collections.EMPTY_SET; } Collection<V> values = new ArrayList<V>(keys.size()); for (K key : keys) { V v = get(key); if (v != null) { values.add(v); } } return values; } public V put(K key, V value) { processQueue(); SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue); SoftValue<V, K> previous = map.put(key, sv); addToStrongReferences(value); return previous != null ? previous.get() : null; } public V remove(Object key) { processQueue(); // throw out garbage collected values first SoftValue<V, K> raw = map.remove(key); return raw != null ? raw.get() : null; } public void clear() { strongReferencesLock.lock(); try { strongReferences.clear(); } finally { strongReferencesLock.unlock(); } processQueue(); map.clear(); } public int size() { processQueue(); return map.size(); } public Set<Entry<K, V>> entrySet() { processQueue(); Collection<K> keys = map.keySet(); if (keys.isEmpty()) { return Collections.EMPTY_SET; } Map<K, V> kvPairs = new HashMap<K, V>(keys.size()); for (K key : keys) { V v = get(key); if (v != null) { kvPairs.put(key, v); } } return kvPairs.entrySet(); } private static class SoftValue<V, K> extends SoftReference<V> { private final K key; private SoftValue(V value, K key, ReferenceQueue<? super V> queue) { super(value, queue); this.key = key; } } }