summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorJesse Wilson <jessewilson@google.com>2011-02-07 15:20:28 -0800
committerAndroid Git Automerger <android-git-automerger@android.com>2011-02-07 15:20:28 -0800
commitb0dd42c2b44ef61f116a7eea0e13237f55e07c9f (patch)
tree2c4ad0e3585aa509b2a27c154374e4c32567f8a6 /luni
parentb4c0d265e13c0eff12ce7c61ff5cb848e89e3bc4 (diff)
parente5720bcc0ac6587d152f2e8525eb2fc35c46bb0c (diff)
downloadlibcore-b0dd42c2b44ef61f116a7eea0e13237f55e07c9f.zip
libcore-b0dd42c2b44ef61f116a7eea0e13237f55e07c9f.tar.gz
libcore-b0dd42c2b44ef61f116a7eea0e13237f55e07c9f.tar.bz2
am e5720bcc: Avoid name collision in libcore.base vs android.util.LruCache.
* commit 'e5720bcc0ac6587d152f2e8525eb2fc35c46bb0c': Avoid name collision in libcore.base vs android.util.LruCache.
Diffstat (limited to 'luni')
-rw-r--r--luni/src/main/java/java/lang/ClassMembers.java6
-rw-r--r--luni/src/main/java/libcore/base/BasicLruCache.java114
-rw-r--r--luni/src/main/java/libcore/base/LruCache.java242
-rw-r--r--luni/src/test/java/libcore/base/BasicLruCacheTest.java149
-rw-r--r--luni/src/test/java/libcore/base/LruCacheTest.java356
5 files changed, 266 insertions, 601 deletions
diff --git a/luni/src/main/java/java/lang/ClassMembers.java b/luni/src/main/java/java/lang/ClassMembers.java
index c0cfab0..465c168 100644
--- a/luni/src/main/java/java/lang/ClassMembers.java
+++ b/luni/src/main/java/java/lang/ClassMembers.java
@@ -27,7 +27,7 @@ import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
-import libcore.base.LruCache;
+import libcore.base.BasicLruCache;
import org.apache.harmony.kernel.vm.LangAccess;
import org.apache.harmony.kernel.vm.ReflectionAccess;
@@ -41,8 +41,8 @@ import org.apache.harmony.kernel.vm.ReflectionAccess;
/*package*/ class ClassMembers<T> {
// TODO: Add constructors and fields.
- static final LruCache<Class<?>, ClassMembers<?>> cache
- = new LruCache<Class<?>, ClassMembers<?>>(16) {
+ static final BasicLruCache<Class<?>, ClassMembers<?>> cache
+ = new BasicLruCache<Class<?>, ClassMembers<?>>(16) {
@SuppressWarnings("unchecked") // use raw types since javac forbids "new ClassCache<?>(key)"
@Override protected ClassMembers<?> create(Class<?> key) {
return new ClassMembers(key);
diff --git a/luni/src/main/java/libcore/base/BasicLruCache.java b/luni/src/main/java/libcore/base/BasicLruCache.java
new file mode 100644
index 0000000..58bce24
--- /dev/null
+++ b/luni/src/main/java/libcore/base/BasicLruCache.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.base;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A minimal least-recently-used cache for libcore. Prefer {@code
+ * android.util.LruCache} where that is available.
+ */
+public class BasicLruCache<K, V> {
+ private final LinkedHashMap<K, V> map;
+ private final int maxSize;
+
+ public BasicLruCache(int maxSize) {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ this.maxSize = maxSize;
+ this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+ }
+
+ /**
+ * Returns the value for {@code key} if it exists in the cache or can be
+ * created by {@code #create}. If a value was returned, it is moved to the
+ * head of the queue. This returns null if a value is not cached and cannot
+ * be created.
+ */
+ public synchronized final V get(K key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ V result = map.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ result = create(key);
+
+ if (result != null) {
+ map.put(key, result);
+ trimToSize();
+ }
+ return result;
+ }
+
+ /**
+ * Caches {@code value} for {@code key}. The value is moved to the head of
+ * the queue.
+ *
+ * @return the previous value mapped by {@code key}. Although that entry is
+ * no longer cached, it has not been passed to {@link #entryEvicted}.
+ */
+ public synchronized final V put(K key, V value) {
+ if (key == null || value == null) {
+ throw new NullPointerException();
+ }
+
+ V previous = map.put(key, value);
+ trimToSize();
+ return previous;
+ }
+
+ private void trimToSize() {
+ while (map.size() > maxSize) {
+ Map.Entry<K, V> toEvict = map.eldest();
+
+ K key = toEvict.getKey();
+ V value = toEvict.getValue();
+ map.remove(key);
+
+ entryEvicted(key, value);
+ }
+ }
+
+ /**
+ * Called for entries that have reached the tail of the least recently used
+ * queue and are be removed. The default implementation does nothing.
+ */
+ protected void entryEvicted(K key, V value) {}
+
+ /**
+ * Called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The
+ * default implementation returns null.
+ */
+ protected V create(K key) {
+ return null;
+ }
+
+ /**
+ * Returns a copy of the current contents of the cache, ordered from least
+ * recently accessed to most recently accessed.
+ */
+ public synchronized Map<K, V> snapshot() {
+ return new LinkedHashMap<K, V>(map);
+ }
+}
diff --git a/luni/src/main/java/libcore/base/LruCache.java b/luni/src/main/java/libcore/base/LruCache.java
deleted file mode 100644
index 24a224e..0000000
--- a/luni/src/main/java/libcore/base/LruCache.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.base;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * A cache that holds strong references to a limited number of values. Each time
- * a value is accessed, it is moved to the head of a queue. When a value is
- * added to a full cache, the value at the end of that queue is evicted and may
- * become eligible for garbage collection.
- *
- * <p>If your cached values hold resources and need to be explicitly released,
- * override {@link #entryEvicted}. This method is only invoked when values are
- * evicted. Values replaced by calls to {@link #put} must be released manually.
- *
- * <p>If cache values should be computed on demand for the corresponding keys,
- * override {@link #create}. This simplifies the calling code, allowing it to
- * assume a value will always be returned, even when there's a cache miss.
- *
- * <p>By default, the cache size is measured in the number of entries. Override
- * {@link #sizeOf} to size the cache in different units. For, this cache is
- * limited to 4MiB of bitmaps:
- * <pre> {@code
- * int cacheSize = 4 * 1024 * 1024; // 4MiB
- * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
- * @Override protected int sizeOf(String key, Bitmap value) {
- * return value.getByteCount();
- * }
- * }}</pre>
- */
-public class LruCache<K, V> {
- private final LinkedHashMap<K, V> map;
-
- /** Size of this cache in units. Not necessarily the number of elements. */
- private int size;
- private final int maxSize;
-
- private int putCount;
- private int createCount;
- private int evictionCount;
- private int hitCount;
- private int missCount;
-
- /**
- * @param maxSize for caches that do not override {@link #sizeOf}, this is
- * the maximum number of entries in the cache. For all other caches,
- * this is the maximum sum of the sizes of the entries in this cache.
- */
- public LruCache(int maxSize) {
- if (maxSize <= 0) {
- throw new IllegalArgumentException("maxSize <= 0");
- }
- this.maxSize = maxSize;
- this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
- }
-
- /**
- * Returns the value for {@code key} if it exists in the cache or can be
- * created by {@code #create}. If a value was returned, it is moved to the
- * head of the queue. This returns null if a value is not cached and cannot
- * be created.
- */
- public synchronized final V get(K key) {
- if (key == null) {
- throw new NullPointerException();
- }
-
- V result = map.get(key);
- if (result != null) {
- hitCount++;
- return result;
- }
-
- missCount++;
-
- // TODO: release the lock while calling this potentially slow user code
- result = create(key);
-
- if (result != null) {
- createCount++;
- size += safeSizeOf(key, result);
- map.put(key, result);
- trimToSize();
- }
- return result;
- }
-
- /**
- * Caches {@code value} for {@code key}. The value is moved to the head of
- * the queue.
- *
- * @return the previous value mapped by {@code key}. Although that entry is
- * no longer cached, it has not been passed to {@link #entryEvicted}.
- */
- public synchronized final V put(K key, V value) {
- if (key == null || value == null) {
- throw new NullPointerException();
- }
-
- putCount++;
- size += safeSizeOf(key, value);
- V previous = map.put(key, value);
- if (previous != null) {
- size -= safeSizeOf(key, previous);
- }
- trimToSize();
- return previous;
- }
-
- private void trimToSize() {
- while (size > maxSize) {
- Map.Entry<K, V> toEvict = map.eldest();
- if (toEvict == null) {
- break; // map is empty so size should be 0! Throw an error below
- }
-
- K key = toEvict.getKey();
- V value = toEvict.getValue();
- map.remove(key);
- size -= safeSizeOf(key, value);
- evictionCount++;
-
- // TODO: release the lock while calling this potentially slow user code
- entryEvicted(key, value);
- }
-
- if (size < 0 || (map.isEmpty() && size != 0)) {
- throw new IllegalStateException(getClass().getName()
- + ".sizeOf() is reporting inconsistent results!");
- }
- }
-
- /**
- * Called for entries that have reached the tail of the least recently used
- * queue and are be removed. The default implementation does nothing.
- */
- protected void entryEvicted(K key, V value) {}
-
- /**
- * Called after a cache miss to compute a value for the corresponding key.
- * Returns the computed value or null if no value can be computed. The
- * default implementation returns null.
- */
- protected V create(K key) {
- return null;
- }
-
- private int safeSizeOf(K key, V value) {
- int result = sizeOf(key, value);
- if (result < 0) {
- throw new IllegalStateException("Negative size: " + key + "=" + value);
- }
- return result;
- }
-
- /**
- * Returns the size of the entry for {@code key} and {@code value} in
- * user-defined units. The default implementation returns 1 so that size
- * is the number of entries and max size is the maximum number of entries.
- *
- * <p>An entry's size must not change while it is in the cache.
- */
- protected int sizeOf(K key, V value) {
- return 1;
- }
-
- /**
- * For caches that do not override {@link #sizeOf}, this is the number of
- * entries in the cache. For all other caches, this is the sum of the sizes
- * of the entries in this cache.
- */
- public synchronized final int size() {
- return size;
- }
-
- /**
- * Returns the number of times {@link #get} returned a value.
- */
- public synchronized final int hitCount() {
- return hitCount;
- }
-
- /**
- * Returns the number of times {@link #get} returned null or required a new
- * value to be created.
- */
- public synchronized final int missCount() {
- return missCount;
- }
-
- /**
- * Returns the number of times {@link #create(Object)} returned a value.
- */
- public synchronized final int createCount() {
- return createCount;
- }
-
- /**
- * Returns the number of times {@link #put} was called.
- */
- public synchronized final int putCount() {
- return putCount;
- }
-
- /**
- * Returns the number of values that have been evicted.
- */
- public synchronized final int evictionCount() {
- return evictionCount;
- }
-
- /**
- * Returns a copy of the current contents of the cache, ordered from least
- * recently accessed to most recently accessed.
- */
- public synchronized Map<K, V> snapshot() {
- return new LinkedHashMap<K, V>(map);
- }
-
- @Override public synchronized final String toString() {
- int accesses = hitCount + missCount;
- int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
- return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
- maxSize, hitCount, missCount, hitPercent);
- }
-}
diff --git a/luni/src/test/java/libcore/base/BasicLruCacheTest.java b/luni/src/test/java/libcore/base/BasicLruCacheTest.java
new file mode 100644
index 0000000..5aa0d72
--- /dev/null
+++ b/luni/src/test/java/libcore/base/BasicLruCacheTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.base;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public final class BasicLruCacheTest extends TestCase {
+
+ public void testCreateOnCacheMiss() {
+ BasicLruCache<String, String> cache = newCreatingCache();
+ String created = cache.get("aa");
+ assertEquals("created-aa", created);
+ }
+
+ public void testNoCreateOnCacheHit() {
+ BasicLruCache<String, String> cache = newCreatingCache();
+ cache.put("aa", "put-aa");
+ assertEquals("put-aa", cache.get("aa"));
+ }
+
+ public void testConstructorDoesNotAllowZeroCacheSize() {
+ try {
+ new BasicLruCache<String, String>(0);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testCannotPutNullKey() {
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3);
+ try {
+ cache.put(null, "A");
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testCannotPutNullValue() {
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3);
+ try {
+ cache.put("a", null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testToString() {
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3);
+ assertEquals("LruCache[maxSize=3,hits=0,misses=0,hitRate=0%]", cache.toString());
+
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("d", "D");
+
+ cache.get("a"); // miss
+ cache.get("b"); // hit
+ cache.get("c"); // hit
+ cache.get("d"); // hit
+ cache.get("e"); // miss
+
+ assertEquals("LruCache[maxSize=3,hits=3,misses=2,hitRate=60%]", cache.toString());
+ }
+
+ public void testEvictionWithSingletonCache() {
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(1);
+ cache.put("a", "A");
+ cache.put("b", "B");
+ assertSnapshot(cache, "b", "B");
+ }
+
+ public void testEntryEvictedWhenFull() {
+ List<String> expectedEvictionLog = new ArrayList<String>();
+ final List<String> evictionLog = new ArrayList<String>();
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3) {
+ @Override protected void entryEvicted(String key, String value) {
+ evictionLog.add(key + "=" + value);
+ }
+ };
+
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ assertEquals(expectedEvictionLog, evictionLog);
+
+ cache.put("d", "D");
+ expectedEvictionLog.add("a=A");
+ assertEquals(expectedEvictionLog, evictionLog);
+ }
+
+ /**
+ * Replacing the value for a key doesn't cause an eviction but it does bring
+ * the replaced entry to the front of the queue.
+ */
+ public void testPutDoesNotCauseEviction() {
+ final List<String> evictionLog = new ArrayList<String>();
+ List<String> expectedEvictionLog = new ArrayList<String>();
+ BasicLruCache<String, String> cache = new BasicLruCache<String, String>(3) {
+ @Override protected void entryEvicted(String key, String value) {
+ evictionLog.add(key + "=" + value);
+ }
+ };
+
+ cache.put("a", "A");
+ cache.put("b", "B");
+ cache.put("c", "C");
+ cache.put("b", "B2");
+ assertEquals(expectedEvictionLog, evictionLog);
+ assertSnapshot(cache, "a", "A", "c", "C", "b", "B2");
+ }
+
+
+ private BasicLruCache<String, String> newCreatingCache() {
+ return new BasicLruCache<String, String>(3) {
+ @Override protected String create(String key) {
+ return (key.length() > 1) ? ("created-" + key) : null;
+ }
+ };
+ }
+
+ private <T> void assertSnapshot(BasicLruCache<T, T> cache, T... keysAndValues) {
+ List<T> actualKeysAndValues = new ArrayList<T>();
+ for (Map.Entry<T, T> entry : cache.snapshot().entrySet()) {
+ actualKeysAndValues.add(entry.getKey());
+ actualKeysAndValues.add(entry.getValue());
+ }
+
+ // assert using lists because order is important for LRUs
+ assertEquals(Arrays.asList(keysAndValues), actualKeysAndValues);
+ }
+}
diff --git a/luni/src/test/java/libcore/base/LruCacheTest.java b/luni/src/test/java/libcore/base/LruCacheTest.java
deleted file mode 100644
index c94c1ce..0000000
--- a/luni/src/test/java/libcore/base/LruCacheTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package libcore.base;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import junit.framework.TestCase;
-
-public final class LruCacheTest extends TestCase {
- private int expectedCreateCount;
- private int expectedPutCount;
- private int expectedHitCount;
- private int expectedMissCount;
- private int expectedEvictionCount;
-
- public void testStatistics() {
- LruCache<String, String> cache = new LruCache<String, String>(3);
- assertStatistics(cache);
-
- assertEquals(null, cache.put("a", "A"));
- expectedPutCount++;
- assertStatistics(cache);
- assertHit(cache, "a", "A");
- assertSnapshot(cache, "a", "A");
-
- assertEquals(null, cache.put("b", "B"));
- expectedPutCount++;
- assertStatistics(cache);
- assertHit(cache, "a", "A");
- assertHit(cache, "b", "B");
- assertSnapshot(cache, "a", "A", "b", "B");
-
- assertEquals(null, cache.put("c", "C"));
- expectedPutCount++;
- assertStatistics(cache);
- assertHit(cache, "a", "A");
- assertHit(cache, "b", "B");
- assertHit(cache, "c", "C");
- assertSnapshot(cache, "a", "A", "b", "B", "c", "C");
-
- assertEquals(null, cache.put("d", "D"));
- expectedPutCount++;
- expectedEvictionCount++; // a should have been evicted
- assertStatistics(cache);
- assertMiss(cache, "a");
- assertHit(cache, "b", "B");
- assertHit(cache, "c", "C");
- assertHit(cache, "d", "D");
- assertHit(cache, "b", "B");
- assertHit(cache, "c", "C");
- assertSnapshot(cache, "d", "D", "b", "B", "c", "C");
-
- assertEquals(null, cache.put("e", "E"));
- expectedPutCount++;
- expectedEvictionCount++; // d should have been evicted
- assertStatistics(cache);
- assertMiss(cache, "d");
- assertMiss(cache, "a");
- assertHit(cache, "e", "E");
- assertHit(cache, "b", "B");
- assertHit(cache, "c", "C");
- assertSnapshot(cache, "e", "E", "b", "B", "c", "C");
- }
-
- public void testStatisticsWithCreate() {
- LruCache<String, String> cache = newCreatingCache();
- assertStatistics(cache);
-
- assertCreated(cache, "aa", "created-aa");
- assertHit(cache, "aa", "created-aa");
- assertSnapshot(cache, "aa", "created-aa");
-
- assertCreated(cache, "bb", "created-bb");
- assertMiss(cache, "c");
- assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb");
-
- assertCreated(cache, "cc", "created-cc");
- assertSnapshot(cache, "aa", "created-aa", "bb", "created-bb", "cc", "created-cc");
-
- expectedEvictionCount++; // aa will be evicted
- assertCreated(cache, "dd", "created-dd");
- assertSnapshot(cache, "bb", "created-bb", "cc", "created-cc", "dd", "created-dd");
-
- expectedEvictionCount++; // bb will be evicted
- assertCreated(cache, "aa", "created-aa");
- assertSnapshot(cache, "cc", "created-cc", "dd", "created-dd", "aa", "created-aa");
- }
-
- public void testCreateOnCacheMiss() {
- LruCache<String, String> cache = newCreatingCache();
- String created = cache.get("aa");
- assertEquals("created-aa", created);
- }
-
- public void testNoCreateOnCacheHit() {
- LruCache<String, String> cache = newCreatingCache();
- cache.put("aa", "put-aa");
- assertEquals("put-aa", cache.get("aa"));
- }
-
- public void testConstructorDoesNotAllowZeroCacheSize() {
- try {
- new LruCache<String, String>(0);
- fail();
- } catch (IllegalArgumentException expected) {
- }
- }
-
- public void testCannotPutNullKey() {
- LruCache<String, String> cache = new LruCache<String, String>(3);
- try {
- cache.put(null, "A");
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- public void testCannotPutNullValue() {
- LruCache<String, String> cache = new LruCache<String, String>(3);
- try {
- cache.put("a", null);
- fail();
- } catch (NullPointerException expected) {
- }
- }
-
- public void testToString() {
- LruCache<String, String> cache = new LruCache<String, String>(3);
- assertEquals("LruCache[maxSize=3,hits=0,misses=0,hitRate=0%]", cache.toString());
-
- cache.put("a", "A");
- cache.put("b", "B");
- cache.put("c", "C");
- cache.put("d", "D");
-
- cache.get("a"); // miss
- cache.get("b"); // hit
- cache.get("c"); // hit
- cache.get("d"); // hit
- cache.get("e"); // miss
-
- assertEquals("LruCache[maxSize=3,hits=3,misses=2,hitRate=60%]", cache.toString());
- }
-
- public void testEvictionWithSingletonCache() {
- LruCache<String, String> cache = new LruCache<String, String>(1);
- cache.put("a", "A");
- cache.put("b", "B");
- assertSnapshot(cache, "b", "B");
- }
-
- public void testEntryEvictedWhenFull() {
- List<String> expectedEvictionLog = new ArrayList<String>();
- final List<String> evictionLog = new ArrayList<String>();
- LruCache<String, String> cache = new LruCache<String, String>(3) {
- @Override protected void entryEvicted(String key, String value) {
- evictionLog.add(key + "=" + value);
- }
- };
-
- cache.put("a", "A");
- cache.put("b", "B");
- cache.put("c", "C");
- assertEquals(expectedEvictionLog, evictionLog);
-
- cache.put("d", "D");
- expectedEvictionLog.add("a=A");
- assertEquals(expectedEvictionLog, evictionLog);
- }
-
- /**
- * Replacing the value for a key doesn't cause an eviction but it does bring
- * the replaced entry to the front of the queue.
- */
- public void testPutDoesNotCauseEviction() {
- final List<String> evictionLog = new ArrayList<String>();
- List<String> expectedEvictionLog = new ArrayList<String>();
- LruCache<String, String> cache = new LruCache<String, String>(3) {
- @Override protected void entryEvicted(String key, String value) {
- evictionLog.add(key + "=" + value);
- }
- };
-
- cache.put("a", "A");
- cache.put("b", "B");
- cache.put("c", "C");
- cache.put("b", "B2");
- assertEquals(expectedEvictionLog, evictionLog);
- assertSnapshot(cache, "a", "A", "c", "C", "b", "B2");
- }
-
- public void testCustomSizesImpactsSize() {
- LruCache<String, String> cache = new LruCache<String, String>(10) {
- @Override protected int sizeOf(String key, String value) {
- return key.length() + value.length();
- }
- };
-
- assertEquals(0, cache.size());
- cache.put("a", "AA");
- assertEquals(3, cache.size());
- cache.put("b", "BBBB");
- assertEquals(8, cache.size());
- cache.put("a", "");
- assertEquals(6, cache.size());
- }
-
- public void testEvictionWithCustomSizes() {
- LruCache<String, String> cache = new LruCache<String, String>(4) {
- @Override protected int sizeOf(String key, String value) {
- return value.length();
- }
- };
-
- cache.put("a", "AAAA");
- assertSnapshot(cache, "a", "AAAA");
- cache.put("b", "BBBB"); // should evict a
- assertSnapshot(cache, "b", "BBBB");
- cache.put("c", "CC"); // should evict b
- assertSnapshot(cache, "c", "CC");
- cache.put("d", "DD");
- assertSnapshot(cache, "c", "CC", "d", "DD");
- cache.put("e", "E"); // should evict c
- assertSnapshot(cache, "d", "DD", "e", "E");
- cache.put("f", "F");
- assertSnapshot(cache, "d", "DD", "e", "E", "f", "F");
- cache.put("g", "G"); // should evict d
- assertSnapshot(cache, "e", "E", "f", "F", "g", "G");
- cache.put("h", "H");
- assertSnapshot(cache, "e", "E", "f", "F", "g", "G", "h", "H");
- cache.put("i", "III"); // should evict e, f, and g
- assertSnapshot(cache, "h", "H", "i", "III");
- cache.put("j", "JJJ"); // should evict h and i
- assertSnapshot(cache, "j", "JJJ");
- }
-
- public void testEvictionThrowsWhenSizesAreInconsistent() {
- LruCache<String, int[]> cache = new LruCache<String, int[]>(4) {
- @Override protected int sizeOf(String key, int[] value) {
- return value[0];
- }
- };
-
- int[] a = { 4 };
- cache.put("a", a);
-
- // get the cache size out of sync
- a[0] = 1;
- assertEquals(4, cache.size());
-
- // evict something
- try {
- cache.put("b", new int[] { 2 });
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- public void testEvictionThrowsWhenSizesAreNegative() {
- LruCache<String, String> cache = new LruCache<String, String>(4) {
- @Override protected int sizeOf(String key, String value) {
- return -1;
- }
- };
-
- try {
- cache.put("a", "A");
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- /**
- * Naive caches evict at most one element at a time. This is problematic
- * because evicting a small element may be insufficient to make room for a
- * large element.
- */
- public void testDifferentElementSizes() {
- LruCache<String, String> cache = new LruCache<String, String>(10) {
- @Override protected int sizeOf(String key, String value) {
- return value.length();
- }
- };
-
- cache.put("a", "1");
- cache.put("b", "12345678");
- cache.put("c", "1");
- assertSnapshot(cache, "a", "1", "b", "12345678", "c", "1");
- cache.put("d", "12345678"); // should evict a and b
- assertSnapshot(cache, "c", "1", "d", "12345678");
- cache.put("e", "12345678"); // should evict c and d
- assertSnapshot(cache, "e", "12345678");
- }
-
- private LruCache<String, String> newCreatingCache() {
- return new LruCache<String, String>(3) {
- @Override protected String create(String key) {
- return (key.length() > 1) ? ("created-" + key) : null;
- }
- };
- }
-
- private void assertHit(LruCache<String, String> cache, String key, String value) {
- assertEquals(value, cache.get(key));
- expectedHitCount++;
- assertStatistics(cache);
- }
-
- private void assertMiss(LruCache<String, String> cache, String key) {
- assertEquals(null, cache.get(key));
- expectedMissCount++;
- assertStatistics(cache);
- }
-
- private void assertCreated(LruCache<String, String> cache, String key, String value) {
- assertEquals(value, cache.get(key));
- expectedMissCount++;
- expectedCreateCount++;
- assertStatistics(cache);
- }
-
- private void assertStatistics(LruCache<?, ?> cache) {
- assertEquals("create count", expectedCreateCount, cache.createCount());
- assertEquals("put count", expectedPutCount, cache.putCount());
- assertEquals("hit count", expectedHitCount, cache.hitCount());
- assertEquals("miss count", expectedMissCount, cache.missCount());
- assertEquals("eviction count", expectedEvictionCount, cache.evictionCount());
- }
-
- private <T> void assertSnapshot(LruCache<T, T> cache, T... keysAndValues) {
- List<T> actualKeysAndValues = new ArrayList<T>();
- for (Map.Entry<T, T> entry : cache.snapshot().entrySet()) {
- actualKeysAndValues.add(entry.getKey());
- actualKeysAndValues.add(entry.getValue());
- }
-
- // assert using lists because order is important for LRUs
- assertEquals(Arrays.asList(keysAndValues), actualKeysAndValues);
- }
-}