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.contacts.util;
18
19import com.android.contacts.test.NeededForTesting;
20
21import android.util.LruCache;
22
23import java.util.concurrent.atomic.AtomicInteger;
24
25import javax.annotation.concurrent.Immutable;
26import 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
87public 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. */
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         */
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         */
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
132        public V getValue() {
133            return mValue;
134        }
135
136        @Override
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
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
263    public static <K, V> ExpirableCache<K, V> create(int maxSize) {
264        return create(new LruCache<K, CachedValue<V>>(maxSize));
265    }
266}
267