diff options
Diffstat (limited to 'java/com/android/dialer/util/ExpirableCache.java')
-rw-r--r-- | java/com/android/dialer/util/ExpirableCache.java | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/java/com/android/dialer/util/ExpirableCache.java b/java/com/android/dialer/util/ExpirableCache.java new file mode 100644 index 000000000..2778a572c --- /dev/null +++ b/java/com/android/dialer/util/ExpirableCache.java @@ -0,0 +1,269 @@ +/* + * 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 com.android.dialer.util; + +import android.util.LruCache; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * An LRU cache in which all items can be marked as expired at a given time and it is possible to + * query whether a particular cached value is expired or not. + * + * <p>A typical use case for this is caching of values which are expensive to compute but which are + * still useful when out of date. + * + * <p>Consider a cache for contact information: + * + * <pre>{@code + * private ExpirableCache<String, Contact> mContactCache; + * }</pre> + * + * which stores the contact information for a given phone number. + * + * <p>When we need to store contact information for a given phone number, we can look up the info in + * the cache: + * + * <pre>{@code + * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber); + * }</pre> + * + * We might also want to fetch the contact information again if the item is expired. + * + * <pre> + * if (cachedContact.isExpired()) { + * fetchContactForNumber(phoneNumber, + * new FetchListener() { + * @Override + * public void onFetched(Contact contact) { + * mContactCache.put(phoneNumber, contact); + * } + * }); + * }</pre> + * + * and insert it back into the cache when the fetch completes. + * + * <p>At a certain point we want to expire the content of the cache because we know the content may + * no longer be up-to-date, for instance, when resuming the activity this is shown into: + * + * <pre> + * @Override + * protected onResume() { + * // We were paused for some time, the cached value might no longer be up to date. + * mContactCache.expireAll(); + * super.onResume(); + * } + * </pre> + * + * The values will be still available from the cache, but they will be expired. + * + * <p>If interested only in the value itself, not whether it is expired or not, one should use the + * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should + * use the {@link #get(Object)} method instead. + * + * <p>This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior + * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache + * by using the {@link #create(LruCache)} method, which can define a custom expiration policy. Since + * the underlying cache maps keys to cached values it can determine which items are expired and + * which are not, allowing for an implementation that evicts expired items before non expired ones. + * + * <p>This class is thread-safe. + * + * @param <K> the type of the keys + * @param <V> the type of the values + */ +@ThreadSafe +public class ExpirableCache<K, V> { + + /** + * The current generation of items added to the cache. + * + * <p>Items in the cache can belong to a previous generation, but in that case they would be + * expired. + * + * @see ExpirableCache.CachedValue#isExpired() + */ + private final AtomicInteger mGeneration; + /** The underlying cache used to stored the cached values. */ + private LruCache<K, CachedValue<V>> mCache; + + private ExpirableCache(LruCache<K, CachedValue<V>> cache) { + mCache = cache; + mGeneration = new AtomicInteger(0); + } + + /** + * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}. + * + * <p>The created cache takes ownership of the cache passed in as an argument. + * + * @param <K> the type of the keys + * @param <V> the type of the values + * @param cache the cache to store the value in + * @return the newly created expirable cache + * @throws IllegalArgumentException if the cache is not empty + */ + public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) { + return new ExpirableCache<K, V>(cache); + } + + /** + * Creates a new {@link ExpirableCache} with the given maximum size. + * + * @param <K> the type of the keys + * @param <V> the type of the values + * @return the newly created expirable cache + */ + public static <K, V> ExpirableCache<K, V> create(int maxSize) { + return create(new LruCache<K, CachedValue<V>>(maxSize)); + } + + /** + * Returns the cached value for the given key, or null if no value exists. + * + * <p>The cached value gives access both to the value associated with the key and whether it is + * expired or not. + * + * <p>If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)} + * instead. + * + * <p>If only wants values that are not expired, use {@link #get(Object)} instead. + * + * @param key the key to look up + */ + public CachedValue<V> getCachedValue(K key) { + return mCache.get(key); + } + + /** + * Returns the value for the given key, or null if no value exists. + * + * <p>When using this method, it is not possible to determine whether the value is expired or not. + * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using {@link + * #getCachedValue(Object)} to determine if an item is expired, one should use the item within the + * {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the value + * afterwards, since that is not guaranteed to return the same value or that the newly returned + * value is in the same state. + * + * @param key the key to look up + */ + public V getPossiblyExpired(K key) { + CachedValue<V> cachedValue = getCachedValue(key); + return cachedValue == null ? null : cachedValue.getValue(); + } + + /** + * Returns the value for the given key only if it is not expired, or null if no value exists or is + * expired. + * + * <p>This method will return null if either there is no value associated with this key or if the + * associated value is expired. + * + * @param key the key to look up + */ + public V get(K key) { + CachedValue<V> cachedValue = getCachedValue(key); + return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue(); + } + + /** + * Puts an item in the cache. + * + * <p>Newly added item will not be expired until {@link #expireAll()} is next called. + * + * @param key the key to look up + * @param value the value to associate with the key + */ + public void put(K key, V value) { + mCache.put(key, newCachedValue(value)); + } + + /** + * Mark all items currently in the cache as expired. + * + * <p>Newly added items after this call will be marked as not expired. + * + * <p>Expiring the items in the cache does not imply they will be evicted. + */ + public void expireAll() { + mGeneration.incrementAndGet(); + } + + /** + * Creates a new {@link CachedValue} instance to be stored in this cache. + * + * <p>Implementation of {@link LruCache#create(K)} can use this method to create a new entry. + */ + public CachedValue<V> newCachedValue(V value) { + return new GenerationalCachedValue<V>(value, mGeneration); + } + + /** + * A cached value stored inside the cache. + * + * <p>It provides access to the value stored in the cache but also allows to check whether the + * value is expired. + * + * @param <V> the type of value stored in the cache + */ + public interface CachedValue<V> { + + /** Returns the value stored in the cache for a given key. */ + V getValue(); + + /** + * Checks whether the value, while still being present in the cache, is expired. + * + * @return true if the value is expired + */ + boolean isExpired(); + } + + /** Cached values storing the generation at which they were added. */ + @Immutable + private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> { + + /** The value stored in the cache. */ + public final V mValue; + /** The generation at which the value was added to the cache. */ + private final int mGeneration; + /** The atomic integer storing the current generation of the cache it belongs to. */ + private final AtomicInteger mCacheGeneration; + + /** + * @param cacheGeneration the atomic integer storing the generation of the cache in which this + * value will be stored + */ + public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) { + mValue = value; + mCacheGeneration = cacheGeneration; + // Snapshot the current generation. + mGeneration = mCacheGeneration.get(); + } + + @Override + public V getValue() { + return mValue; + } + + @Override + public boolean isExpired() { + return mGeneration != mCacheGeneration.get(); + } + } +} |