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