diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java index a354be08..bb144842 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java @@ -330,7 +330,7 @@ abstract class AbstractDecoratedMap extends AbstractMap implements M } /** - * A simple Map.Entry implementaton. + * A simple Map.Entry implementation. */ static class BasicEntry implements Entry, Serializable { K mKey; diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java index b3199f56..50dd8839 100755 --- a/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java +++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/TimeoutMap.java @@ -34,7 +34,7 @@ import java.io.Serializable; import java.util.*; /** - * A {@code Map} implementation that removes (exipres) its elements after + * A {@code Map} implementation that removes (expires) its elements after * a given period. The map is by default backed by a {@link java.util.HashMap}, * or can be instantiated with any given {@code Map} as backing. *

@@ -67,7 +67,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi protected long expiryTime = 60000L; // 1 minute ////////////////////// - private volatile long nextExpiryTime; + private volatile long nextExpiryTime = Long.MAX_VALUE; ////////////////////// /** @@ -178,7 +178,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi * @return {@code true} if this map contains no key-value mappings. */ public boolean isEmpty() { - return (size() <= 0); + return size() <= 0; } /** @@ -208,7 +208,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi * @see #containsKey(java.lang.Object) */ public V get(Object pKey) { - TimedEntry entry = (TimedEntry) entries.get(pKey); + TimedEntry entry = (TimedEntry) entries.get(pKey); if (entry == null) { return null; @@ -236,7 +236,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi * {@code null} values. */ public V put(K pKey, V pValue) { - TimedEntry entry = (TimedEntry) entries.get(pKey); + TimedEntry entry = (TimedEntry) entries.get(pKey); V oldValue; if (entry == null) { @@ -272,7 +272,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi * {@code null} values. */ public V remove(Object pKey) { - TimedEntry entry = (TimedEntry) entries.remove(pKey); + TimedEntry entry = (TimedEntry) entries.remove(pKey); return (entry != null) ? entry.getValue() : null; } @@ -284,13 +284,12 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi init(); } - /*protected*/ TimedEntry createEntry(K pKey, V pValue) { - return new TimedEntry(pKey, pValue); + /*protected*/ TimedEntry createEntry(K pKey, V pValue) { + return new TimedEntry(pKey, pValue); } /** * Removes any expired mappings. - * */ protected void removeExpiredEntries() { // Remove any expired elements @@ -312,7 +311,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi long next = Long.MAX_VALUE; nextExpiryTime = next; // Avoid multiple runs... for (Iterator> iterator = new EntryIterator(); iterator.hasNext();) { - TimedEntry entry = (TimedEntry) iterator.next(); + TimedEntry entry = (TimedEntry) iterator.next(); //// long expires = entry.expires(); if (expires < next) { @@ -376,7 +375,7 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi while (mNext == null && mIterator.hasNext()) { Entry> entry = mIterator.next(); - TimedEntry timed = (TimedEntry) entry.getValue(); + TimedEntry timed = (TimedEntry) entry.getValue(); if (timed.isExpiredBy(mNow)) { // Remove from map, and continue @@ -425,19 +424,28 @@ public class TimeoutMap extends AbstractDecoratedMap implements Expi /** * Keeps track of timed objects */ - private class TimedEntry extends BasicEntry { + private class TimedEntry extends BasicEntry { private long mTimestamp; TimedEntry(K pKey, V pValue) { super(pKey, pValue); - mTimestamp = System.currentTimeMillis(); + updateTimestamp(); } public V setValue(V pValue) { - mTimestamp = System.currentTimeMillis(); + updateTimestamp(); return super.setValue(pValue); } + private void updateTimestamp() { + mTimestamp = System.currentTimeMillis(); + + long expires = expires(); + if (expires < nextExpiryTime) { + nextExpiryTime = expires; + } + } + final boolean isExpired() { return isExpiredBy(System.currentTimeMillis()); } diff --git a/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java b/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java index 1ca479b5..dfd297e0 100755 --- a/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java +++ b/common/common-lang/src/test/java/com/twelvemonkeys/util/TimeoutMapTest.java @@ -557,7 +557,7 @@ public class TimeoutMapTest extends MapAbstractTest { // NOTE: Only wait fist time, to avoid slooow tests synchronized (this) { try { - wait(60l); + wait(60L); } catch (InterruptedException e) { } @@ -591,7 +591,7 @@ public class TimeoutMapTest extends MapAbstractTest { try { wait(60l); } - catch (InterruptedException e) { + catch (InterruptedException ignore) { } } } @@ -651,5 +651,24 @@ public class TimeoutMapTest extends MapAbstractTest { assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey)); assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey)); } + + + @Test + public void testContainsKeyOnEmptyMap() { + // See #600 + Map timeoutMap = new TimeoutMap<>(30); + assertFalse(timeoutMap.containsKey("xyz")); + timeoutMap.put("xyz", "xyz"); + assertTrue(timeoutMap.containsKey("xyz")); + + try { + Thread.sleep(50); // Let the item expire + } + catch (InterruptedException ignore) { + } + + assertFalse(timeoutMap.containsKey("xyz")); + assertNull(timeoutMap.get("xyz")); + } }