/*
* $Id$
*
* Copyright (c) 2007 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.concurrent;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A memory-sensitive {@link ConcurrentMap} which stores the values
* in {@link SoftReference}s. This <code>ConcurrentMap</code> grows
* without bound, but when the JVM is under memory pressure, values
* held by it may be garbage collected.
*
* <p>All methods except {@link #get} cause the <code>Map</code> to
* be cleared of key-value pairs for which the value has been garbage
* collected. Processing key-value pairs with dead values is <em>not</em>
* an atomic operation. Thus, it is possible, though unlikely, that more
* values will be garbage collected between the removal of dead key-value
* pairs and the return of the method in which this takes place.</p>
*
* <p>This implementation does not permit <code>null</code> keys or
* values.</p>
*
* @since 3.1.0
* @author Joel Uckelman
*/
public class ConcurrentSoftHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V> {
private static final class SoftValue<K,V> extends SoftReference<V> {
private final K key;
private SoftValue(K key, V value, ReferenceQueue<V> queue) {
super(value, queue);
this.key = key;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || o.getClass() != this.getClass()) return false;
final SoftValue<?, ?> sv = (SoftValue<?, ?>) o;
return key.equals(sv.key) &&
get() == null ? sv.get() == null : get().equals(sv.get());
}
@Override
public int hashCode() {
return get() == null ? 0 : get().hashCode();
}
}
private final ConcurrentMap<K,SoftValue<K,V>> map =
new ConcurrentHashMap<K,SoftValue<K,V>>();
private final ReferenceQueue<V> queue = new ReferenceQueue<V>();
@SuppressWarnings("unchecked")
private void processQueue() {
SoftValue<K,V> sv;
// The ReferenceQueue API is broken. ReferenceQueue<T>.poll()
// returns a Reference<? extends T>. WTF? How could you ever use
// this without having to cast back to the kind of Reference
// you put in?
while ((sv = (SoftValue<K,V>) queue.poll()) != null) {
map.remove(sv.key, sv);
// System.out.println("Hasta la vista, " + sv.key + ".");
}
// System.out.println("Cache size = " + map.size());
}
// Query Operations
/** {@inheritDoc} */
@Override
public int size() {
processQueue();
return map.size();
}
/** {@inheritDoc} */
@Override
public boolean containsKey(Object key) {
if (key == null)
throw new NullPointerException();
processQueue();
return map.containsKey(key);
}
/** {@inheritDoc} */
@Override
public V get(Object key) {
if (key == null)
throw new NullPointerException();
final SoftValue<K,V> sv = map.get(key);
if (sv != null) {
final V value = sv.get();
if (value == null) {
map.remove(key, sv);
}
return value;
}
return null;
}
// Modification Operations
/** {@inheritDoc} */
@Override
public V put(K key, V value) {
if (key == null)
throw new NullPointerException();
if (value == null)
throw new NullPointerException();
processQueue();
final SoftValue<K,V> oldSV =
map.put(key, new SoftValue<K,V>(key, value, queue));
return oldSV == null ? null : oldSV.get();
}
/** {@inheritDoc} */
@Override
public V remove(Object key) {
if (key == null)
throw new NullPointerException();
processQueue();
final SoftValue<K,V> oldSV = map.remove(key);
return oldSV == null ? null : oldSV.get();
}
// Bulk Operations
/** {@inheritDoc} */
@Override
public void clear() {
map.clear();
while (queue.poll() != null);
}
// Views
private Set<Map.Entry<K,V>> entrySet;
/**
* An implementation of {@link Map.Entry}. Remove this and use
* {@link AbstractMap.SimpleEntry} with 1.6+.
*/
public static class SimpleEntry<K,V> implements Entry<K,V> {
private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
private static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
}
/** {@inheritDoc} */
public Set<Map.Entry<K, V>> entrySet() {
processQueue();
if (entrySet == null) {
entrySet = new AbstractSet<Map.Entry<K,V>>() {
public Iterator<Map.Entry<K,V>> iterator() {
return new Iterator<Map.Entry<K,V>>() {
private final Iterator<Map.Entry<K,SoftValue<K,V>>> i =
map.entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public Map.Entry<K,V> next() {
final Map.Entry<K,SoftValue<K,V>> e = i.next();
return new SimpleEntry<K,V>(e.getKey(), e.getValue().get());
}
public void remove() {
i.remove();
}
};
}
public int size() {
return ConcurrentSoftHashMap.this.size();
}
public boolean contains(Object v) {
return ConcurrentSoftHashMap.this.containsValue(v);
}
};
}
return entrySet;
}
// Concurrent Operations
/** {@inheritDoc} */
public V putIfAbsent(K key, V value) {
if (key == null)
throw new NullPointerException();
if (value == null)
throw new NullPointerException();
processQueue();
final SoftValue<K,V> oldSV =
map.putIfAbsent(key, new SoftValue<K,V>(key, value, queue));
return oldSV == null ? null : oldSV.get();
}
/** {@inheritDoc} */
public boolean remove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
if (value == null)
throw new NullPointerException();
processQueue();
return map.remove(key, new SoftValue<Object,Object>(key, value, null));
}
/** {@inheritDoc} */
public boolean replace(K key, V oldValue, V newValue) {
if (key == null)
throw new NullPointerException();
if (oldValue == null)
throw new NullPointerException();
if (newValue == null)
throw new NullPointerException();
processQueue();
return map.replace(key,
new SoftValue<K,V>(key, oldValue, null),
new SoftValue<K,V>(key, newValue, queue));
}
/** {@inheritDoc} */
public V replace(K key, V value) {
if (key == null)
throw new NullPointerException();
if (value == null)
throw new NullPointerException();
processQueue();
final SoftValue<K,V> oldSV =
map.replace(key, new SoftValue<K,V>(key, value, queue));
return oldSV == null ? null : oldSV.get();
}
}