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
17package com.android.dialer.util;
18
19import android.util.LruCache;
20import java.util.concurrent.atomic.AtomicInteger;
21import javax.annotation.concurrent.Immutable;
22import 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 *                     &#64;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 *     &#64;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
91public 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
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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   */
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. */
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     */
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     */
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
260    public V getValue() {
261      return mValue;
262    }
263
264    @Override
265    public boolean isExpired() {
266      return mGeneration != mCacheGeneration.get();
267    }
268  }
269}
270