/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support;
/**
* A key-value cache with a fixed size and optional expiration time.
* The least recently used item is removed if the cache is full and a new entry is added.
*
* Existing entries are only returned if they are not expired.
*
* Notice that expired items are ONLY removed when trying to get() them or when they fall out during push() because they are the
* least-recently-used.
* Therefore, this cache is intended for small sizes (or large amounts of small items).
*
* If you want to do a large cache and therefore need periodical garbage collection, please email me and I might implement it.
*
* Pushing and getting are executed in O(lg N) using a tree, to avoid hash collision DoS'es.
*
* @author xor (xor@freenetproject.org)
*/
public final class LRUCache<Key extends Comparable<Key>, Value> {
private final int mSizeLimit;
private final long mExpirationDelay;
private final class Entry {
private final Value mValue;
private final long mExpirationDate;
public Entry(final Value myValue) {
mValue = myValue;
mExpirationDate = (mExpirationDelay < Long.MAX_VALUE) ? (CurrentTimeUTC.getInMillis() + mExpirationDelay) : (Long.MAX_VALUE);
}
public boolean expired(final long time) {
return mExpirationDate < time;
}
public boolean expired() {
return expired(CurrentTimeUTC.getInMillis());
}
public Value getValue() {
return mValue;
}
}
private final LRUMap<Key, Entry> mCache;
/**
* Creates a cache without an expiration time.
* @param sizeLimit The maximal amount of items which the cache should hold.
*/
public LRUCache(final int sizeLimit) {
mCache = LRUMap.createSafeMap();
mSizeLimit = sizeLimit;
mExpirationDelay = Long.MAX_VALUE;
}
/**
* @param sizeLimit The maximal amount of items which the cache should hold.
* @param expirationDelay The amount of milliseconds after which an entry expires.
*/
public LRUCache(final int sizeLimit, final long expirationDelay) {
mCache = LRUMap.createSafeMap();
mSizeLimit = sizeLimit;
mExpirationDelay = expirationDelay;
}
/**
* Removes the least recently used items until a free space of the given capacity is available
*/
private void freeCapacity(final int capacity) {
assert(capacity <= mSizeLimit);
final int limit = mSizeLimit - capacity;
while(mCache.size() > limit)
mCache.popValue();
}
/**
* Puts a value in the cache. If an entry for the key already exists, its value is updated and its
* expiration time is increased.
*
* If the size limit of this cache is exceeded the least recently used entry is removed.
*/
public void put(final Key key, final Value value) {
mCache.push(key, new Entry(value));
freeCapacity(0);
}
/**
* Gets a value from the cache and moves it to top.
* Returns null if there is no entry for the given key or if the entry is expired.
* If an expired entry was found, it is removed from the cache.
*/
public Value get(final Key key) {
final Entry entry = mCache.get(key);
if(entry == null)
return null;
if(mExpirationDelay < Long.MAX_VALUE && entry.expired()) {
mCache.removeKey(key);
return null;
}
mCache.push(key, entry); // Move the key to top.
return entry.getValue();
}
public void clear() {
mCache.clear();
}
}