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 */ entryEvicted(TKey key, TValue value)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 */ create(TKey key)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 */ sizeOf(TKey key, TValue value)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 */ LruPool(int maxSize)116 public LruPool(int maxSize) { 117 this(maxSize, new Configuration<TKey, TValue>()); 118 } 119 LruPool(int maxSize, Configuration<TKey, TValue> configuration)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 */ acquire(TKey key)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 */ add(TKey key, TValue value)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 */ trimToSize(int trimToSize)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 */ getSize()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 */ getMaxSize()221 public final int getMaxSize() { 222 return mMaxSize; 223 } 224 225 @GuardedBy("mLock") unsafeTrimToSize(int trimToSize)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 checkedSizeOf(TKey key, TValue value)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