package org.infinispan.commons.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* This Map will remove entries when the value in the map has been cleaned from
* garbage collection
*
* @param <K> the key type
* @param <V> the value type
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
* @see <a href="http://anonsvn.jboss.org/repos/common/common-core/trunk/src/main/java/org/jboss/util/collection/">
* JBoss Common Core source code for origins of this class</a>
*/
public final class WeakValueHashMap<K, V> extends java.util.AbstractMap<K, V> {
/**
* Hash table mapping keys to ref values
*/
private Map<K, ValueRef<K, V>> map;
/**
* Reference queue for cleared RefKeys
*/
private ReferenceQueue<V> queue = new ReferenceQueue<V>();
/**
* Constructs a new, empty <code>WeakValueHashMap</code> with the given
* initial capacity and the given load factor.
*
* @param initialCapacity The initial capacity of the <code>WeakValueHashMap</code>
* @param loadFactor The load factor of the <code>WeakValueHashMap</code>
* @throws IllegalArgumentException If the initial capacity is less than
* zero, or if the load factor is
* nonpositive
*/
public WeakValueHashMap(int initialCapacity, float loadFactor) {
map = createMap(initialCapacity, loadFactor);
}
/**
* Constructs a new, empty <code>WeakValueHashMap</code> with the given
* initial capacity and the default load factor, which is
* <code>0.75</code>.
*
* @param initialCapacity The initial capacity of the <code>WeakValueHashMap</code>
* @throws IllegalArgumentException If the initial capacity is less than
* zero
*/
public WeakValueHashMap(int initialCapacity) {
map = createMap(initialCapacity);
}
/**
* Constructs a new, empty <code>WeakValueHashMap</code> with the default
* initial capacity and the default load factor, which is
* <code>0.75</code>.
*/
public WeakValueHashMap() {
map = createMap();
}
/**
* Constructs a new <code>WeakValueHashMap</code> with the same mappings as
* the specified <tt>Map</tt>. The <code>WeakValueHashMap</code> is created
* with an initial capacity of twice the number of mappings in the specified
* map or 11 (whichever is greater), and a default load factor, which is
* <tt>0.75</tt>.
*
* @param t the map whose mappings are to be placed in this map.
* @since 1.3
*/
public WeakValueHashMap(Map<K, V> t) {
this(Math.max(2 * t.size(), 11), 0.75f);
putAll(t);
}
/**
* Create new value ref instance.
*
* @param key the key
* @param value the value
* @param q the ref queue
* @return new value ref instance
*/
private ValueRef<K, V> create(K key, V value, ReferenceQueue<V> q) {
return WeakValueRef.create(key, value, q);
}
/**
* Create map.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @return new map instance
*/
private Map<K, ValueRef<K, V>> createMap(int initialCapacity, float loadFactor) {
return new HashMap<K, ValueRef<K, V>>(initialCapacity, loadFactor);
}
/**
* Create map.
*
* @param initialCapacity the initial capacity
* @return new map instance
*/
private Map<K, ValueRef<K, V>> createMap(int initialCapacity) {
return new HashMap<K, ValueRef<K, V>>(initialCapacity);
}
/**
* Create map.
*
* @return new map instance
*/
protected Map<K, ValueRef<K, V>> createMap() {
return new HashMap<K, ValueRef<K, V>>();
}
@Override
public int size() {
processQueue();
return map.size();
}
@Override
public boolean containsKey(Object key) {
processQueue();
return map.containsKey(key);
}
@Override
public V get(Object key) {
processQueue();
ValueRef<K, V> ref = map.get(key);
if (ref != null)
return ref.get();
return null;
}
@Override
public V put(K key, V value) {
processQueue();
ValueRef<K, V> ref = create(key, value, queue);
ValueRef<K, V> result = map.put(key, ref);
if (result != null)
return result.get();
return null;
}
@Override
public V remove(Object key) {
processQueue();
ValueRef<K, V> result = map.remove(key);
if (result != null)
return result.get();
return null;
}
@Override
public Set<Entry<K, V>> entrySet() {
processQueue();
return new EntrySet();
}
@Override
public void clear() {
processQueue();
map.clear();
}
@Override
public String toString() {
return map.toString();
}
/**
* Remove all entries whose values have been discarded.
*/
@SuppressWarnings("unchecked")
private void processQueue() {
ValueRef<K, V> ref = (ValueRef<K, V>) queue.poll();
while (ref != null) {
// only remove if it is the *exact* same WeakValueRef
if (ref == map.get(ref.getKey()))
map.remove(ref.getKey());
ref = (ValueRef<K, V>) queue.poll();
}
}
/**
* EntrySet.
*/
private class EntrySet extends AbstractSet<Entry<K, V>> {
@Override
public Iterator<Entry<K, V>> iterator() {
return new EntrySetIterator(map.entrySet().iterator());
}
@Override
public int size() {
return WeakValueHashMap.this.size();
}
}
/**
* EntrySet iterator
*/
private class EntrySetIterator implements Iterator<Entry<K, V>> {
/**
* The delegate
*/
private Iterator<Entry<K, ValueRef<K, V>>> delegate;
/**
* Create a new EntrySetIterator.
*
* @param delegate the delegate
*/
public EntrySetIterator(Iterator<Entry<K, ValueRef<K, V>>> delegate) {
this.delegate = delegate;
}
public boolean hasNext() {
return delegate.hasNext();
}
public Entry<K, V> next() {
Entry<K, ValueRef<K, V>> next = delegate.next();
return next.getValue();
}
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
/**
* Weak value ref.
*
* @param <K> the key type
* @param <V> the value type
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
private static class WeakValueRef<K, V>
extends WeakReference<V> implements ValueRef<K, V> {
/**
* The key
*/
public K key;
/**
* Safely create a new WeakValueRef
*
* @param <K> the key type
* @param <V> the value type
* @param key the key
* @param val the value
* @param q the reference queue
* @return the reference or null if the value is null
*/
static <K, V> WeakValueRef<K, V> create(K key, V val, ReferenceQueue<V> q) {
if (val == null)
return null;
else
return new WeakValueRef<K, V>(key, val, q);
}
/**
* Create a new WeakValueRef.
*
* @param key the key
* @param val the value
* @param q the reference queue
*/
private WeakValueRef(K key, V val, ReferenceQueue<V> q) {
super(val, q);
this.key = key;
}
public K getKey() {
return key;
}
public V getValue() {
return get();
}
public V setValue(V value) {
throw new UnsupportedOperationException("setValue");
}
@Override
public String toString() {
return String.valueOf(get());
}
}
public interface ValueRef<K, V> extends Map.Entry<K, V> {
/**
* Get underlying value.
*
* @return the value
*/
V get();
}
}