blob: 217003c8f074b1d47f843455446b616b546dfd94 [file] [log] [blame]
/**
* JDBM LICENSE v1.00
*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "JDBM" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Cees de Groot. For written permission,
* please contact cg@cdegroot.com.
*
* 4. Products derived from this Software may not be called "JDBM"
* nor may "JDBM" appear in their names without prior written
* permission of Cees de Groot.
*
* 5. Due credit should be given to the JDBM Project
* (http://jdbm.sourceforge.net/).
*
* THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2000 (C) Cees de Groot. All Rights Reserved.
* Contributions are Copyright (C) 2000 by their associated contributors.
*
* $Id
*/
package jdbm.helper;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.Reference;
import java.util.Enumeration;
import java.util.Map;
import java.util.HashMap;
/**
* Wraps a deterministic cache policy with a <q>Level-2</q> cache based on
* J2SE's {@link SoftReference soft references}. Soft references allow
* this cache to keep references to objects until the memory they occupy
* is required elsewhere.
* <p>
* Since the {@link CachePolicy} interface requires an event be fired
* when an object is evicted, and the event contains the actual object,
* this class cannot be a stand-alone implementation of
* <code>CachePolicy</code>. This limitation arises because Java References
* does not support notification before references are cleared; nor do
* they support reaching soft referents. Therefore, this wrapper cache
* aggressively notifies evictions: events are fired when the objects are
* evicted from the internal cache. Consequently, the soft cache may return
* a non-null object when <code>get( )</code> is called, even if that
* object was said to have been evicted.
* <p>
* The current implementation uses a hash structure for its internal key
* to value mappings.
* <p>
* Note: this component's publicly exposed methods are not threadsafe;
* potentially concurrent code should synchronize on the cache instance.
*
* @author <a href="mailto:dranatunga@users.sourceforge.net">Dilum Ranatunga</a>
* @version $Id: SoftCache.java,v 1.1 2003/11/01 13:29:27 dranatunga Exp $
*/
public class SoftCache implements CachePolicy {
private static final int INITIAL_CAPACITY = 128;
private static final float DEFAULT_LOAD_FACTOR = 1.5f;
private final ReferenceQueue _clearQueue = new ReferenceQueue();
private final CachePolicy _internal;
private final Map _cacheMap;
/**
* Creates a soft-reference based L2 cache with a {@link MRU} cache as
* the internal (L1) cache. The soft reference cache uses the
* default load capacity of 1.5f, which is intended to sacrifice some
* performance for space. This compromise is reasonable, since all
* {@link #get(Object) get( )s} first try the L1 cache anyway. The
* internal MRU is given a capacity of 128 elements.
*/
public SoftCache() {
this(new MRU(INITIAL_CAPACITY));
}
/**
* Creates a soft-reference based L2 cache wrapping the specified
* L1 cache.
*
* @param internal non null internal cache.
* @throws NullPointerException if the internal cache is null.
*/
public SoftCache(CachePolicy internal) throws NullPointerException {
this(DEFAULT_LOAD_FACTOR, internal);
}
/**
* Creates a soft-reference based L2 cache wrapping the specified
* L1 cache. This constructor is somewhat implementation-specific,
* so users are encouraged to use {@link #SoftCache(CachePolicy)}
* instead.
*
* @param loadFactor load factor that the soft cache's hash structure
* should use.
* @param internal non null internal cache.
* @throws IllegalArgumentException if the load factor is nonpositive.
* @throws NullPointerException if the internal cache is null.
*/
public SoftCache(float loadFactor, CachePolicy internal) throws IllegalArgumentException, NullPointerException {
if (internal == null) {
throw new NullPointerException("Internal cache cannot be null.");
}
_internal = internal;
_cacheMap = new HashMap(INITIAL_CAPACITY, loadFactor);
}
/**
* Adds the specified value to the cache under the specified key. Note
* that the object is added to both this and the internal cache.
* @param key the (non-null) key to store the object under
* @param value the (non-null) object to place in the cache
* @throws CacheEvictionException exception that the internal cache
* would have experienced while evicting an object it currently
* cached.
*/
public void put(Object key, Object value) throws CacheEvictionException {
if (key == null) {
throw new IllegalArgumentException("key cannot be null.");
} else if (value == null) {
throw new IllegalArgumentException("value cannot be null.");
}
_internal.put(key, value);
removeClearedEntries();
_cacheMap.put(key, new Entry(key, value, _clearQueue));
}
/**
* Gets the object cached under the specified key.
* <p>
* The cache is looked up in the following manner:
* <ol>
* <li>The internal (L1) cache is checked. If the object is found, it is
* returned.</li>
* <li>This (L2) cache is checked. If the object is not found, then
* the caller is informed that the object is inaccessible.</li>
* <li>Since the object exists in L2, but not in L1, the object is
* readded to L1 using {@link CachePolicy#put(Object, Object)}.</li>
* <li>If the readding succeeds, the value is returned to caller.</li>
* <li>If a cache eviction exception is encountered instead, we
* remove the object from L2 and behave as if the object was
* inaccessible.</li>
* </ol>
* @param key the key that the object was stored under.
* @return the object stored under the key specified; null if the
* object is not (nolonger) accessible via this cache.
*/
public Object get(Object key) {
// first try the internal cache.
Object value = _internal.get(key);
if (value != null) {
return value;
}
// poll and remove cleared references.
removeClearedEntries();
Entry entry = (Entry)_cacheMap.get(key);
if (entry == null) { // object is not in cache.
return null;
}
value = entry.getValue();
if (value == null) { // object was in cache, but it was cleared.
return null;
}
// we have the object. so we try to re-insert it into internal cache
try {
_internal.put(key, value);
} catch (CacheEvictionException e) {
// if the internal cache causes a fuss, we kick the object out.
_cacheMap.remove(key);
return null;
}
return value;
}
/**
* Removes any object stored under the key specified. Note that the
* object is removed from both this (L2) and the internal (L1)
* cache.
* @param key the key whose object should be removed
*/
public void remove(Object key) {
_cacheMap.remove(key);
_internal.remove(key);
}
/**
* Removes all objects in this (L2) and its internal (L1) cache.
*/
public void removeAll() {
_cacheMap.clear();
_internal.removeAll();
}
/**
* Gets all the objects stored by the internal (L1) cache.
* @return an enumeration of objects in internal cache.
*/
public Enumeration elements() {
return _internal.elements();
}
/**
* Adds the specified listener to this cache. Note that the events
* fired by this correspond to the <em>internal</em> cache's events.
* @param listener the (non-null) listener to add to this policy
* @throws IllegalArgumentException if listener is null.
*/
public void addListener(CachePolicyListener listener)
throws IllegalArgumentException {
_internal.addListener(listener);
}
/**
* Removes a listener that was added earlier.
* @param listener the listener to remove.
*/
public void removeListener(CachePolicyListener listener) {
_internal.removeListener(listener);
}
/**
* Cleans the mapping structure of any obsolete entries. This is usually
* called before insertions and lookups on the mapping structure. The
* runtime of this is usually very small, but it can be as expensive as
* n * log(n) if a large number of soft references were recently cleared.
*/
private final void removeClearedEntries() {
for (Reference r = _clearQueue.poll(); r != null; r = _clearQueue.poll()) {
Object key = ((Entry)r).getKey();
_cacheMap.remove(key);
}
}
/**
* Value objects we keep in the internal map. This contains the key in
* addition to the value, because polling for cleared references
* returns these instances, and having access to their corresponding
* keys drastically improves the performance of removing the pair
* from the map (see {@link SoftCache#removeClearedEntries()}.)
*/
private static class Entry extends SoftReference {
private final Object _key;
/**
* Constructor that uses <code>value</code> as the soft
* reference's referent.
*/
public Entry(Object key, Object value, ReferenceQueue queue) {
super(value, queue);
_key = key;
}
/**
* Gets the key
* @return the key associated with this value.
*/
final Object getKey() {
return _key;
}
/**
* Gets the value
* @return the value; null if it is no longer accessible
*/
final Object getValue() {
return this.get();
}
}
}