Home | History | Annotate | Download | only in util
      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  *                     &#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
     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 generation;
    102   /** The underlying cache used to stored the cached values. */
    103   private LruCache<K, CachedValue<V>> cache;
    104 
    105   private ExpirableCache(LruCache<K, CachedValue<V>> cache) {
    106     this.cache = cache;
    107     generation = 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 cache.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     cache.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     generation.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, generation);
    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 value;
    243     /** The generation at which the value was added to the cache. */
    244     private final int generation;
    245     /** The atomic integer storing the current generation of the cache it belongs to. */
    246     private final AtomicInteger cacheGeneration;
    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       this.value = value;
    254       this.cacheGeneration = cacheGeneration;
    255       // Snapshot the current generation.
    256       generation = this.cacheGeneration.get();
    257     }
    258 
    259     @Override
    260     public V getValue() {
    261       return value;
    262     }
    263 
    264     @Override
    265     public boolean isExpired() {
    266       return generation != cacheGeneration.get();
    267     }
    268   }
    269 }
    270