changeset 995:69dd2eb02dbf

TimedHashMap implements Map, new tests * netx/net/sourceforge/jnlp/util/TimedHashMap.java: implements Map interface, added all missing methods. (timeStamps) removed, refactored to only be composed of one backing map rather than two. * tests/netx/unit/net/sourceforge/jnlp/util/TimedHashMapTest.java: new test methods added
author Andrew Azores <aazores@redhat.com>
date Fri, 09 May 2014 17:30:54 -0400
parents 84fb0215c0bc
children f46fb24d32fb
files ChangeLog netx/net/sourceforge/jnlp/util/TimedHashMap.java tests/netx/unit/net/sourceforge/jnlp/util/TimedHashMapTest.java
diffstat 3 files changed, 310 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri May 09 16:19:42 2014 -0400
+++ b/ChangeLog	Fri May 09 17:30:54 2014 -0400
@@ -1,3 +1,11 @@
+2014-05-09  Andrew Azores  <aazores@redhat.com>
+
+	* netx/net/sourceforge/jnlp/util/TimedHashMap.java: implements Map
+	interface, added all missing methods. (timeStamps) removed, refactored to
+	only be composed of one backing map rather than two.
+	* tests/netx/unit/net/sourceforge/jnlp/util/TimedHashMapTest.java: new
+	test methods added
+
 2014-05-09  Andrew Azores  <aazores@redhat.com>
 
 	* netx/net/sourceforge/jnlp/cache/ResourceTracker.java: (selectByFlag)
--- a/netx/net/sourceforge/jnlp/util/TimedHashMap.java	Fri May 09 16:19:42 2014 -0400
+++ b/netx/net/sourceforge/jnlp/util/TimedHashMap.java	Fri May 09 17:30:54 2014 -0400
@@ -37,38 +37,90 @@
 
 package net.sourceforge.jnlp.util;
 
-import net.sourceforge.jnlp.util.logging.OutputController;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import static java.util.Objects.requireNonNull;
+import net.sourceforge.jnlp.util.logging.OutputController;
 
 /**
  * Simple utility class that extends HashMap by adding an expiry to the entries.
  *
- * This map stores entries, and returns them only if the entries were last accessed within time t=10 seconds
+ * This map stores entries, and returns them only if the entries were last accessed within a specified timeout period.
+ * Otherwise, null is returned.
+ * 
+ * This map does not allow null keys but does allow null values.
  *
  * @param K The key type
  * @param V The Object type
  */
-public class TimedHashMap<K, V> {
+public class TimedHashMap<K, V> implements Map<K, V> {
+
+    private static class TimedEntry<T> {
+        private final T value;
+        private long timestamp;
+
+        public TimedEntry(final T value) {
+            this.value = value;
+            updateTimestamp();
+        }
 
-    HashMap<K,V> actualMap = new HashMap<K,V>();
-    HashMap<K, Long> timeStamps = new HashMap<K, Long>();
-    Long expiry = 10000000000L;
+        public void updateTimestamp() {
+            timestamp = System.nanoTime();
+        }
+    }
 
-    public void setExpiry(long expiry) {
-        this.expiry = expiry;
+    private static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toNanos(10);
+
+    private final HashMap<K, TimedEntry<V>> actualMap = new HashMap<>();
+    private long timeout = DEFAULT_TIMEOUT;
+
+    public TimedHashMap() {
+        this(DEFAULT_TIMEOUT, TimeUnit.NANOSECONDS);
     }
 
     /**
-     * Store the item in the map and associate a timestamp with it
+     * Create a new map with a non-default entry timeout period
+     * @param unit the units of the timeout
+     * @param timeout the length of the timeout
+     */
+    public TimedHashMap(final long timeout, final TimeUnit unit) {
+        setTimeout(timeout, unit);
+    }
+
+    /**
+     * Specify how long (in nanoseconds) entries are valid for
+     * @param unit the units of the timeout
+     * @param timeout the length of the timeout
+     */
+    public void setTimeout(final long timeout, final TimeUnit unit) {
+        this.timeout = unit.toNanos(timeout);
+    }
+
+    /**
+     * Store the item in the map and associate a timestamp with it. null is not accepted as a key.
      *
      * @param key The key
      * @param value The value to store
      */
-    public V put(K key, V value) {
-        timeStamps.put(key, System.nanoTime());
-        return actualMap.put(key, value);
+    @Override
+    public V put(final K key, final V value) {
+        requireNonNull(key);
+        final TimedEntry<V> oldEntry = actualMap.get(key);
+        final V oldValue;
+        if (oldEntry != null) {
+            oldValue = oldEntry.value;
+        } else {
+            oldValue = null;
+        }
+        actualMap.put(key, new TimedEntry<>(value));
+        return oldValue;
     }
 
     /**
@@ -79,23 +131,94 @@
      *
      * @param key The key
      */
-    public V get(K key) {
-        Long now = System.nanoTime();
+    @Override
+    public V get(final Object key) {
+        final long now = System.nanoTime();
 
         if (actualMap.containsKey(key)) {
-            Long age = now - timeStamps.get(key);
+            final TimedEntry<V> timedEntry = actualMap.get(key);
+            final long age = now - timedEntry.timestamp;
 
             // Item exists. If it has not expired, renew its access time and return it
-            if (age <= expiry) {
-                OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Returning proxy " + actualMap.get(key) + " from cache for " + key);
-                timeStamps.put(key, System.nanoTime());
-                return actualMap.get(key);
+            if (age <= timeout) {
+                OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Returning entry " + actualMap.get(key) + " from cache for " + key);
+                timedEntry.updateTimestamp();
+                return timedEntry.value;
             } else {
-                OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Proxy cache for " + key + " has expired (age=" + (age * 1e-9) + " seconds)");
+                OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Cached entry for " + key + " has expired (age=" + (age * 1e-9) + " seconds)");
             }
         }
 
         return null;
     }
+
+    @Override
+    public boolean containsKey(final Object key) {
+        return actualMap.containsKey(key);
+    }
+
+    @Override
+    public int size() {
+        return actualMap.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return actualMap.isEmpty();
+    }
+
+    @Override
+    public boolean containsValue(final Object value) {
+        for (final TimedEntry<V> entry : actualMap.values()) {
+            if (Objects.equals(entry.value, value)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public V remove(final Object key) {
+        if (actualMap.containsKey(key)) {
+            return actualMap.remove(key).value;
+        }
+        return null;
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        for (final Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+            actualMap.put(entry.getKey(), new TimedEntry<V>(entry.getValue()));
+        }
+    }
+
+    @Override
+    public void clear() {
+        actualMap.clear();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return new HashSet<>(actualMap.keySet());
+    }
+
+    @Override
+    public Collection<V> values() {
+        final Collection<V> values = new ArrayList<>(actualMap.size());
+        for (final TimedEntry<V> value : actualMap.values()) {
+            values.add(value.value);
+        }
+        return values;
+    }
+
+    @Override
+    public Set<Map.Entry<K, V>> entrySet() {
+        final Map<K, V> strippedMap = new HashMap<>(actualMap.size());
+        for (final Map.Entry<K, TimedEntry<V>> entry : actualMap.entrySet()) {
+            strippedMap.put(entry.getKey(), entry.getValue().value);
+        }
+        return strippedMap.entrySet();
+    }
+
 }
 
--- a/tests/netx/unit/net/sourceforge/jnlp/util/TimedHashMapTest.java	Fri May 09 16:19:42 2014 -0400
+++ b/tests/netx/unit/net/sourceforge/jnlp/util/TimedHashMapTest.java	Fri May 09 17:30:54 2014 -0400
@@ -1,4 +1,4 @@
-/*
+/* TimedHashMapTest.java
    Copyright (C) 2014 Red Hat, Inc.
 
 This file is part of IcedTea.
@@ -37,34 +37,174 @@
 
 package net.sourceforge.jnlp.util;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+public class TimedHashMapTest {
+
+    private TimedHashMap<Object, Object> testMap;
+    private Object o1, o2, o3, o4;
 
-public class TimedHashMapTest {
+    @Before
+    public void resetTestMap() {
+        testMap = new TimedHashMap<>();
+        o1 = new Object();
+        o2 = new Object();
+        o3 = new Object();
+        o4 = new Object();
+    }
 
     @Test
     public void testPutAndGet() {
-        final TimedHashMap<Object, Object> map = new TimedHashMap<>();
-        final Object o1 = new Object(), o2 = new Object(), o3 = new Object(), o4 = new Object();
-        map.put(o1, o2);
-        map.put(o2, o4);
-        map.put(o3, o4);
-        assertEquals(o2, map.get(o1));
-        assertEquals(o4, map.get(o2));
-        assertEquals(o4, map.get(o3));
-        map.put(o1, o3);
-        assertEquals(o3, map.get(o1));
+        testMap.put(o1, o2);
+        testMap.put(o2, o4);
+        testMap.put(o3, o4);
+        assertEquals("map[o1] != o2", o2, testMap.get(o1));
+        assertEquals("map[o2] != o4", o4, testMap.get(o2));
+        assertEquals("map[o3] != o4", o4, testMap.get(o3));
+        testMap.put(o1, o3);
+        assertEquals("map[o1] != o3", o3, testMap.get(o1));
     }
 
     @Test
     public void testEntryExpiry() throws Exception {
-        final TimedHashMap<Object, Object> map = new TimedHashMap<>();
-        final Object o1 = new Object(), o2 = new Object();
-        map.setExpiry(0l); // immediate expiry
-        map.put(o1, o2);
+        testMap.setTimeout(0, TimeUnit.NANOSECONDS); // immediate expiry
+        testMap.put(o1, o2);
         Thread.sleep(5); // so we don't manage to put and get in the same nanosecond
-        assertNull(map.get(o1));
+        assertNull("map[o1] should have expired", testMap.get(o1));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testPutNullKey() {
+        testMap.put(null, o1);
+    }
+
+    @Test
+    public void testPutNullValue() {
+        testMap.put(o1, null);
+        assertNull("map[o1] != null", testMap.get(o1));
+        assertTrue("testMap should contain the key o1", testMap.containsKey(o1));
+    }
+
+    @Test
+    public void testContainsKey() {
+        testMap.put(o1, o2);
+        assertTrue("testMap should contain the key o1", testMap.containsKey(o1));
+    }
+
+    @Test
+    public void testSize() {
+        assertEquals(0, testMap.size());
+        testMap.put(o1, o2);
+        assertEquals(1, testMap.size());
+    }
+
+    @Test
+    public void testIsEmpty() {
+        assertTrue("map should be empty", testMap.isEmpty());
+        testMap.put(o1, o2);
+        assertFalse("map should not be empty", testMap.isEmpty());
+    }
+
+    @Test
+    public void testContainsValue() {
+        assertFalse("map should not contain o2", testMap.containsValue(o2));
+        testMap.put(o1, o2);
+        assertTrue("map does not contain o2", testMap.containsValue(o2));
+    }
+
+    @Test
+    public void testContainsValueNull() {
+        assertFalse("map should not contain null value", testMap.containsValue(null));
+        testMap.put(o1, null);
+        assertTrue("map does not contain null value", testMap.containsValue(null));
+    }
+
+    @Test
+    public void testRemove() {
+        testMap.put(o1, o2);
+        o3 = testMap.remove(o1);
+        assertEquals("o2 != o3", o2, o3);
+        assertFalse("map should not contain o1", testMap.containsKey(o1));
+    }
+
+    @Test
+    public void testRemoveFromEmpty() {
+        o2 = testMap.remove(o1);
+        assertNull("o2 should be null", o2);
+    }
+
+    @Test
+    public void testPutAll() {
+        final Map<Object, Object> newMap = new HashMap<>();
+        newMap.put(o1, o2);
+        newMap.put(o3, o4);
+        testMap.putAll(newMap);
+        assertTrue("map should contain key o1", testMap.containsKey(o1));
+        assertTrue("map should contain value o2", testMap.containsValue(o2));
+        assertTrue("map should contain key o3", testMap.containsKey(o3));
+        assertTrue("map should contain value o4", testMap.containsValue(o4));
+        assertEquals("map[o1] != o2", o2, testMap.get(o1));
+        assertEquals("map[o3] != o4", o4, testMap.get(o3));
+        assertEquals(2, testMap.size());
+    }
+
+    @Test
+    public void testClear() {
+        testMap.put(o1, o2);
+        testMap.clear();
+        assertEquals(0, testMap.size());
+        assertFalse("map should not contain key o1", testMap.containsKey(o1));
+        assertFalse("map should not contain value o2", testMap.containsValue(o2));
+    }
+
+    @Test
+    public void testKeySet() {
+        testMap.put(o1, o2);
+        Set<Object> keys = testMap.keySet();
+        assertNotNull("keyset should not be null", keys);
+        assertTrue("keyset should contain o1", keys.contains(o1));
+        assertEquals(1, keys.size());
+    }
+
+    @Test
+    public void testValues() {
+        testMap.put(o1, o2);
+        Collection<Object> values = testMap.values();
+        assertNotNull("values collection should not be null", values);
+        assertTrue("values collection should contain o2", values.contains(o2));
+        assertEquals(1, values.size());
+    }
+
+    @Test
+    public void testEntrySet() {
+        testMap.put(o1, o2);
+        testMap.put(o3, o4);
+        Set<Map.Entry<Object, Object>> entrySet = testMap.entrySet();
+        assertNotNull("entryset should not be null", entrySet);
+        assertEquals(2, entrySet.size());
+        for (final Map.Entry<Object, Object> entry : entrySet) {
+            final Object key = entry.getKey();
+            final Object value = entry.getValue();
+            if (key.equals(o1)) {
+                assertEquals("entry with key o1 should have value o2", o2, value);
+            }
+            if (key.equals(o3)) {
+                assertEquals("entry with key o3 should have value o4", o4, value);
+            }
+        }
     }
 
 }