diff options
author | Jesse Wilson <jessewilson@google.com> | 2011-02-07 15:20:28 -0800 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2011-02-07 15:20:28 -0800 |
commit | b0dd42c2b44ef61f116a7eea0e13237f55e07c9f (patch) | |
tree | 2c4ad0e3585aa509b2a27c154374e4c32567f8a6 | |
parent | b4c0d265e13c0eff12ce7c61ff5cb848e89e3bc4 (diff) | |
parent | e5720bcc0ac6587d152f2e8525eb2fc35c46bb0c (diff) | |
download | libcore-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.
-rw-r--r-- | luni/src/main/java/java/lang/ClassMembers.java | 6 | ||||
-rw-r--r-- | luni/src/main/java/libcore/base/BasicLruCache.java | 114 | ||||
-rw-r--r-- | luni/src/main/java/libcore/base/LruCache.java | 242 | ||||
-rw-r--r-- | luni/src/test/java/libcore/base/BasicLruCacheTest.java | 149 | ||||
-rw-r--r-- | luni/src/test/java/libcore/base/LruCacheTest.java | 356 |
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); - } -} |