diff --git a/src/com/silverwrist/util/cache/CacheMap.java b/src/com/silverwrist/util/cache/CacheMap.java index 69e0633..4e97724 100644 --- a/src/com/silverwrist/util/cache/CacheMap.java +++ b/src/com/silverwrist/util/cache/CacheMap.java @@ -20,6 +20,17 @@ package com.silverwrist.util.cache; import java.lang.ref.*; import java.util.*; +/** + * A special kind of Map that acts as a cache of data items. The CacheMap + * uses SoftReferences, so its data values will be shed if the virtual machine needs + * more memory; also, if too many entries are added to the map, certain entries will be removed in + * accordance with the supplied or default CacheMapStrategy object. + * + * @author Eric J. Bowersox <erbo@silcom.com> + * @version X + * @see CacheMapStrategy + * @see java.util.Map + */ public class CacheMap implements Map { /*-------------------------------------------------------------------------------- @@ -27,7 +38,7 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ - static class CacheOrdering implements Comparator + static final class CacheOrdering implements Comparator { private CacheMapStrategy strategy; // CacheMap's strategy object private long tick; // when the sort operation started @@ -60,7 +71,7 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ - static class DefaultStrategy implements CacheMapStrategy + static final class DefaultStrategy implements CacheMapStrategy { private static final long SCALING_FACTOR = 5000; @@ -100,6 +111,17 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ + /** + * Constructs a new CacheMap. + * + * @param capacity The maximum number of entries this map can contain. + * @param shrink_percentage The percentage of entries which will be removed from the cache + * whenever it needs to shrink itself. + * @param strategy The strategy object which is used to determine which elements to remove. + * @exception java.lang.IllegalArgumentException If the specified capacity is negative or zero, or + * the shrink percentage is not in the range [1..100]. + * @exception java.lang.NullPointerException If the strategy object reference is null. + */ public CacheMap(int capacity, int shrink_percentage, CacheMapStrategy strategy) { if (capacity<=0) @@ -118,12 +140,27 @@ public class CacheMap implements Map } // end constructor + /** + * Constructs a new CacheMap with a default strategy. + * + * @param capacity The maximum number of entries this map can contain. + * @param shrink_percentage The percentage of entries which will be removed from the cache + * whenever it needs to shrink itself. + * @exception java.lang.IllegalArgumentException If the specified capacity is negative or zero, or + * the shrink percentage is not in the range [1..100]. + */ public CacheMap(int capacity, int shrink_percentage) { this(capacity,shrink_percentage,default_strategy_singleton); } // end constructor + /** + * Constructs a new CacheMap with a default strategy and shrink percentage. + * + * @param capacity The maximum number of entries this map can contain. + * @exception java.lang.IllegalArgumentException If the specified capacity is negative or zero. + */ public CacheMap(int capacity) { this(capacity,10,default_strategy_singleton); @@ -137,9 +174,9 @@ public class CacheMap implements Map private void doSweep() { - Reference r = rq.poll(); - ArrayList ditch = new ArrayList(); - Iterator it; + Reference r = rq.poll(); // the reference that's been cleared + ArrayList ditch = new ArrayList(); // a list of entries to ditch + Iterator it; // current iterator while (r!=null) { // look for the dead reference in the element list @@ -195,6 +232,12 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ + /** + * Returns the number of key-value mappings in this map. If the map contains more than + * Integer.MAX_VALUE elements, returns Integer.MAX_VALUE. + * + * @return The number of key-value mappings in this map. + */ public int size() { doSweep(); @@ -202,6 +245,11 @@ public class CacheMap implements Map } // end size + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings. + */ public boolean isEmpty() { doSweep(); @@ -209,6 +257,14 @@ public class CacheMap implements Map } // end isEmpty + /** + * Returns true if this map contains a mapping for the specified key. + * + * @param key Key whose presence in this map is to be tested. + * @return true if this map contains a mapping for the specified key. + * @exception java.lang.ClassCastException If the key is of an inappropriate type for this map. + * @exception java.lang.NullPointerException If the key is null. + */ public boolean containsKey(Object key) { doSweep(); @@ -216,6 +272,12 @@ public class CacheMap implements Map } // end containsKey + /** + * Returns true if this map maps one or more keys to the specified value. + * + * @param value Value whose presence in this map is to be tested. + * @return true if this map maps one or more keys to the specified value. + */ public boolean containsValue(Object value) { doSweep(); @@ -243,6 +305,17 @@ public class CacheMap implements Map } // end containsValue + /** + * Returns the value to which this map maps the specified key. Returns null if the map + * contains no mapping for this key. + * + * @param key Key whose associated value is to be returned. + * @return The value to which this map maps the specified key, or null if the map contains + * no mapping for this key. + * @exception java.lang.ClassCastException If the key is of an inappropriate type for this map. + * @exception java.lang.NullPointerException If the key is null. + * @see #containsKey(java.lang.Object) + */ public Object get(Object key) { doSweep(); @@ -254,6 +327,20 @@ public class CacheMap implements Map } // end get + /** + * Associates the specified value with the specified key in this map. If the map previously contained + * a mapping for this key, the old value is replaced. + * + * @param key Key with which the specified value is to be associated. + * @param value Value to be associated with the specified key. + * @return The previous value associated with the specified key, or null if there was no + * mapping for the key. + * @exception java.lang.ClassCastException If the class of the specified key or value prevents it from + * being stored in this map. + * @exception java.lang.IllegalArgumentException If some aspect of this key or value prevents it from + * being stored in this map. + * @exception java.lang.NullPointerException If the specified key or value is null. + */ public Object put(Object key, Object value) { doSweep(); @@ -284,6 +371,13 @@ public class CacheMap implements Map } // end put + /** + * Removes the mapping for this key from this map if present. + * + * @param key Key whose mapping is to be removed from the map. + * @return The previous value associated with the specified key, or null if there was no + * mapping for the key. + */ public Object remove(Object key) { doSweep(); @@ -308,6 +402,17 @@ public class CacheMap implements Map } // end remove + /** + * Copies all of the mappings from the specified map to this map. These mappings will replace any mappings + * that this map had for any of the keys currently in the specified map. + * + * @param map Mappings to be stored in this map. + * @exception java.lang.ClassCastException If the class of a key or value in the specified map prevents + * it from being stored in this map. + * @exception java.lang.IllegalArgumentException If some aspect of a key or value in the specified map + * prevents it from being stored in this map. + * @exception java.lang.NullPointerException If the specified key or value is null. + */ public void putAll(Map map) { doSweep(); @@ -329,6 +434,9 @@ public class CacheMap implements Map } // end putAll + /** + * Removes all mappings from this map. + */ public synchronized void clear() { base_map.clear(); @@ -344,24 +452,48 @@ public class CacheMap implements Map } // end clear + /** + * Returns a set view of the keys contained in this map. + * + * @return A set view of the keys contained in this map. + */ public Set keySet() { return base_map.keySet(); } // end keySet + /** + * Returns a collection view of the values contained in this map. + * + * @return A collection view of the values contained in this map. + * @exception java.lang.UnsupportedOperationException This map does not support this operation. + */ public Collection values() { throw new UnsupportedOperationException("CacheMap.values() is not implemented"); } // end values + /** + * Returns a set view of the mappings contained in this map. + * + * @return A set view of the mappings contained in this map. + * @exception java.lang.UnsupportedOperationException This map does not support this operation. + */ public Set entrySet() { throw new UnsupportedOperationException("CacheMap.entrySet() is not implemented"); } // end entrySet + /** + * Compares the specified object with this map for equality. Returns true if the given object + * is also a map and the two Maps represent the same mappings. + * + * @param o Object to be compared for equality with this map. + * @return true if the specified object is equal to this map. + */ public boolean equals(Object o) { if ((o==null) || !(o instanceof Map)) @@ -395,6 +527,14 @@ public class CacheMap implements Map } // end equals + /** + * Returns the hash code value for this map. The hash code of a map is defined to be the sum of the + * hash codes of each entry in the map's entrySet view. + * + * @return The hash code value for this map. + * @see #equals(java.lang.Object) + * @see CacheMapEntry#hashCode() + */ public int hashCode() { doSweep(); @@ -416,12 +556,23 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ + /** + * Returns the capacity of this cache map. + * + * @return The capacity of this cache map. + */ public int getCapacity() { return capacity; } // end getCapacity + /** + * Sets the capacity of this cache map. + * + * @param c The new capacity for this cache map. + * @exception java.lang.IllegalArgumentException If the specified capacity is negative or zero. + */ public void setCapacity(int c) { if (c<=0) @@ -430,12 +581,24 @@ public class CacheMap implements Map } // end setCapacity + /** + * Returns the shrink percentage of this cache map. + * + * @return The shrink percentage of this cache map. + */ public int getShrinkPercentage() { return shrink_percentage; } // end getShrinkPercentage + /** + * Sets the shrink percentage of this cache map. + * + * @param p The new shrink percentage for this cache map. + * @exception java.lang.IllegalArgumentException If the specified shrink percentage is not in the + * range [1..100]. + */ public void setShrinkPercentage(int p) { if ((p<=0) || (p>100)) @@ -444,12 +607,23 @@ public class CacheMap implements Map } // end setShrinkPercentage + /** + * Returns the strategy object associated with the cache map. + * + * @return The strategy object associated with the cache map. + */ public CacheMapStrategy getStrategy() { return strategy; } // end getStrategy + /** + * Sets the strategy object associated with this cache map. + * + * @param s The new strategy object to be associated with the cache map. + * @exception java.lang.NullPointerException If the strategy object reference is null. + */ public void setStrategy(CacheMapStrategy s) { if (s==null) @@ -463,6 +637,11 @@ public class CacheMap implements Map *-------------------------------------------------------------------------------- */ + /** + * Causes the current size of the cache map to shrink by at least the shrink percentage specified + * in the constructor or in setShrinkPercentage. Cached items already reclaimed by the + * garbage collector are stripped out first. + */ public synchronized void shrink() { // Figure out how many elements to remove. diff --git a/src/com/silverwrist/util/cache/CacheMapEntry.java b/src/com/silverwrist/util/cache/CacheMapEntry.java index dafd491..6d7d73e 100644 --- a/src/com/silverwrist/util/cache/CacheMapEntry.java +++ b/src/com/silverwrist/util/cache/CacheMapEntry.java @@ -20,7 +20,19 @@ package com.silverwrist.util.cache; import java.lang.ref.*; import java.util.*; -final class CacheMapEntry implements Map.Entry +/** + * An entry from the CacheMap class. This object holds the key, a SoftReference + * to the value, the timestamp of its last access, and the number of times this element has been accessed. + * The latter two values are frequently used by an object implementing the CacheMapStrategy + * interface to compute a "figure of merit" for the entry that decides whether or not it gets discarded. + * + * @author Eric J. Bowersox <erbo@silcom.com> + * @version X + * @see CacheMap + * @see CacheMapStrategy + * @see java.lang.ref.SoftReference + */ +public final class CacheMapEntry implements Map.Entry { /*-------------------------------------------------------------------------------- * Attributes @@ -37,6 +49,12 @@ final class CacheMapEntry implements Map.Entry *-------------------------------------------------------------------------------- */ + /** + * Creates a new CacheMapEntry. + * + * @param key The key for this entry. + * @param value The value to be held in this entry. + */ CacheMapEntry(Object key, Object value) { this.key = key; @@ -45,6 +63,13 @@ final class CacheMapEntry implements Map.Entry } // end constructor + /** + * Creates a new CacheMapEntry. + * + * @param key The key for this entry. + * @param value The value to be held in this entry. + * @param q The ReferenceQueue to add the new reference generated by this constructor to. + */ CacheMapEntry(Object key, Object value, ReferenceQueue q) { this.key = key; @@ -58,6 +83,10 @@ final class CacheMapEntry implements Map.Entry *-------------------------------------------------------------------------------- */ + /** + * Called by the garbage collector on an object when garbage collection determines that there are no + * more references to the object. + */ protected void finalize() { key = null; @@ -71,26 +100,53 @@ final class CacheMapEntry implements Map.Entry *-------------------------------------------------------------------------------- */ + /** + * Returns the key corresponding to this entry. + * + * @return The key corresponding to this entry. + */ public final Object getKey() { return key; } // end getKey + /** + * Returns the value corresponding to this entry. If the value has already been reclaimed by the + * garbage collector, the return value is null. + * + * @return The value corresponding to this entry. + */ public final Object getValue() { return value.get(); } // end getValue + /** + * Replaces the value corresponding to this entry with the specified value. + * + * @param o The new value to be stored in this entry. + * @return The former value stored in this entry. If the value has already been reclaimed by the + * garbage collector, the return value is null. + */ public final Object setValue(Object o) { Object rc = value.get(); + value.clear(); value = new SoftReference(o); return rc; } // end setValue + /** + * Compares the specified object with this object for equality. The CacheMap considers + * two entries to be equivalent if they have the same key. (The value is not considered because it + * might already have been garbage-collected.) + * + * @param o The other object to compare to. + * @return true if the specified object is equal to this entry. + */ public final boolean equals(Object o) { // make sure the other element is a Map.Entry @@ -106,6 +162,11 @@ final class CacheMapEntry implements Map.Entry } // end equals + /** + * Returns the hash code value for this map entry, defined to be the hash code value of the key. + * + * @return The hash code value for this map entry. + */ public final int hashCode() { return ((key==null) ? 0 : key.hashCode()); @@ -117,6 +178,14 @@ final class CacheMapEntry implements Map.Entry *-------------------------------------------------------------------------------- */ + /** + * Replaces the value corresponding to this entry with the specified value. + * + * @param o The new value to be stored in this entry. + * @param q The ReferenceQueue to add the freshly-generated reference to. + * @return The former value stored in this entry. If the value has already been reclaimed by the + * garbage collector, the return value is null. + */ final Object setValue(Object o, ReferenceQueue q) { Object rc = value.get(); @@ -126,12 +195,24 @@ final class CacheMapEntry implements Map.Entry } // end setValue - final boolean isCleared() + /** + * Returns true if the value in this map entry has been cleared by the garbage collector. + * + * @return true if the value has been cleared, false if not. + */ + public final boolean isCleared() { return (value.get()==null); } // end isCleared + /** + * Returns true if the specified reference is the one contained in this map entry. + * + * @param r The reference we're trying to match. + * @return true/CODE> if this reference is the same as the one contained in the map entry, + * false if not. + */ final boolean matchReference(Reference r) { if (r instanceof SoftReference) @@ -141,6 +222,9 @@ final class CacheMapEntry implements Map.Entry } // end matchReference + /** + * Clears out all references held by this map entry. + */ final void discard() { key = null; @@ -149,24 +233,43 @@ final class CacheMapEntry implements Map.Entry } // end discard - final int getHits() + /** + * Returns the number of times this map entry has been referenced. + * + * @return The number of times this map entry has been referenced. + */ + public final int getHits() { return hits; } // end getHits - final long getTimestamp() + /** + * Returns the timestamp of the most recent reference to this map entry. + * + * @return The timestamp of the most recent reference to this map entry. + */ + public final long getTimestamp() { return timestamp; } // end getTimestamp - final long getAge(long tick) + /** + * Returns the number of milliseconds since this map entry was last referenced. + * + * @param tick The current time tick of the system in milliseconds. + * @return The number of milliseconds since this map entry was last referenced. + */ + public final long getAge(long tick) { return (tick - timestamp); } // end getAge + /** + * References this entry, which increments its "hit" counter and updates its current time stamp. + */ final void touch() { hits++; diff --git a/src/com/silverwrist/util/cache/CacheMapStrategy.java b/src/com/silverwrist/util/cache/CacheMapStrategy.java index 471e508..fb7aee4 100644 --- a/src/com/silverwrist/util/cache/CacheMapStrategy.java +++ b/src/com/silverwrist/util/cache/CacheMapStrategy.java @@ -17,8 +17,25 @@ */ package com.silverwrist.util.cache; +/** + * An interface which allows external code to define a "strategy" for use with the CacheMap + * object, in deciding which entries in the map are to be released. + * + * @author Eric J. Bowersox <erbo@silcom.com> + * @version X + * @see CacheMap + */ public interface CacheMapStrategy { + /** + * Computes a "figure of merit" for a given CacheMapEntry. This value is used by the + * CacheMap to determine which entries to remove; entries with lower "figure of merit" + * values get removed first. + * + * @param entry The cache map entry to compute a figure of merit for. + * @param tick The current time stamp, which may be used in a call to CacheMapEntry.getAge. + * @return The "figure of merit" value. + */ public abstract long getEntryValue(CacheMapEntry entry, long tick); } // end interface CacheMapStrategy