| /* | 
 |  * 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 <memory> | 
 | #include <unordered_set> | 
 |  | 
 | #include "utils/TypeHelpers.h"  // hash_t | 
 |  | 
 | 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); | 
 |     virtual ~LruCache(); | 
 |  | 
 |     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(); | 
 |  | 
 | 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; } | 
 |     }; | 
 |  | 
 |     struct HashForEntry : public std::unary_function<Entry*, hash_t> { | 
 |         size_t operator() (const Entry* entry) const { | 
 |             return hash_type(entry->key); | 
 |         }; | 
 |     }; | 
 |  | 
 |     struct EqualityForHashedEntries : public std::unary_function<Entry*, hash_t> { | 
 |         bool operator() (const Entry* lhs, const Entry* rhs) const { | 
 |             return lhs->key == rhs->key; | 
 |         }; | 
 |     }; | 
 |  | 
 |     typedef std::unordered_set<Entry*, HashForEntry, EqualityForHashedEntries> LruCacheSet; | 
 |  | 
 |     void attachToCache(Entry& entry); | 
 |     void detachFromCache(Entry& entry); | 
 |  | 
 |     typename LruCacheSet::iterator findByKey(const TKey& key) { | 
 |         Entry entryForSearch(key, mNullValue); | 
 |         typename LruCacheSet::iterator result = mSet->find(&entryForSearch); | 
 |         return result; | 
 |     } | 
 |  | 
 |     std::unique_ptr<LruCacheSet> mSet; | 
 |     OnEntryRemoved<TKey, TValue>* mListener; | 
 |     Entry* mOldest; | 
 |     Entry* mYoungest; | 
 |     uint32_t mMaxCapacity; | 
 |     TValue mNullValue; | 
 |  | 
 | public: | 
 |     // To be used like: | 
 |     // while (it.next()) { | 
 |     //   it.value(); it.key(); | 
 |     // } | 
 |     class Iterator { | 
 |     public: | 
 |         Iterator(const LruCache<TKey, TValue>& cache): | 
 |                 mCache(cache), mIterator(mCache.mSet->begin()), mBeginReturned(false) { | 
 |         } | 
 |  | 
 |         bool next() { | 
 |             if (mIterator == mCache.mSet->end()) { | 
 |                 return false; | 
 |             } | 
 |             if (!mBeginReturned) { | 
 |                 // mIterator has been initialized to the beginning and | 
 |                 // hasn't been returned. Do not advance: | 
 |                 mBeginReturned = true; | 
 |             } else { | 
 |                 std::advance(mIterator, 1); | 
 |             } | 
 |             bool ret = (mIterator != mCache.mSet->end()); | 
 |             return ret; | 
 |         } | 
 |  | 
 |         const TValue& value() const { | 
 |             return (*mIterator)->value; | 
 |         } | 
 |  | 
 |         const TKey& key() const { | 
 |             return (*mIterator)->key; | 
 |         } | 
 |     private: | 
 |         const LruCache<TKey, TValue>& mCache; | 
 |         typename LruCacheSet::iterator mIterator; | 
 |         bool mBeginReturned; | 
 |     }; | 
 | }; | 
 |  | 
 | // Implementation is here, because it's fully templated | 
 | template <typename TKey, typename TValue> | 
 | LruCache<TKey, TValue>::LruCache(uint32_t maxCapacity) | 
 |     : mSet(new LruCacheSet()) | 
 |     , mListener(NULL) | 
 |     , mOldest(NULL) | 
 |     , mYoungest(NULL) | 
 |     , mMaxCapacity(maxCapacity) | 
 |     , mNullValue(NULL) { | 
 |     mSet->max_load_factor(1.0); | 
 | }; | 
 |  | 
 | template <typename TKey, typename TValue> | 
 | LruCache<TKey, TValue>::~LruCache() { | 
 |     // Need to delete created entries. | 
 |     clear(); | 
 | }; | 
 |  | 
 | 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 mSet->size(); | 
 | } | 
 |  | 
 | template <typename TKey, typename TValue> | 
 | const TValue& LruCache<TKey, TValue>::get(const TKey& key) { | 
 |     typename LruCacheSet::const_iterator find_result = findByKey(key); | 
 |     if (find_result == mSet->end()) { | 
 |         return mNullValue; | 
 |     } | 
 |     Entry *entry = *find_result; | 
 |     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(); | 
 |     } | 
 |  | 
 |     if (findByKey(key) != mSet->end()) { | 
 |         return false; | 
 |     } | 
 |  | 
 |     Entry* newEntry = new Entry(key, value); | 
 |     mSet->insert(newEntry); | 
 |     attachToCache(*newEntry); | 
 |     return true; | 
 | } | 
 |  | 
 | template <typename TKey, typename TValue> | 
 | bool LruCache<TKey, TValue>::remove(const TKey& key) { | 
 |     typename LruCacheSet::const_iterator find_result = findByKey(key); | 
 |     if (find_result == mSet->end()) { | 
 |         return false; | 
 |     } | 
 |     Entry* entry = *find_result; | 
 |     mSet->erase(entry); | 
 |     if (mListener) { | 
 |         (*mListener)(entry->key, entry->value); | 
 |     } | 
 |     detachFromCache(*entry); | 
 |     delete entry; | 
 |     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; | 
 |     for (auto entry : *mSet.get()) { | 
 |         delete entry; | 
 |     } | 
 |     mSet->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; | 
 | } | 
 |  | 
 | } | 
 | #endif // ANDROID_UTILS_LRU_CACHE_H |