/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package javassist.scopedpool;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
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
*
* @version <tt>$Revision: 1.4 $</tt>
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
*/
public class SoftValueHashMap<K, V> implements Map<K, V> {
private static class SoftValueRef<K1, V1> extends SoftReference<V1> {
public K1 key;
private SoftValueRef(K1 key, V1 val, ReferenceQueue<V1> q) {
super(val, q);
this.key = key;
}
}
private SoftValueRef<K, V> create(K key, V val,
ReferenceQueue<V> q) {
if (val == null)
return null;
else
return new SoftValueRef<K, V>(key, val, q);
}
private static class InnerEntry<K1, V1> implements Map.Entry<K1, V1> {
private K1 key;
private V1 value;
private InnerEntry(K1 k, V1 v) {
this.key = k;
this.value = v;
}
@Override
public K1 getKey() {
return key;
}
@Override
public V1 getValue() {
return value;
}
@Override
public V1 setValue(V1 object) {
V1 old = this.value;
this.value = object;
return old;
}
}
/**
* Returns a set of the mappings contained in this hash table.
*/
public Set<Entry<K, V>> entrySet() {
processQueue();
final HashSet<Entry<K, V>> set = new HashSet<Entry<K, V>>();
for (Entry<K, SoftValueRef<K, V>> e : hash.entrySet()) {
set.add(new InnerEntry<K, V>(e.getKey(), e.getValue().get()));
}
return set;
}
/* Hash table mapping WeakKeys to values */
private Map<K, SoftValueRef<K, V>> hash;
/* Reference queue for cleared WeakKeys */
private ReferenceQueue<V> queue = new ReferenceQueue<V>();
/*
* Remove all invalidated entries from the map, that is, remove all entries
* whose values have been discarded.
*/
@SuppressWarnings("unchecked")
private void processQueue() {
SoftValueRef<K, V> ref;
while ((ref = (SoftValueRef<K, V>)queue.poll()) != null) {
if (ref == hash.get(ref.key)) {
// only remove if it is the *exact* same WeakValueRef
//
hash.remove(ref.key);
}
}
}
/* -- Constructors -- */
/**
* Constructs a new, empty <code>WeakHashMap</code> with the given initial
* capacity and the given load factor.
*
* @param initialCapacity
* The initial capacity of the <code>WeakHashMap</code>
*
* @param loadFactor
* The load factor of the <code>WeakHashMap</code>
*
* @throws IllegalArgumentException
* If the initial capacity is less than zero, or if the load
* factor is nonpositive
*/
public SoftValueHashMap(int initialCapacity, float loadFactor) {
hash = new HashMap<K, SoftValueRef<K, V>>(initialCapacity, loadFactor);
}
/**
* Constructs a new, empty <code>WeakHashMap</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>WeakHashMap</code>
*
* @throws IllegalArgumentException
* If the initial capacity is less than zero
*/
public SoftValueHashMap(int initialCapacity) {
hash = new HashMap<K, SoftValueRef<K, V>>(initialCapacity);
}
/**
* Constructs a new, empty <code>WeakHashMap</code> with the default
* initial capacity and the default load factor, which is <code>0.75</code>.
*/
public SoftValueHashMap() {
hash = new HashMap<K, SoftValueRef<K, V>>();
}
/**
* Constructs a new <code>WeakHashMap</code> with the same mappings as the
* specified <tt>Map</tt>. The <code>WeakHashMap</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.
*/
public SoftValueHashMap(Map<K, V> t) {
this(Math.max(2 * t.size(), 11), 0.75f);
putAll(t);
}
/* -- Simple queries -- */
/**
* Returns the number of key-value mappings in this map. <strong>Note:</strong>
* <em>In contrast with most implementations of the
* <code>Map</code> interface, the time required by this operation is
* linear in the size of the map.</em>
*/
public int size() {
processQueue();
return hash.size();
}
/**
* Returns <code>true</code> if this map contains no key-value mappings.
*/
public boolean isEmpty() {
processQueue();
return hash.isEmpty();
}
/**
* Returns <code>true</code> if this map contains a mapping for the
* specified key.
*
* @param key
* The key whose presence in this map is to be tested.
*/
public boolean containsKey(Object key) {
processQueue();
return hash.containsKey(key);
}
/* -- Lookup and modification operations -- */
/**
* Returns the value to which this map maps the specified <code>key</code>.
* If this map does not contain a value for this key, then return
* <code>null</code>.
*
* @param key
* The key whose associated value, if any, is to be returned.
*/
public V get(Object key) {
processQueue();
SoftReference<V> ref = (SoftReference<V>)hash.get(key);
if (ref != null)
return ref.get();
return null;
}
/**
* Updates this map so that the given <code>key</code> maps to the given
* <code>value</code>. If the map previously contained a mapping for
* <code>key</code> then that mapping is replaced and the previous value
* is returned.
*
* @param key
* The key that is to be mapped to the given <code>value</code>
* @param value
* The value to which the given <code>key</code> is to be
* mapped
*
* @return The previous value to which this key was mapped, or
* <code>null</code> if if there was no mapping for the key
*/
public V put(K key, V value) {
processQueue();
SoftValueRef<K, V> rtn = hash.put(key, create(key, value, queue));
if (rtn != null)
return rtn.get();
return null;
}
/**
* Removes the mapping for the given <code>key</code> from this map, if
* present.
*
* @param key
* The key whose mapping is to be removed.
*
* @return The value to which this key was mapped, or <code>null</code> if
* there was no mapping for the key.
*/
public V remove(Object key) {
processQueue();
SoftValueRef<K, V> ref = hash.remove(key);
if (null != ref)
return ref.get();
return null;
}
/**
* Removes all mappings from this map.
*/
public void clear() {
processQueue();
hash.clear();
}
@Override
public boolean containsValue(Object value) {
if (value instanceof SoftValueRef) {
return hash.containsValue(value);
}
for (SoftValueRef<K, V> ref : hash.values()) {
if (ref.get() == value) {
return true;
}
}
return false;
}
@Override
public Set<K> keySet() {
return hash.keySet();
}
@Override
public void putAll(Map<? extends K, ? extends V> arg0) {
for (Entry<? extends K,? extends V> e : arg0.entrySet()) {
put(e.getKey(), e.getValue());
}
}
@Override
public Collection<V> values() {
final ArrayList<V> set = new ArrayList<V>();
for (SoftValueRef<K, V> v : hash.values()) {
set.add(v.get());
}
return set;
}
}