270 lines
9.1 KiB
Java
270 lines
9.1 KiB
Java
/*
|
|
* 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 generation;
|
|
/** The underlying cache used to stored the cached values. */
|
|
private LruCache<K, CachedValue<V>> cache;
|
|
|
|
private ExpirableCache(LruCache<K, CachedValue<V>> cache) {
|
|
this.cache = cache;
|
|
generation = 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 cache.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) {
|
|
cache.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() {
|
|
generation.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, generation);
|
|
}
|
|
|
|
/**
|
|
* 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 value;
|
|
/** The generation at which the value was added to the cache. */
|
|
private final int generation;
|
|
/** The atomic integer storing the current generation of the cache it belongs to. */
|
|
private final AtomicInteger cacheGeneration;
|
|
|
|
/**
|
|
* @param cacheGeneration the atomic integer storing the generation of the cache in which this
|
|
* value will be stored
|
|
*/
|
|
public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) {
|
|
this.value = value;
|
|
this.cacheGeneration = cacheGeneration;
|
|
// Snapshot the current generation.
|
|
generation = this.cacheGeneration.get();
|
|
}
|
|
|
|
@Override
|
|
public V getValue() {
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public boolean isExpired() {
|
|
return generation != cacheGeneration.get();
|
|
}
|
|
}
|
|
}
|