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