javadoc & minor enhancements (implemented all methods in the Map API instead of subclassing AbstractMap)

git-svn-id: https://svn.apache.org/repos/asf/incubator/jsecurity/trunk@770461 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/ki/util/SoftHashMap.java b/core/src/main/java/org/apache/ki/util/SoftHashMap.java
index d7d29d6..ca82416 100644
--- a/core/src/main/java/org/apache/ki/util/SoftHashMap.java
+++ b/core/src/main/java/org/apache/ki/util/SoftHashMap.java
@@ -21,6 +21,8 @@
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 
 /**
@@ -30,75 +32,117 @@
  * want the cache to auto-resize itself based on memory constraints).
  * <p/>
  * Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory
- * limitations and garbage collection.  This ensures that the cache will not cause memory leaks by holding hard
+ * limitations and garbage collection.  This ensures that the cache will not cause memory leaks by holding strong
  * references to all of its values.
  * <p/>
  * This class is a generics-enabled Map based on initial ideas from Hienz Kabutz's and Sydney Redelinghuys's
  * <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version</a>, with continued
  * modifications.
+ * <p/>
+ * This implementation is thread-safe and usuable in highly concurrent environments.
  *
  * @author Les Hazlewood
  * @since 1.0
  */
-public class SoftHashMap<K, V> extends AbstractMap<K, V> {
+public class SoftHashMap<K, V> implements Map<K, V> {
 
-    /** The default value of the HARD_SIZE attribute, equal to 100. */
-    private static final int DEFAULT_HARD_SIZE = 100;
+    /** The default value of the RETENTION_SIZE attribute, equal to 100. */
+    private static final int DEFAULT_RETENTION_SIZE = 100;
 
     /** The internal HashMap that will hold the SoftReference. */
     private final Map<K, SoftValue<V, K>> map;
 
     /**
-     * The number of &quot;hard&quot; references to hold internally, that is, the number of instances to prevent
+     * The number of strong references to hold internally, that is, the number of instances to prevent
      * from being garbage collected automatically (unlike other soft references).
      */
-    private final int HARD_SIZE;
+    private final int RETENTION_SIZE;
 
-    /** The FIFO list of hard references (not to be garbage collected), order of last access. */
-    protected final Collection<V> hardCache;
-    private int hardCacheSize = 0;
+    /** The FIFO list of strong references (not to be garbage collected), order of last access. */
+    protected final Queue<V> strongReferences;
+    private int strongReferenceCount = 0;
 
     /** Reference queue for cleared SoftReference objects. */
     private final ReferenceQueue<? super V> queue = new ReferenceQueue<V>();
 
+    /**
+     * Creates a new SoftHashMap with a default retention size size of
+     * {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
+     *
+     * @see #SoftHashMap(int)
+     */
     public SoftHashMap() {
-        this(DEFAULT_HARD_SIZE);
+        this(DEFAULT_RETENTION_SIZE);
     }
 
+    /**
+     * Creates a new SoftHashMap with the specified retention size.
+     * <p/>
+     * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
+     * (ie 'retained') to prevent them from being eagerly garbage collected.  That is, the point of a SoftHashMap is to
+     * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
+     * elements retained after a GC due to the strong references.
+     * <p/>
+     * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
+     * than the actual <code>retentionSize</code> value.  This number is intended to be a best-effort retention low
+     * water mark.
+     *
+     * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
+     *                      (retained), preventing them from being eagerly garbage collected by the JVM.
+     */
     @SuppressWarnings({"unchecked"})
-    public SoftHashMap(int hardSize) {
+    public SoftHashMap(int retentionSize) {
         super();
-        HARD_SIZE = hardSize;
+        RETENTION_SIZE = Math.max(0, retentionSize);
         map = createSoftReferenceMap();
-        hardCache = createHardCache();
+        strongReferences = createStrongReferenceCache();
+    }
+
+    /**
+     * Creates a {@code SoftHashMap} backed by the specified {@code source}, with a default retention
+     * size of {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries).
+     *
+     * @param source the backing map to populate this {@code SoftHashMap}
+     * @see #SoftHashMap(Map,int)
+     */
+    public SoftHashMap(Map<K, V> source) {
+        this(DEFAULT_RETENTION_SIZE);
+        putAll(source);
+    }
+
+    /**
+     * Creates a {@code SoftHashMap} backed by the specified {@code source}, with the specified retention size.
+     * <p/>
+     * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced
+     * (ie 'retained') to prevent them from being eagerly garbage collected.  That is, the point of a SoftHashMap is to
+     * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n)
+     * elements retained after a GC due to the strong references.
+     * <p/>
+     * Note that in a highly concurrent environments the exact total number of strong references may differ slightly
+     * than the actual <code>retentionSize</code> value.  This number is intended to be a best-effort retention low
+     * water mark.
+     *
+     * @param source        the backing map to populate this {@code SoftHashMap}
+     * @param retentionSize the total number of most recent entries in the map that will be strongly referenced
+     *                      (retained), preventing them from being eagerly garbage collected by the JVM.
+     */
+    public SoftHashMap(Map<K, V> source, int retentionSize) {
+        this(retentionSize);
+        putAll(source);
     }
 
     protected Map<K, SoftValue<V, K>> createSoftReferenceMap() {
-        Map<K, SoftValue<V, K>> map;
-        if (JavaEnvironment.isAtLeastVersion15()) {
-            map = new java.util.concurrent.ConcurrentHashMap<K, SoftValue<V, K>>();
-        } else {
-            map = (Map) ClassUtils.newInstance("edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap");
-        }
-        return map;
+        return new ConcurrentHashMap<K, SoftValue<V, K>>();
     }
 
     @SuppressWarnings({"unchecked"})
-    protected Collection<V> createHardCache() {
-        Collection<V> c;
-        if (JavaEnvironment.isAtLeastVersion15()) {
-            c = new java.util.concurrent.ConcurrentLinkedQueue<V>();
-        } else {
-            c = (Collection) ClassUtils.newInstance("edu.emory.mathcs.backport.java.util.concurrent.ConcurrentLinkedQueue");
-        }
-        return c;
-    }
-
-    protected V pollQueue(Collection<V> queue) {
-        return ((Queue<V>) queue).poll();
+    protected Queue<V> createStrongReferenceCache() {
+        return new ConcurrentLinkedQueue<V>();
     }
 
     public V get(Object key) {
+        processQueue();
+
         V result = null;
         SoftValue<V, K> value = map.get(key);
 
@@ -109,48 +153,34 @@
                 //The wrapped value was garbage collected, so remove this entry from the backing map:
                 map.remove(key);
             } else {
-                //Add this value to the beginning of the 'hard' reference queue (FIFO).
-                addToHardCache(result);
-                trimHardCacheIfNecessary();
+                //Add this value to the beginning of the strong reference queue (FIFO).
+                addToStrongReferences(result);
+                trimStrongReferencesIfNecessary();
             }
         }
         return result;
     }
 
-    protected void addToHardCache(V result) {
-        hardCache.add(result);
-        hardCacheSize++;
+    protected void addToStrongReferences(V result) {
+        strongReferences.add(result);
+        strongReferenceCount++;
     }
 
-    protected void trimHardCacheIfNecessary() {
-        //trim the hard ref queue if necessary:
-        V trimmed = null;
-        if (hardCacheSize > HARD_SIZE) {
-            trimmed = pollHardCache();
-        }
-        if (trimmed != null) {
-            hardCacheSize--;
+    protected void trimStrongReferencesIfNecessary() {
+        //trim the strong ref queue if necessary:
+        while (strongReferenceCount > RETENTION_SIZE) {
+            pollStrongReferences();
         }
     }
 
-    protected V pollHardCache() {
-        V polled = null;
-        if (JavaEnvironment.isAtLeastVersion15() && hardCache instanceof Queue) {
-            polled = ((Queue<V>) hardCache).poll();
-        } else {
-            Iterator<V> i = hardCache.iterator();
-            if (i.hasNext()) {
-                polled = i.next();
-                i.remove();
-            }
-        }
+    protected V pollStrongReferences() {
+        V polled = strongReferences.poll();
         if (polled != null) {
-            hardCacheSize--;
+            strongReferenceCount--;
         }
         return polled;
     }
 
-
     /**
      * Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map
      * by looking them up using the SoftValue.key data member.
@@ -162,6 +192,54 @@
         }
     }
 
+    public boolean isEmpty() {
+        processQueue();
+        return map.isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        processQueue();
+        return map.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        processQueue();
+        Collection values = values();
+        return values != null && values.contains(value);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> m) {
+        if (m == null || m.isEmpty()) {
+            processQueue();
+            return;
+        }
+        for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public Set<K> keySet() {
+        processQueue();
+        return map.keySet();
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public Collection<V> values() {
+        processQueue();
+        if (map.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+        Collection<K> keys = map.keySet();
+        Collection<V> values = new ArrayList<V>(map.values().size());
+        for (K key : keys) {
+            V v = get(key);
+            if (v != null) {
+                values.add(v);
+            }
+        }
+        return values;
+    }
+
     /** Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection. */
     public V put(K key, V value) {
         processQueue(); // throw out garbage collected values first
@@ -177,7 +255,7 @@
     }
 
     public void clear() {
-        hardCache.clear();
+        strongReferences.clear();
         processQueue(); // throw out garbage collected values
         map.clear();
     }
@@ -190,8 +268,19 @@
     @SuppressWarnings({"unchecked"})
     public Set<Map.Entry<K, V>> entrySet() {
         processQueue(); // throw out garbage collected values first
-        Set set = map.entrySet();
-        return Collections.unmodifiableSet(set);
+        if (map.isEmpty()) {
+            return Collections.EMPTY_SET;
+        }
+
+        Map kvPairs = new HashMap(map.size());
+        Collection<K> keys = map.keySet();
+        for (K key : keys) {
+            V v = get(key);
+            if (v != null) {
+                kvPairs.put(key, v);
+            }
+        }
+        return kvPairs.entrySet();
     }
 
     /**