/*
* 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