/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ANDROID_UTILS_LRU_CACHE_H #define ANDROID_UTILS_LRU_CACHE_H #include #include namespace android { /** * GenerationCache callback used when an item is removed */ template class OnEntryRemoved { public: virtual ~OnEntryRemoved() { }; virtual void operator()(EntryKey& key, EntryValue& value) = 0; }; // class OnEntryRemoved template class LruCache { public: explicit LruCache(uint32_t maxCapacity); enum Capacity { kUnlimitedCapacity, }; void setOnEntryRemovedListener(OnEntryRemoved* listener); size_t size() const; const TValue& get(const TKey& key); bool put(const TKey& key, const TValue& value); bool remove(const TKey& key); bool removeOldest(); void clear(); class Iterator { public: Iterator(const LruCache& cache): mCache(cache), mIndex(-1) { } bool next() { mIndex = mCache.mTable->next(mIndex); return mIndex != -1; } size_t index() const { return mIndex; } const TValue& value() const { return mCache.mTable->entryAt(mIndex).value; } const TKey& key() const { return mCache.mTable->entryAt(mIndex).key; } private: const LruCache& mCache; size_t mIndex; }; private: LruCache(const LruCache& that); // disallow copy constructor struct Entry { TKey key; TValue value; Entry* parent; Entry* child; Entry(TKey key_, TValue value_) : key(key_), value(value_), parent(NULL), child(NULL) { } const TKey& getKey() const { return key; } }; void attachToCache(Entry& entry); void detachFromCache(Entry& entry); void rehash(size_t newCapacity); UniquePtr > mTable; OnEntryRemoved* mListener; Entry* mOldest; Entry* mYoungest; uint32_t mMaxCapacity; TValue mNullValue; }; // Implementation is here, because it's fully templated template LruCache::LruCache(uint32_t maxCapacity): mMaxCapacity(maxCapacity), mNullValue(NULL), mTable(new BasicHashtable), mYoungest(NULL), mOldest(NULL), mListener(NULL) { }; template void LruCache::setOnEntryRemovedListener(OnEntryRemoved* listener) { mListener = listener; } template size_t LruCache::size() const { return mTable->size(); } template const TValue& LruCache::get(const TKey& key) { hash_t hash = hash_type(key); ssize_t index = mTable->find(-1, hash, key); if (index == -1) { return mNullValue; } Entry& entry = mTable->editEntryAt(index); detachFromCache(entry); attachToCache(entry); return entry.value; } template bool LruCache::put(const TKey& key, const TValue& value) { if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) { removeOldest(); } hash_t hash = hash_type(key); ssize_t index = mTable->find(-1, hash, key); if (index >= 0) { return false; } if (!mTable->hasMoreRoom()) { rehash(mTable->capacity() * 2); } // Would it be better to initialize a blank entry and assign key, value? Entry initEntry(key, value); index = mTable->add(hash, initEntry); Entry& entry = mTable->editEntryAt(index); attachToCache(entry); return true; } template bool LruCache::remove(const TKey& key) { hash_t hash = hash_type(key); ssize_t index = mTable->find(-1, hash, key); if (index < 0) { return false; } Entry& entry = mTable->editEntryAt(index); if (mListener) { (*mListener)(entry.key, entry.value); } detachFromCache(entry); mTable->removeAt(index); return true; } template bool LruCache::removeOldest() { if (mOldest != NULL) { return remove(mOldest->key); // TODO: should probably abort if false } return false; } template void LruCache::clear() { if (mListener) { for (Entry* p = mOldest; p != NULL; p = p->child) { (*mListener)(p->key, p->value); } } mYoungest = NULL; mOldest = NULL; mTable->clear(); } template void LruCache::attachToCache(Entry& entry) { if (mYoungest == NULL) { mYoungest = mOldest = &entry; } else { entry.parent = mYoungest; mYoungest->child = &entry; mYoungest = &entry; } } template void LruCache::detachFromCache(Entry& entry) { if (entry.parent != NULL) { entry.parent->child = entry.child; } else { mOldest = entry.child; } if (entry.child != NULL) { entry.child->parent = entry.parent; } else { mYoungest = entry.parent; } entry.parent = NULL; entry.child = NULL; } template void LruCache::rehash(size_t newCapacity) { UniquePtr > oldTable(mTable.release()); Entry* oldest = mOldest; mOldest = NULL; mYoungest = NULL; mTable.reset(new BasicHashtable(newCapacity)); for (Entry* p = oldest; p != NULL; p = p->child) { put(p->key, p->value); } } } #endif // ANDROID_UTILS_LRU_CACHE_H