/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.toolkit.util.collection;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A memory-sensitive implementation of the <code>Map</code> interface.
* <p>
* 这个类是从sun的<code>sun.misc.SoftCache</code>移植并修改的.
* </p>
* <p>
* A <code>SoftCache</code> object uses {@link java.lang.ref.SoftReference soft
* references} to implement a memory-sensitive hash map. If the garbage
* collector determines at a certain point in time that a value object in a
* <code>SoftCache</code> entry is no longer strongly reachable, then it may
* remove that entry in order to release the memory occupied by the value
* object. All <code>SoftCache</code> objects are guaranteed to be completely
* cleared before the virtual machine will throw an
* <code>OutOfMemoryError</code>. Because of this automatic clearing feature,
* the behavior of this class is somewhat different from that of other
* <code>Map</code> implementations.
* </p>
* <p>
* Both null values and the null key are supported. This class has the same
* performance characteristics as the <code>HashMap</code> class, and has the
* same efficiency parameters of <em>initial capacity</em> and
* <em>load factor</em>.
* </p>
* <p>
* Like most collection classes, this class is not synchronized. A synchronized
* <code>SoftCache</code> may be constructed using the
* <code>Collections.synchronizedMap</code> method.
* </p>
* <p>
* In typical usage this class will be subclassed and the <code>fill</code>
* method will be overridden. When the <code>get</code> method is invoked on a
* key for which there is no mapping in the cache, it will in turn invoke the
* <code>fill</code> method on that key in an attempt to construct a
* corresponding value. If the <code>fill</code> method returns such a value
* then the cache will be updated and the new value will be returned. Thus, for
* example, a simple URL-content cache can be constructed as follows:
* <p/>
* <pre>
* public class URLCache extends SoftCache {
* protected Object fill(Object key) {
* return ((URL) key).getContent();
* }
* }
* </pre>
* <p/>
* </p>
* <p>
* The behavior of the <code>SoftCache</code> class depends in part upon the
* actions of the garbage collector, so several familiar (though not required)
* <code>Map</code> invariants do not hold for this class.
* </p>
* <p>
* Because entries are removed from a <code>SoftCache</code> in response to
* dynamic advice from the garbage collector, a <code>SoftCache</code> may
* behave as though an unknown thread is silently removing entries. In
* particular, even if you synchronize on a <code>SoftCache</code> instance and
* invoke none of its mutator methods, it is possible for the <code>size</code>
* method to return smaller values over time, for the <code>isEmpty</code>
* method to return <code>false</code> and then <code>true</code>, for the
* <code>containsKey</code> method to return <code>true</code> and later
* <code>false</code> for a given key, for the <code>get</code> method to return
* a value for a given key but later return <code>null</code>, for the
* <code>put</code> method to return <code>null</code> and the
* <code>remove</code> method to return <code>false</code> for a key that
* previously appeared to be in the map, and for successive examinations of the
* key set, the value set, and the entry set to yield successively smaller
* numbers of elements.
* </p>
*
* @author Mark Reinhold
* @version 1.4, 00/02/02
* @see java.util.HashMap
* @see java.lang.ref.SoftReference
* @since JDK1.2
*/
public class SoftHashMap extends AbstractMap {
/*
* The basic idea of this implementation is to maintain an internal HashMap
* that maps keys to soft references whose referents are the keys' values;
* the various accessor methods dereference these soft references before
* returning values. Because we don't have access to the innards of the
* HashMap, each soft reference must contain the key that maps to it so that
* the processQueue method can remove keys whose values have been discarded.
* Thus the HashMap actually maps keys to instances of the ValueCell class,
* which is a simple extension of the SoftReference class.
*/
private static class ValueCell extends SoftReference {
private static Object INVALID_KEY = new Object();
private static int dropped = 0;
private Object key;
private ValueCell(Object key, Object value, ReferenceQueue queue) {
super(value, queue);
this.key = key;
}
private static ValueCell create(Object key, Object value, ReferenceQueue queue) {
if (value == null) {
return null;
}
return new ValueCell(key, value, queue);
}
private static Object strip(Object val, boolean drop) {
if (val == null) {
return null;
}
ValueCell vc = (ValueCell) val;
Object o = vc.get();
if (drop) {
vc.drop();
}
return o;
}
private boolean isValid() {
return key != INVALID_KEY;
}
private void drop() {
super.clear();
key = INVALID_KEY;
dropped++;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
return valEquals(this.get(), ((ValueCell) obj).get());
}
@Override
public int hashCode() {
Object o = this.get();
return o == null ? 0 : o.hashCode();
}
}
/* Hash table mapping keys to ValueCells */
private Map hash;
/* Reference queue for cleared ValueCells */
private ReferenceQueue queue = new ReferenceQueue();
/*
* Process any ValueCells that have been cleared and enqueued by the garbage
* collector. This method should be invoked once by each public mutator in
* this class. We don't invoke this method in public accessors because that
* can lead to surprising ConcurrentModificationExceptions.
*/
private void processQueue() {
ValueCell vc;
while ((vc = (ValueCell) queue.poll()) != null) {
if (vc.isValid()) {
hash.remove(vc.key);
} else {
ValueCell.dropped--;
}
}
}
/* -- Constructors -- */
/**
* Construct a new, empty <code>SoftCache</code> with the given initial
* capacity and the given load factor.
*
* @param initialCapacity The initial capacity of the cache
* @param loadFactor A number between 0.0 and 1.0
* @throws IllegalArgumentException If the initial capacity is less than or
* equal to zero, or if the load factor is less than zero
*/
public SoftHashMap(int initialCapacity, float loadFactor) {
hash = new HashMap(initialCapacity, loadFactor);
}
/**
* Construct a new, empty <code>SoftCache</code> with the given initial
* capacity and the default load factor.
*
* @param initialCapacity The initial capacity of the cache
* @throws IllegalArgumentException If the initial capacity is less than or
* equal to zero
*/
public SoftHashMap(int initialCapacity) {
hash = new HashMap(initialCapacity);
}
/**
* Construct a new, empty <code>SoftCache</code> with the default capacity
* and the default load factor.
*/
public SoftHashMap() {
hash = new HashMap();
}
/* -- Simple queries -- */
/**
* Return the number of key-value mappings in this cache. The time required
* by this operation is linear in the size of the map.
*/
@Override
public int size() {
return entrySet().size();
}
/** Return <code>true</code> if this cache contains no key-value mappings. */
@Override
public boolean isEmpty() {
return entrySet().isEmpty();
}
/**
* Return <code>true</code> if this cache contains a mapping for the
* specified key. If there is no mapping for the key, this method will not
* attempt to construct one by invoking the <code>fill</code> method.
*
* @param key The key whose presence in the cache is to be tested
*/
@Override
public boolean containsKey(Object key) {
Object value = hash.get(key);
if (value == null) {
return hash.containsKey(key);
} else {
return ValueCell.strip(value, false) != null;
}
}
/* -- Lookup and modification operations -- */
/**
* Create a value object for the given <code>key</code>. This method is
* invoked by the <code>get</code> method when there is no entry for
* <code>key</code>. If this method returns a non-<code>null</code> value,
* then the cache will be updated to map <code>key</code> to that value, and
* that value will be returned by the <code>get</code> method.
* <p>
* The default implementation of this method simply returns
* <code>null</code> for every <code>key</code> value. A subclass may
* override this method to provide more useful behavior.
* </p>
*
* @param key The key for which a value is to be computed
* @return A value for <code>key</code>, or <code>null</code> if one could
* not be computed
* @see #get
*/
protected Object fill(Object key) {
return null;
}
/**
* Return the value to which this cache maps the specified <code>key</code>.
* If the cache does not presently contain a value for this key, then invoke
* the <code>fill</code> method in an attempt to compute such a value. If
* that method returns a non-<code>null</code> value, then update the cache
* and return the new value. Otherwise, return <code>null</code>.
* <p>
* Note that because this method may update the cache, it is considered a
* mutator and may cause <code>ConcurrentModificationException</code>s to be
* thrown if invoked while an iterator is in use.
* </p>
*
* @param key The key whose associated value, if any, is to be returned
* @see #fill
*/
@Override
public Object get(Object key) {
processQueue();
Object v = hash.get(key);
if (v == null) {
v = fill(key);
if (v != null) {
hash.put(key, ValueCell.create(key, v, queue));
return v;
}
}
return ValueCell.strip(v, false);
}
/**
* Update this cache so that the given <code>key</code> maps to the given
* <code>value</code>. If the cache previously contained a mapping for
* <code>key</code> then that mapping is replaced and the old 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
*/
@Override
public Object put(Object key, Object value) {
processQueue();
ValueCell vc = ValueCell.create(key, value, queue);
return ValueCell.strip(hash.put(key, vc), true);
}
/**
* Remove the mapping for the given <code>key</code> from this cache, 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
*/
@Override
public Object remove(Object key) {
processQueue();
return ValueCell.strip(hash.remove(key), true);
}
/** Remove all mappings from this cache. */
@Override
public void clear() {
processQueue();
hash.clear();
}
/* -- Views -- */
private static boolean valEquals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
/*
* Internal class for entries. Because it uses SoftCache.this.queue, this
* class cannot be static.
*/
private class Entry implements Map.Entry {
private Map.Entry ent;
private Object value;/*
* Strong reference to value, to prevent the GC
* from flushing the value while this Entry exists
*/
Entry(Map.Entry ent, Object value) {
this.ent = ent;
this.value = value;
}
public Object getKey() {
return ent.getKey();
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
return ent.setValue(ValueCell.create(ent.getKey(), value, queue));
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
return valEquals(ent.getKey(), e.getKey()) && valEquals(value, e.getValue());
}
@Override
public int hashCode() {
Object k;
return ((k = getKey()) == null ? 0 : k.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}
/* Internal class for entry sets */
private class EntrySet extends AbstractSet {
Set hashEntries = hash.entrySet();
@Override
public Iterator iterator() {
return new Iterator() {
Iterator hashIterator = hashEntries.iterator();
Entry next = null;
public boolean hasNext() {
while (hashIterator.hasNext()) {
Map.Entry ent = (Map.Entry) hashIterator.next();
ValueCell vc = (ValueCell) ent.getValue();
Object v = null;
if (vc != null && (v = vc.get()) == null) {
/* Value has been flushed by GC */
continue;
}
next = new Entry(ent, v);
return true;
}
return false;
}
public Object next() {
if (next == null && !hasNext()) {
throw new NoSuchElementException();
}
Entry e = next;
next = null;
return e;
}
public void remove() {
hashIterator.remove();
}
};
}
@Override
public boolean isEmpty() {
return !iterator().hasNext();
}
@Override
public int size() {
int j = 0;
for (Iterator i = iterator(); i.hasNext(); i.next()) {
j++;
}
return j;
}
@Override
public boolean remove(Object o) {
processQueue();
if (o instanceof Entry) {
return hashEntries.remove(((Entry) o).ent);
} else if (o instanceof Map.Entry) {
Map.Entry e = (Map.Entry) o;
return hashEntries.remove(new DefaultMapEntry(e.getKey(), ValueCell.create(e.getKey(), e.getValue(),
queue)));
} else {
return false;
}
}
}
private Set entrySet = null;
/** Return a <code>Set</code> view of the mappings in this cache. */
@Override
public Set entrySet() {
if (entrySet == null) {
entrySet = new EntrySet();
}
return entrySet;
}
}