Home | History | Annotate | Download | only in memory
      1 /*
      2  * Copyright (C) 2015 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.camera.processing.memory;
     18 
     19 import com.google.common.base.Preconditions;
     20 
     21 import java.util.HashMap;
     22 import java.util.LinkedList;
     23 import java.util.Queue;
     24 
     25 import javax.annotation.concurrent.GuardedBy;
     26 
     27 /**
     28  * LruPool that will evict items from the pool after reaching maximum size in
     29  * order of Least Recently Used (LRU). This code is based on the Android
     30  * Lru implementation but removes the hard requirement that keys must only
     31  * exist once. Different values may be returned for the same key, and there is
     32  * no guarantee that adding and then immediately acquiring the same key will
     33  * return the same value instance.
     34  *
     35  * The size of the pool is generally equal to the number of items, but can be
     36  * reconfigured by a subclass to be proportional to some other computed value.
     37  *
     38  * This class has multiple moving parts and should only be use to track and
     39  * reuse objects that are expensive to create or re-create.
     40  *
     41  * WARNING:
     42  * {@link #acquire(TKey)} is currently linear time, pending a better
     43  * implementation.
     44  *
     45  * TODO: Build a constant time acquire(TKey) method implementation.
     46  *
     47  */
     48 public class LruPool<TKey, TValue> {
     49     public static class Configuration<TKey, TValue> {
     50         /**
     51          * Called for entries that have been evicted or removed. This method is
     52          * invoked when a value is evicted to make space, but NOT when an item is
     53          * removed via {@link #acquire}. The default
     54          * implementation does nothing.
     55          *
     56          * <p>The method is called without synchronization: other threads may
     57          * access the cache while this method is executing.
     58          */
     59         void entryEvicted(TKey key, TValue value) { }
     60 
     61         /**
     62          * Called after a cache miss to compute a value for the corresponding key.
     63          * Returns the computed value or null if no value can be computed. The
     64          * default implementation returns null.
     65          *
     66          * <p>The method is called without synchronization: other threads may
     67          * access the cache while this method is executing.
     68          */
     69         TValue create(TKey key) {
     70             return null;
     71         }
     72 
     73         /**
     74          * Returns the size of the entry for {@code key} and {@code value} in
     75          * user-defined units.  The default implementation returns 1 so that size
     76          * is the number of entries and max size is the maximum number of entries.
     77          *
     78          * <p>An entry's size must not change while it is in the cache.
     79          */
     80         int sizeOf(TKey key, TValue value) {
     81             return 1;
     82         }
     83     }
     84 
     85     private final Object mLock;
     86 
     87     /**
     88      * Maintains an ordered list of keys by "most recently added". Duplicate
     89      * keys can exist in the list.
     90      */
     91     @GuardedBy("mLock")
     92     private final LinkedList<TKey> mLruKeyList;
     93 
     94     /**
     95      * Maintains individual pools for each distinct key type.
     96      */
     97     @GuardedBy("mLock")
     98     private final HashMap<TKey, Queue<TValue>> mValuePool;
     99     private final Configuration<TKey, TValue> mConfiguration;
    100 
    101     private final int mMaxSize;
    102 
    103     /**
    104      * Size may be configured to represent quantities other than the number of
    105      * items in the pool. By default, it represents the number of items
    106      * in the pool.
    107      */
    108     @GuardedBy("mLock")
    109     private int mSize;
    110 
    111     /**
    112      * Creates and sets the size of the Lru Pool
    113      *
    114      * @param maxSize Sets the size of the Lru Pool.
    115      */
    116     public LruPool(int maxSize) {
    117         this(maxSize, new Configuration<TKey, TValue>());
    118     }
    119 
    120     public LruPool(int maxSize, Configuration<TKey, TValue> configuration) {
    121         Preconditions.checkArgument(maxSize > 0, "maxSize must be > 0.");
    122 
    123         mMaxSize = maxSize;
    124         mConfiguration = configuration;
    125 
    126         mLock = new Object();
    127         mLruKeyList = new LinkedList<>();
    128         mValuePool = new HashMap<>();
    129     }
    130 
    131     /**
    132      * Acquire a value from the pool, or attempt to create a new one if the create
    133      * method is overridden. If an item cannot be retrieved or created, this method
    134      * will return null.
    135      *
    136      * WARNING:
    137      * This implementation is currently linear time, pending a better
    138      * implementation.
    139      *
    140      * TODO: Build a constant time acquire(TKey) method implementation.
    141      *
    142      * @param key the type of object to retrieve from the pool.
    143      * @return a value or null if none exists or can be created.
    144      */
    145     public final TValue acquire(TKey key) {
    146         Preconditions.checkNotNull(key);
    147 
    148         // We must remove the item we acquire from the list
    149         TValue value;
    150 
    151         synchronized (mLock) {
    152             if (mLruKeyList.removeLastOccurrence(key)) {
    153                 value = mValuePool.get(key).remove();
    154                 mSize -= checkedSizeOf(key, value);
    155             } else {
    156                 value = mConfiguration.create(key);
    157             }
    158         }
    159 
    160         return value;
    161     }
    162 
    163     /**
    164      * Add a new or previously existing value to the pool. The most recently added
    165      * item will be placed at the top of the Lru list, and will trim existing items
    166      * off the list, if the list exceeds the maximum size.
    167      *
    168      * @param key the type of object to add to the pool.
    169      * @param value the object to add into the pool.
    170      */
    171     public final void add(TKey key, TValue value) {
    172         Preconditions.checkNotNull(key);
    173         Preconditions.checkNotNull(value);
    174 
    175         synchronized (mLock) {
    176             final Queue<TValue> pool;
    177 
    178             mLruKeyList.push(key);
    179             if (!mValuePool.containsKey(key)) {
    180                 pool = new LinkedList<>();
    181                 mValuePool.put(key, pool);
    182             } else {
    183                 pool = mValuePool.get(key);
    184             }
    185             pool.add(value);
    186             mSize += checkedSizeOf(key, value);
    187 
    188             unsafeTrimToSize(mMaxSize);
    189         }
    190     }
    191 
    192     /**
    193      * Remove the oldest entries until the total of remaining entries is at or
    194      * below the configured size.
    195      *
    196      * @param trimToSize the maximum size of the cache before returning. May
    197      *                   be -1 to evict even 0-sized elements.
    198      */
    199     public final void trimToSize(int trimToSize) {
    200         synchronized (mLock) {
    201             unsafeTrimToSize(trimToSize);
    202         }
    203     }
    204 
    205     /**
    206      * For pools that do not override {@link Configuration#sizeOf}, this
    207      * returns the number of items in the pool. For custom sizes, this returns
    208      * the sum of the sizes of the entries in this pool.
    209      */
    210     public final int getSize() {
    211         synchronized (mLock) {
    212             return mSize;
    213         }
    214     }
    215 
    216     /**
    217      * For pools that do not override {@link Configuration#sizeOf}, this
    218      * returns the maximum number of entries in the pool. For all other pools,
    219      * this returns the maximum sum of the sizes of the entries in this pool.
    220      */
    221     public final int getMaxSize() {
    222         return mMaxSize;
    223     }
    224 
    225     @GuardedBy("mLock")
    226     private void unsafeTrimToSize(int trimToSize) {
    227         while (mSize > trimToSize && !mLruKeyList.isEmpty()) {
    228             TKey key = mLruKeyList.removeLast();
    229             if (key == null) {
    230                 break;
    231             }
    232 
    233             Queue<TValue> pool = mValuePool.get(key);
    234             TValue value = pool.remove();
    235 
    236             if (pool.size() <= 0) {
    237                 mValuePool.remove(key);
    238             }
    239 
    240             mSize = mSize - checkedSizeOf(key, value);
    241             mConfiguration.entryEvicted(key, value);
    242         }
    243 
    244         if (mSize < 0 || (mLruKeyList.isEmpty() && mSize != 0)) {
    245             throw new IllegalStateException("LruPool.sizeOf() is reporting "
    246                   + "inconsistent results!");
    247         }
    248     }
    249 
    250     private int checkedSizeOf(TKey key, TValue value) {
    251         int result = mConfiguration.sizeOf(key, value);
    252         Preconditions.checkArgument(result >= 0, "Size was < 0.");
    253         return result;
    254     }
    255 }
    256