1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dialer.util; 18 19 import android.util.LruCache; 20 import java.util.concurrent.atomic.AtomicInteger; 21 import javax.annotation.concurrent.Immutable; 22 import javax.annotation.concurrent.ThreadSafe; 23 24 /** 25 * An LRU cache in which all items can be marked as expired at a given time and it is possible to 26 * query whether a particular cached value is expired or not. 27 * 28 * <p>A typical use case for this is caching of values which are expensive to compute but which are 29 * still useful when out of date. 30 * 31 * <p>Consider a cache for contact information: 32 * 33 * <pre>{@code 34 * private ExpirableCache<String, Contact> mContactCache; 35 * }</pre> 36 * 37 * which stores the contact information for a given phone number. 38 * 39 * <p>When we need to store contact information for a given phone number, we can look up the info in 40 * the cache: 41 * 42 * <pre>{@code 43 * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber); 44 * }</pre> 45 * 46 * We might also want to fetch the contact information again if the item is expired. 47 * 48 * <pre> 49 * if (cachedContact.isExpired()) { 50 * fetchContactForNumber(phoneNumber, 51 * new FetchListener() { 52 * @Override 53 * public void onFetched(Contact contact) { 54 * mContactCache.put(phoneNumber, contact); 55 * } 56 * }); 57 * }</pre> 58 * 59 * and insert it back into the cache when the fetch completes. 60 * 61 * <p>At a certain point we want to expire the content of the cache because we know the content may 62 * no longer be up-to-date, for instance, when resuming the activity this is shown into: 63 * 64 * <pre> 65 * @Override 66 * protected onResume() { 67 * // We were paused for some time, the cached value might no longer be up to date. 68 * mContactCache.expireAll(); 69 * super.onResume(); 70 * } 71 * </pre> 72 * 73 * The values will be still available from the cache, but they will be expired. 74 * 75 * <p>If interested only in the value itself, not whether it is expired or not, one should use the 76 * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should 77 * use the {@link #get(Object)} method instead. 78 * 79 * <p>This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior 80 * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache 81 * by using the {@link #create(LruCache)} method, which can define a custom expiration policy. Since 82 * the underlying cache maps keys to cached values it can determine which items are expired and 83 * which are not, allowing for an implementation that evicts expired items before non expired ones. 84 * 85 * <p>This class is thread-safe. 86 * 87 * @param <K> the type of the keys 88 * @param <V> the type of the values 89 */ 90 @ThreadSafe 91 public class ExpirableCache<K, V> { 92 93 /** 94 * The current generation of items added to the cache. 95 * 96 * <p>Items in the cache can belong to a previous generation, but in that case they would be 97 * expired. 98 * 99 * @see ExpirableCache.CachedValue#isExpired() 100 */ 101 private final AtomicInteger mGeneration; 102 /** The underlying cache used to stored the cached values. */ 103 private LruCache<K, CachedValue<V>> mCache; 104 ExpirableCache(LruCache<K, CachedValue<V>> cache)105 private ExpirableCache(LruCache<K, CachedValue<V>> cache) { 106 mCache = cache; 107 mGeneration = new AtomicInteger(0); 108 } 109 110 /** 111 * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}. 112 * 113 * <p>The created cache takes ownership of the cache passed in as an argument. 114 * 115 * @param <K> the type of the keys 116 * @param <V> the type of the values 117 * @param cache the cache to store the value in 118 * @return the newly created expirable cache 119 * @throws IllegalArgumentException if the cache is not empty 120 */ create(LruCache<K, CachedValue<V>> cache)121 public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) { 122 return new ExpirableCache<K, V>(cache); 123 } 124 125 /** 126 * Creates a new {@link ExpirableCache} with the given maximum size. 127 * 128 * @param <K> the type of the keys 129 * @param <V> the type of the values 130 * @return the newly created expirable cache 131 */ create(int maxSize)132 public static <K, V> ExpirableCache<K, V> create(int maxSize) { 133 return create(new LruCache<K, CachedValue<V>>(maxSize)); 134 } 135 136 /** 137 * Returns the cached value for the given key, or null if no value exists. 138 * 139 * <p>The cached value gives access both to the value associated with the key and whether it is 140 * expired or not. 141 * 142 * <p>If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)} 143 * instead. 144 * 145 * <p>If only wants values that are not expired, use {@link #get(Object)} instead. 146 * 147 * @param key the key to look up 148 */ getCachedValue(K key)149 public CachedValue<V> getCachedValue(K key) { 150 return mCache.get(key); 151 } 152 153 /** 154 * Returns the value for the given key, or null if no value exists. 155 * 156 * <p>When using this method, it is not possible to determine whether the value is expired or not. 157 * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using {@link 158 * #getCachedValue(Object)} to determine if an item is expired, one should use the item within the 159 * {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the value 160 * afterwards, since that is not guaranteed to return the same value or that the newly returned 161 * value is in the same state. 162 * 163 * @param key the key to look up 164 */ getPossiblyExpired(K key)165 public V getPossiblyExpired(K key) { 166 CachedValue<V> cachedValue = getCachedValue(key); 167 return cachedValue == null ? null : cachedValue.getValue(); 168 } 169 170 /** 171 * Returns the value for the given key only if it is not expired, or null if no value exists or is 172 * expired. 173 * 174 * <p>This method will return null if either there is no value associated with this key or if the 175 * associated value is expired. 176 * 177 * @param key the key to look up 178 */ get(K key)179 public V get(K key) { 180 CachedValue<V> cachedValue = getCachedValue(key); 181 return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue(); 182 } 183 184 /** 185 * Puts an item in the cache. 186 * 187 * <p>Newly added item will not be expired until {@link #expireAll()} is next called. 188 * 189 * @param key the key to look up 190 * @param value the value to associate with the key 191 */ put(K key, V value)192 public void put(K key, V value) { 193 mCache.put(key, newCachedValue(value)); 194 } 195 196 /** 197 * Mark all items currently in the cache as expired. 198 * 199 * <p>Newly added items after this call will be marked as not expired. 200 * 201 * <p>Expiring the items in the cache does not imply they will be evicted. 202 */ expireAll()203 public void expireAll() { 204 mGeneration.incrementAndGet(); 205 } 206 207 /** 208 * Creates a new {@link CachedValue} instance to be stored in this cache. 209 * 210 * <p>Implementation of {@link LruCache#create(K)} can use this method to create a new entry. 211 */ newCachedValue(V value)212 public CachedValue<V> newCachedValue(V value) { 213 return new GenerationalCachedValue<V>(value, mGeneration); 214 } 215 216 /** 217 * A cached value stored inside the cache. 218 * 219 * <p>It provides access to the value stored in the cache but also allows to check whether the 220 * value is expired. 221 * 222 * @param <V> the type of value stored in the cache 223 */ 224 public interface CachedValue<V> { 225 226 /** Returns the value stored in the cache for a given key. */ getValue()227 V getValue(); 228 229 /** 230 * Checks whether the value, while still being present in the cache, is expired. 231 * 232 * @return true if the value is expired 233 */ isExpired()234 boolean isExpired(); 235 } 236 237 /** Cached values storing the generation at which they were added. */ 238 @Immutable 239 private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> { 240 241 /** The value stored in the cache. */ 242 public final V mValue; 243 /** The generation at which the value was added to the cache. */ 244 private final int mGeneration; 245 /** The atomic integer storing the current generation of the cache it belongs to. */ 246 private final AtomicInteger mCacheGeneration; 247 248 /** 249 * @param cacheGeneration the atomic integer storing the generation of the cache in which this 250 * value will be stored 251 */ GenerationalCachedValue(V value, AtomicInteger cacheGeneration)252 public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) { 253 mValue = value; 254 mCacheGeneration = cacheGeneration; 255 // Snapshot the current generation. 256 mGeneration = mCacheGeneration.get(); 257 } 258 259 @Override getValue()260 public V getValue() { 261 return mValue; 262 } 263 264 @Override isExpired()265 public boolean isExpired() { 266 return mGeneration != mCacheGeneration.get(); 267 } 268 } 269 } 270