| /** |
| * 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(); |
| } |
| } |
| } |