|  | /* | 
|  | * 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 <UniquePtr.h> | 
|  | #include <utils/BasicHashtable.h> | 
|  |  | 
|  | namespace android { | 
|  |  | 
|  | /** | 
|  | * GenerationCache callback used when an item is removed | 
|  | */ | 
|  | template<typename EntryKey, typename EntryValue> | 
|  | class OnEntryRemoved { | 
|  | public: | 
|  | virtual ~OnEntryRemoved() { }; | 
|  | virtual void operator()(EntryKey& key, EntryValue& value) = 0; | 
|  | }; // class OnEntryRemoved | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | class LruCache { | 
|  | public: | 
|  | explicit LruCache(uint32_t maxCapacity); | 
|  |  | 
|  | enum Capacity { | 
|  | kUnlimitedCapacity, | 
|  | }; | 
|  |  | 
|  | void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* 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(); | 
|  | const TValue& peekOldestValue(); | 
|  |  | 
|  | class Iterator { | 
|  | public: | 
|  | Iterator(const LruCache<TKey, TValue>& cache): mCache(cache), mIndex(-1) { | 
|  | } | 
|  |  | 
|  | bool next() { | 
|  | mIndex = mCache.mTable->next(mIndex); | 
|  | return (ssize_t)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<TKey, TValue>& 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<BasicHashtable<TKey, Entry> > mTable; | 
|  | OnEntryRemoved<TKey, TValue>* mListener; | 
|  | Entry* mOldest; | 
|  | Entry* mYoungest; | 
|  | uint32_t mMaxCapacity; | 
|  | TValue mNullValue; | 
|  | }; | 
|  |  | 
|  | // Implementation is here, because it's fully templated | 
|  | template <typename TKey, typename TValue> | 
|  | LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity) | 
|  | : mTable(new BasicHashtable<TKey, Entry>) | 
|  | , mListener(NULL) | 
|  | , mOldest(NULL) | 
|  | , mYoungest(NULL) | 
|  | , mMaxCapacity(maxCapacity) | 
|  | , mNullValue(NULL) { | 
|  | }; | 
|  |  | 
|  | template<typename K, typename V> | 
|  | void LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) { | 
|  | mListener = listener; | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | size_t LruCache<TKey, TValue>::size() const { | 
|  | return mTable->size(); | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | const TValue& LruCache<TKey, TValue>::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 <typename TKey, typename TValue> | 
|  | bool LruCache<TKey, TValue>::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 <typename TKey, typename TValue> | 
|  | bool LruCache<TKey, TValue>::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 <typename TKey, typename TValue> | 
|  | bool LruCache<TKey, TValue>::removeOldest() { | 
|  | if (mOldest != NULL) { | 
|  | return remove(mOldest->key); | 
|  | // TODO: should probably abort if false | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | const TValue& LruCache<TKey, TValue>::peekOldestValue() { | 
|  | if (mOldest) { | 
|  | return mOldest->value; | 
|  | } | 
|  | return mNullValue; | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | void LruCache<TKey, TValue>::clear() { | 
|  | if (mListener) { | 
|  | for (Entry* p = mOldest; p != NULL; p = p->child) { | 
|  | (*mListener)(p->key, p->value); | 
|  | } | 
|  | } | 
|  | mYoungest = NULL; | 
|  | mOldest = NULL; | 
|  | mTable->clear(); | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | void LruCache<TKey, TValue>::attachToCache(Entry& entry) { | 
|  | if (mYoungest == NULL) { | 
|  | mYoungest = mOldest = &entry; | 
|  | } else { | 
|  | entry.parent = mYoungest; | 
|  | mYoungest->child = &entry; | 
|  | mYoungest = &entry; | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename TKey, typename TValue> | 
|  | void LruCache<TKey, TValue>::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 <typename TKey, typename TValue> | 
|  | void LruCache<TKey, TValue>::rehash(size_t newCapacity) { | 
|  | UniquePtr<BasicHashtable<TKey, Entry> > oldTable(mTable.release()); | 
|  | Entry* oldest = mOldest; | 
|  |  | 
|  | mOldest = NULL; | 
|  | mYoungest = NULL; | 
|  | mTable.reset(new BasicHashtable<TKey, Entry>(newCapacity)); | 
|  | for (Entry* p = oldest; p != NULL; p = p->child) { | 
|  | put(p->key, p->value); | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | #endif // ANDROID_UTILS_LRU_CACHE_H |