blob: af39315962cd668dceb09e0b4fb05efb7cd18d4b [file] [log] [blame]
Raph Levienb6ea1752012-10-25 23:11:13 -07001/*
2 * Copyright (C) 2012 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#include <utils/BasicHashtable.h>
18#include <utils/GenerationCache.h>
19#include <utils/UniquePtr.h>
20
21namespace android {
22
23// OnEntryRemoved is defined in GenerationCache.h, but maybe should move here.
24
25template <typename TKey, typename TValue>
26class LruCache {
27public:
28 explicit LruCache(uint32_t maxCapacity);
29
30 enum Capacity {
31 kUnlimitedCapacity,
32 };
33
34 void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener);
35 size_t size() const;
36 const TValue& get(const TKey& key);
37 bool put(const TKey& key, const TValue& value);
38 bool remove(const TKey& key);
39 bool removeOldest();
40 void clear();
41
42private:
43 LruCache(const LruCache& that); // disallow copy constructor
44
45 struct Entry {
46 TKey key;
47 TValue value;
48 Entry* parent;
49 Entry* child;
50
51 Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) {
52 }
53 const TKey& getKey() const { return key; }
54 };
55
56 void attachToCache(Entry& entry);
57 void detachFromCache(Entry& entry);
58 void rehash(size_t newCapacity);
59
60 UniquePtr<BasicHashtable<TKey, Entry> > mTable;
61 OnEntryRemoved<TKey, TValue>* mListener;
62 Entry* mOldest;
63 Entry* mYoungest;
64 uint32_t mMaxCapacity;
65 TValue mNullValue;
66};
67
68// Implementation is here, because it's fully templated
69template <typename TKey, typename TValue>
70LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity),
71 mNullValue(NULL), mTable(new BasicHashtable<TKey, Entry>), mYoungest(NULL), mOldest(NULL),
72 mListener(NULL) {
73};
74
75template<typename K, typename V>
76void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {
77 mListener = listener;
78}
79
80template <typename TKey, typename TValue>
81size_t LruCache<TKey, TValue>::size() const {
82 return mTable->size();
83}
84
85template <typename TKey, typename TValue>
86const TValue& LruCache<TKey, TValue>::get(const TKey& key) {
87 hash_t hash = hash_type(key);
88 ssize_t index = mTable->find(-1, hash, key);
89 if (index == -1) {
90 return mNullValue;
91 }
92 Entry& entry = mTable->editEntryAt(index);
93 detachFromCache(entry);
94 attachToCache(entry);
95 return entry.value;
96}
97
98template <typename TKey, typename TValue>
99bool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {
100 if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {
101 removeOldest();
102 }
103
104 hash_t hash = hash_type(key);
105 ssize_t index = mTable->find(-1, hash, key);
106 if (index >= 0) {
107 return false;
108 }
109 if (!mTable->hasMoreRoom()) {
110 rehash(mTable->capacity() * 2);
111 }
112
113 // Would it be better to initialize a blank entry and assign key, value?
114 Entry initEntry(key, value);
115 index = mTable->add(hash, initEntry);
116 Entry& entry = mTable->editEntryAt(index);
117 attachToCache(entry);
118 return true;
119}
120
121template <typename TKey, typename TValue>
122bool LruCache<TKey, TValue>::remove(const TKey& key) {
123 hash_t hash = hash_type(key);
124 ssize_t index = mTable->find(-1, hash, key);
125 if (index < 0) {
126 return false;
127 }
128 Entry& entry = mTable->editEntryAt(index);
129 if (mListener) {
130 (*mListener)(entry.key, entry.value);
131 }
132 detachFromCache(entry);
133 mTable->removeAt(index);
134 return true;
135}
136
137template <typename TKey, typename TValue>
138bool LruCache<TKey, TValue>::removeOldest() {
139 if (mOldest != NULL) {
140 return remove(mOldest->key);
141 // TODO: should probably abort if false
142 }
143 return false;
144}
145
146template <typename TKey, typename TValue>
147void LruCache<TKey, TValue>::clear() {
148 if (mListener) {
149 for (Entry* p = mOldest; p != NULL; p = p->child) {
150 (*mListener)(p->key, p->value);
151 }
152 }
153 mYoungest = NULL;
154 mOldest = NULL;
155 mTable->clear();
156}
157
158template <typename TKey, typename TValue>
159void LruCache<TKey, TValue>::attachToCache(Entry& entry) {
160 if (mYoungest == NULL) {
161 mYoungest = mOldest = &entry;
162 } else {
163 entry.parent = mYoungest;
164 mYoungest->child = &entry;
165 mYoungest = &entry;
166 }
167}
168
169template <typename TKey, typename TValue>
170void LruCache<TKey, TValue>::detachFromCache(Entry& entry) {
171 if (entry.parent != NULL) {
172 entry.parent->child = entry.child;
173 } else {
174 mOldest = entry.child;
175 }
176 if (entry.child != NULL) {
177 entry.child->parent = entry.parent;
178 } else {
179 mYoungest = entry.parent;
180 }
181
182 entry.parent = NULL;
183 entry.child = NULL;
184}
185
186template <typename TKey, typename TValue>
187void LruCache<TKey, TValue>::rehash(size_t newCapacity) {
188 UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release());
189 Entry* oldest = mOldest;
190
191 mOldest = NULL;
192 mYoungest = NULL;
193 mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity));
194 for (Entry* p = oldest; p != NULL; p = p->child) {
195 put(p->key, p->value);
196 }
197}
198
199}