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 android.util;
18
19import java.util.LinkedHashMap;
20import java.util.Map;
21
22/**
23 * BEGIN LAYOUTLIB CHANGE
24 * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
25 * END LAYOUTLIB CHANGE
26 *
27 * A cache that holds strong references to a limited number of values. Each time
28 * a value is accessed, it is moved to the head of a queue. When a value is
29 * added to a full cache, the value at the end of that queue is evicted and may
30 * become eligible for garbage collection.
31 *
32 * <p>If your cached values hold resources that need to be explicitly released,
33 * override {@link #entryRemoved}.
34 *
35 * <p>If a cache miss should be computed on demand for the corresponding keys,
36 * override {@link #create}. This simplifies the calling code, allowing it to
37 * assume a value will always be returned, even when there's a cache miss.
38 *
39 * <p>By default, the cache size is measured in the number of entries. Override
40 * {@link #sizeOf} to size the cache in different units. For example, this cache
41 * is limited to 4MiB of bitmaps:
42 * <pre>   {@code
43 *   int cacheSize = 4 * 1024 * 1024; // 4MiB
44 *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
45 *       protected int sizeOf(String key, Bitmap value) {
46 *           return value.getByteCount();
47 *       }
48 *   }}</pre>
49 *
50 * <p>This class is thread-safe. Perform multiple cache operations atomically by
51 * synchronizing on the cache: <pre>   {@code
52 *   synchronized (cache) {
53 *     if (cache.get(key) == null) {
54 *         cache.put(key, value);
55 *     }
56 *   }}</pre>
57 *
58 * <p>This class does not allow null to be used as a key or value. A return
59 * value of null from {@link #get}, {@link #put} or {@link #remove} is
60 * unambiguous: the key was not in the cache.
61 *
62 * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part
63 * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
64 * Support Package</a> for earlier releases.
65 */
66public class LruCache<K, V> {
67    private final LinkedHashMap<K, V> map;
68
69    /** Size of this cache in units. Not necessarily the number of elements. */
70    private int size;
71    private int maxSize;
72
73    private int putCount;
74    private int createCount;
75    private int evictionCount;
76    private int hitCount;
77    private int missCount;
78
79    /**
80     * @param maxSize for caches that do not override {@link #sizeOf}, this is
81     *     the maximum number of entries in the cache. For all other caches,
82     *     this is the maximum sum of the sizes of the entries in this cache.
83     */
84    public LruCache(int maxSize) {
85        if (maxSize <= 0) {
86            throw new IllegalArgumentException("maxSize <= 0");
87        }
88        this.maxSize = maxSize;
89        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
90    }
91
92    /**
93     * Sets the size of the cache.
94     * @param maxSize The new maximum size.
95     *
96     * @hide
97     */
98    public void resize(int maxSize) {
99        if (maxSize <= 0) {
100            throw new IllegalArgumentException("maxSize <= 0");
101        }
102
103        synchronized (this) {
104            this.maxSize = maxSize;
105        }
106        trimToSize(maxSize);
107    }
108
109    /**
110     * Returns the value for {@code key} if it exists in the cache or can be
111     * created by {@code #create}. If a value was returned, it is moved to the
112     * head of the queue. This returns null if a value is not cached and cannot
113     * be created.
114     */
115    public final V get(K key) {
116        if (key == null) {
117            throw new NullPointerException("key == null");
118        }
119
120        V mapValue;
121        synchronized (this) {
122            mapValue = map.get(key);
123            if (mapValue != null) {
124                hitCount++;
125                return mapValue;
126            }
127            missCount++;
128        }
129
130        /*
131         * Attempt to create a value. This may take a long time, and the map
132         * may be different when create() returns. If a conflicting value was
133         * added to the map while create() was working, we leave that value in
134         * the map and release the created value.
135         */
136
137        V createdValue = create(key);
138        if (createdValue == null) {
139            return null;
140        }
141
142        synchronized (this) {
143            createCount++;
144            mapValue = map.put(key, createdValue);
145
146            if (mapValue != null) {
147                // There was a conflict so undo that last put
148                map.put(key, mapValue);
149            } else {
150                size += safeSizeOf(key, createdValue);
151            }
152        }
153
154        if (mapValue != null) {
155            entryRemoved(false, key, createdValue, mapValue);
156            return mapValue;
157        } else {
158            trimToSize(maxSize);
159            return createdValue;
160        }
161    }
162
163    /**
164     * Caches {@code value} for {@code key}. The value is moved to the head of
165     * the queue.
166     *
167     * @return the previous value mapped by {@code key}.
168     */
169    public final V put(K key, V value) {
170        if (key == null || value == null) {
171            throw new NullPointerException("key == null || value == null");
172        }
173
174        V previous;
175        synchronized (this) {
176            putCount++;
177            size += safeSizeOf(key, value);
178            previous = map.put(key, value);
179            if (previous != null) {
180                size -= safeSizeOf(key, previous);
181            }
182        }
183
184        if (previous != null) {
185            entryRemoved(false, key, previous, value);
186        }
187
188        trimToSize(maxSize);
189        return previous;
190    }
191
192    /**
193     * @param maxSize the maximum size of the cache before returning. May be -1
194     *     to evict even 0-sized elements.
195     */
196    private void trimToSize(int maxSize) {
197        while (true) {
198            K key;
199            V value;
200            synchronized (this) {
201                if (size < 0 || (map.isEmpty() && size != 0)) {
202                    throw new IllegalStateException(getClass().getName()
203                            + ".sizeOf() is reporting inconsistent results!");
204                }
205
206                if (size <= maxSize) {
207                    break;
208                }
209
210                // BEGIN LAYOUTLIB CHANGE
211                // get the last item in the linked list.
212                // This is not efficient, the goal here is to minimize the changes
213                // compared to the platform version.
214                Map.Entry<K, V> toEvict = null;
215                for (Map.Entry<K, V> entry : map.entrySet()) {
216                    toEvict = entry;
217                }
218                // END LAYOUTLIB CHANGE
219
220                if (toEvict == null) {
221                    break;
222                }
223
224                key = toEvict.getKey();
225                value = toEvict.getValue();
226                map.remove(key);
227                size -= safeSizeOf(key, value);
228                evictionCount++;
229            }
230
231            entryRemoved(true, key, value, null);
232        }
233    }
234
235    /**
236     * Removes the entry for {@code key} if it exists.
237     *
238     * @return the previous value mapped by {@code key}.
239     */
240    public final V remove(K key) {
241        if (key == null) {
242            throw new NullPointerException("key == null");
243        }
244
245        V previous;
246        synchronized (this) {
247            previous = map.remove(key);
248            if (previous != null) {
249                size -= safeSizeOf(key, previous);
250            }
251        }
252
253        if (previous != null) {
254            entryRemoved(false, key, previous, null);
255        }
256
257        return previous;
258    }
259
260    /**
261     * Called for entries that have been evicted or removed. This method is
262     * invoked when a value is evicted to make space, removed by a call to
263     * {@link #remove}, or replaced by a call to {@link #put}. The default
264     * implementation does nothing.
265     *
266     * <p>The method is called without synchronization: other threads may
267     * access the cache while this method is executing.
268     *
269     * @param evicted true if the entry is being removed to make space, false
270     *     if the removal was caused by a {@link #put} or {@link #remove}.
271     * @param newValue the new value for {@code key}, if it exists. If non-null,
272     *     this removal was caused by a {@link #put}. Otherwise it was caused by
273     *     an eviction or a {@link #remove}.
274     */
275    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
276
277    /**
278     * Called after a cache miss to compute a value for the corresponding key.
279     * Returns the computed value or null if no value can be computed. The
280     * default implementation returns null.
281     *
282     * <p>The method is called without synchronization: other threads may
283     * access the cache while this method is executing.
284     *
285     * <p>If a value for {@code key} exists in the cache when this method
286     * returns, the created value will be released with {@link #entryRemoved}
287     * and discarded. This can occur when multiple threads request the same key
288     * at the same time (causing multiple values to be created), or when one
289     * thread calls {@link #put} while another is creating a value for the same
290     * key.
291     */
292    protected V create(K key) {
293        return null;
294    }
295
296    private int safeSizeOf(K key, V value) {
297        int result = sizeOf(key, value);
298        if (result < 0) {
299            throw new IllegalStateException("Negative size: " + key + "=" + value);
300        }
301        return result;
302    }
303
304    /**
305     * Returns the size of the entry for {@code key} and {@code value} in
306     * user-defined units.  The default implementation returns 1 so that size
307     * is the number of entries and max size is the maximum number of entries.
308     *
309     * <p>An entry's size must not change while it is in the cache.
310     */
311    protected int sizeOf(K key, V value) {
312        return 1;
313    }
314
315    /**
316     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
317     */
318    public final void evictAll() {
319        trimToSize(-1); // -1 will evict 0-sized elements
320    }
321
322    /**
323     * For caches that do not override {@link #sizeOf}, this returns the number
324     * of entries in the cache. For all other caches, this returns the sum of
325     * the sizes of the entries in this cache.
326     */
327    public synchronized final int size() {
328        return size;
329    }
330
331    /**
332     * For caches that do not override {@link #sizeOf}, this returns the maximum
333     * number of entries in the cache. For all other caches, this returns the
334     * maximum sum of the sizes of the entries in this cache.
335     */
336    public synchronized final int maxSize() {
337        return maxSize;
338    }
339
340    /**
341     * Returns the number of times {@link #get} returned a value that was
342     * already present in the cache.
343     */
344    public synchronized final int hitCount() {
345        return hitCount;
346    }
347
348    /**
349     * Returns the number of times {@link #get} returned null or required a new
350     * value to be created.
351     */
352    public synchronized final int missCount() {
353        return missCount;
354    }
355
356    /**
357     * Returns the number of times {@link #create(Object)} returned a value.
358     */
359    public synchronized final int createCount() {
360        return createCount;
361    }
362
363    /**
364     * Returns the number of times {@link #put} was called.
365     */
366    public synchronized final int putCount() {
367        return putCount;
368    }
369
370    /**
371     * Returns the number of values that have been evicted.
372     */
373    public synchronized final int evictionCount() {
374        return evictionCount;
375    }
376
377    /**
378     * Returns a copy of the current contents of the cache, ordered from least
379     * recently accessed to most recently accessed.
380     */
381    public synchronized final Map<K, V> snapshot() {
382        return new LinkedHashMap<K, V>(map);
383    }
384
385    @Override public synchronized final String toString() {
386        int accesses = hitCount + missCount;
387        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
388        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
389                maxSize, hitCount, missCount, hitPercent);
390    }
391}
392