/*******************************************************************************
* Copyright (c) 2006-2010, G. Weirich and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* G. Weirich - initial implementation
*
*******************************************************************************/
package ch.elexis.core.data.cache;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import ch.rgw.tools.Log;
/**
* A Cache with soft references and optional expiring items The cache keeps count on numbers of items
* that are added, removed or expired and can display its statistic
*
* @author Gerry
*/
@SuppressWarnings("unchecked")
public class SoftCache<K> implements IPersistentObjectCache<K> {
private static boolean enabled = true;
protected Map<K, CacheEntry> cache;
protected long hits, misses, removed, inserts, expired;
protected Log log = Log.get(SoftCache.class.getName());
public SoftCache(){
// must be thread-safe
cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>());
}
/**
*
* @param num the initial cache capcity
* @param load the load factor
*/
public SoftCache(final int num, final float load){
cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>(num, load));
}
public SoftCache(final int num){
cache = Collections.synchronizedMap(new HashMap<K, CacheEntry>(num));
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#put(K, java.lang.Object, int)
*/
public void put(final K key, final Object object, final int timeToCacheInSeconds){
if (enabled) {
cache.put(key, new CacheEntry(object, timeToCacheInSeconds));
inserts++;
}
}
public Object get(final K key, final int timeToCacheInSeconds) {
return get(key);
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#get(K)
*/
public Object get(final K key){
if (!enabled) {
return null;
}
synchronized (cache) {
CacheEntry ref = cache.get(key);
if (ref == null) {
misses++;
return null;
}
Object ret = ref.get();
if (ret == null) {
remove(key);
return null;
} else {
hits++;
return ret;
}
}
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#remove(K)
*/
public void remove(final K key){
synchronized (cache) {
cache.remove(key);
removed++;
}
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#clear()
*/
public void clear(){
synchronized (cache) {
purge();
cache.clear();
}
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#stat()
*/
public void stat(){
long total = hits + misses + removed + expired;
if (total != 0) {
StringBuilder sb = new StringBuilder();
sb.append("--------- cache statistics ------\n").append("Total read:\t").append(total)
.append("\n").append("cache hits:\t").append(hits).append(" (")
.append(hits * 100 / total).append("%)\n").append("object expired:\t")
.append(expired).append(" (").append(expired * 100 / total).append("%)\n")
.append("cache missed:\t").append(misses).append(" (").append(misses * 100 / total)
.append("%)\n").append("object removed:\t").append(removed).append(" (")
.append(removed * 100 / total).append("%)\n").append("Object inserts:\t")
.append(inserts).append("\n");
log.log(sb.toString(), Log.INFOS);
}
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#purge()
*/
public void purge(){
synchronized (cache) {
Iterator<Entry<K, CacheEntry>> it = cache.entrySet().iterator();
long freeBefore = Runtime.getRuntime().freeMemory();
while (it.hasNext()) {
Entry<K, CacheEntry> e = it.next();
CacheEntry ce = e.getValue();
ce.expires = 0;
ce.get();
it.remove();
}
// TODO change logging for debug mode
// if (Hub.plugin.DEBUGMODE) {
// long freeAfter = Runtime.getRuntime().freeMemory();
// StringBuilder sb = new StringBuilder();
// sb.append("Cache purge: Free memore before: ").append(freeBefore)
// .append(", free memory after: ").append(freeAfter).append("\n");
// Hub.log.log(sb.toString(), Log.INFOS);
// }
}
}
/*
* (non-Javadoc)
*
* @see ch.elexis.data.cache.IPersistentObjectCache#reset()
*/
public synchronized void reset(){
purge();
cache.clear();
}
public class CacheEntry extends SoftReference {
long expires;
public CacheEntry(final Object obj, final int timeInSeconds){
super(obj);
expires = System.currentTimeMillis() + timeInSeconds * 1000;
}
@Override
public synchronized Object get(){
Object ret = super.get();
if (System.currentTimeMillis() > expires) {
expired++;
super.clear();
ret = null;
}
return ret;
}
}
}