/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
package org.biojava.utils.cache;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Map implementation which keeps weak references to values.
* Entries are removed from the map when their value is
* no longer reachable using normal (hard) references. This is
* useful for maintaining canonical copies of objects without forcing
* these objects to remain in memory forever.
*
*
* Note that this is distinct from the standard library class,
* WeakHashMap
which has weak keys.
*
*
* @author Thomas Down
* @since 1.3
*/
public class WeakValueHashMap extends AbstractMap {
private final Map keyToRefMap;
private final ReferenceQueue queue;
private final Set iteratorRefs;
private final ReferenceQueue iteratorRefQueue;
public WeakValueHashMap() {
keyToRefMap = new HashMap();
queue = new ReferenceQueue();
iteratorRefs = new HashSet();
iteratorRefQueue = new ReferenceQueue();
}
private void diddleReferenceQueue() {
// Avoid making behind-the-scenes modifications while iterators exist.
if (iteratorRefs.size() > 0) {
Reference ref;
while ((ref = iteratorRefQueue.poll()) != null) {
iteratorRefs.remove(ref);
}
if (iteratorRefs.size() > 0) {
return;
}
}
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
keyToRefMap.remove(ref.getKey());
}
}
public Object put(Object key, Object value) {
diddleReferenceQueue();
Reference oldRef = (Reference) keyToRefMap.put(key, new KeyedWeakReference(key, value, queue));
if (oldRef != null) {
Object oldRefVal = oldRef.get();
oldRef.clear();
return oldRefVal;
} else {
return null;
}
}
public Object get(Object key) {
Reference ref = (Reference) keyToRefMap.get(key);
if (ref != null) {
return ref.get();
} else {
return null;
}
}
public boolean containsKey(Object o) {
diddleReferenceQueue();
return keyToRefMap.containsKey(o);
}
public Set entrySet() {
diddleReferenceQueue();
return new WVEntrySet();
}
private class WVEntrySet extends AbstractSet {
private Set keyRefEntrySet;
public WVEntrySet() {
super();
keyRefEntrySet = keyToRefMap.entrySet();
}
public int size() {
return keyRefEntrySet.size();
}
public Iterator iterator() {
Iterator i = new WVEntryIterator(keyRefEntrySet.iterator());
iteratorRefs.add(new PhantomReference(i, iteratorRefQueue));
return i;
}
}
private class WVEntryIterator implements Iterator {
private Object cache;
private Iterator keyRefIterator;
public WVEntryIterator(Iterator keyRefIterator) {
this.keyRefIterator = keyRefIterator;
}
public boolean hasNext() {
if (cache == null) {
primeCache();
}
return cache != null;
}
public Object next() {
if (cache == null) {
primeCache();
}
if (cache == null) {
throw new NoSuchElementException();
} else {
Object o = cache;
cache = null;
return o;
}
}
public void remove() {
if (cache != null) {
throw new IllegalStateException("next() not called");
} else {
keyRefIterator.remove();
}
}
private void primeCache() {
while (keyRefIterator.hasNext()) {
Map.Entry krme = (Map.Entry) keyRefIterator.next();
Object ref = ((Reference) krme.getValue()).get();
if (ref != null) {
cache = new WVMapEntry(krme.getKey(), ref);
return;
}
}
}
}
private static class WVMapEntry implements Map.Entry {
private Object key;
private Object value;
private WVMapEntry(Object key, Object value) {
this.key = key;
this.value = value;
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object v) {
throw new UnsupportedOperationException();
}
public boolean equals(Object o) {
if (! (o instanceof Map.Entry)) {
return false;
}
Map.Entry mo = (Map.Entry) o;
return ((key == null ? mo.getKey() == null : key.equals(mo.getKey())) &&
(value == null ? mo.getValue() == null : value.equals(mo.getValue())));
}
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}
}