Mercurial > hg > release > icedtea-web-1.6
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); + } + } } }