package com.nutiteq.cache;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* <p>
* Memory cache implementing LRU (least recently used) strategy. If cache is
* full, least recently used items will be pushed out.
* </p>
*
* <p>
* Current implementation uses only actual data size. Objects/keys overhead is
* not calculated in cache size.
* </p>
*/
public class MemoryCache implements Cache {
private final Hashtable cache;
private final int maxSize;
private CacheItem mru;
private CacheItem lru;
private int size;
/**
* Create a new MemoryCache instance.
*
* @param cacheSize
* cache size in bytes.
*/
public MemoryCache(final int cacheSize) {
maxSize = cacheSize;
cache = new Hashtable();
}
public void initialize() {
}
public void deinitialize() {
}
public byte[] get(final String cacheId) {
final CacheItem result = (CacheItem) cache.get(cacheId);
if (result == null) {
return null;
}
//make it the most recently used entry
if (mru != result) { //not already the MRU
if (lru == result) { // I'm the least recently used
lru = result.previous;
}
// Remove myself from the LRU list.
if (result.next != null) {
result.next.previous = result.previous;
}
result.previous.next = result.next;
// Add myself back in to the front.
mru.previous = result;
result.previous = null;
result.next = mru;
mru = result;
}
return result.data;
}
public void cache(final String cacheId, final byte[] data, final int cacheLevel) {
if ((cacheLevel & CACHE_LEVEL_MEMORY) != CACHE_LEVEL_MEMORY || data == null || data.length == 0) {
return;
}
final byte[] existing = get(cacheId);
if (existing != null) {
// The key has already been used. By calling get() we already promoted
// it to the MRU spot. However, if the data has changed, we need to
// update it in the hash table.
//TODO jaanus : check also data content?
if (existing.length != data.length) {
final CacheItem i = (CacheItem) cache.get(cacheId);
i.data = data;
}
} else {
// cache miss
final CacheItem item = new CacheItem();
item.key = cacheId;
item.data = data;
item.next = mru;
item.previous = null;
if (cache.size() == 0) {
// then cache is empty
lru = item;
} else {
mru.previous = item;
}
mru = item;
cache.put(cacheId, item);
size += data.length;
}
while (size > maxSize) {
// Kick out the least recently used element.
cache.remove(lru.key);
size -= lru.data.length;
if (lru.previous != null) {
lru.previous.next = null;
}
lru = lru.previous;
}
}
public boolean contains(final String cacheKey) {
return cache.containsKey(cacheKey);
}
public boolean contains(final String cacheKey, final int cacheLevel) {
if ((cacheLevel & CACHE_LEVEL_MEMORY) != CACHE_LEVEL_MEMORY) {
return false;
}
return contains(cacheKey);
}
//TEST METHODS
protected int getCalculatedSize() {
return size;
}
protected int getActualElementsSize() {
final Enumeration e = cache.elements();
int result = 0;
while (e.hasMoreElements()) {
final CacheItem item = (CacheItem) e.nextElement();
result += item.data.length;
}
return result;
}
protected CacheItem getMRU() {
return mru;
}
}