package pt.ist.fenixframework.core;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import pt.ist.fenixframework.core.AbstractDomainObject;
public class SharedIdentityMap implements IdentityMap {
private final static SharedIdentityMap instance = new SharedIdentityMap();
private static final ReferenceQueue<AbstractDomainObject> refQueue = new ReferenceQueue<AbstractDomainObject>();
private ConcurrentHashMap<Object,CacheEntry> cache;
public SharedIdentityMap() {
this.cache = new ConcurrentHashMap<Object,CacheEntry>();
}
public static SharedIdentityMap getCache() {
return instance;
}
@Override
public AbstractDomainObject cache(AbstractDomainObject obj) {
processQueue();
Object key = obj.getOid();
CacheEntry newEntry = new CacheEntry(obj, key, this.refQueue);
return cacheNewEntry(newEntry, obj);
}
private AbstractDomainObject cacheNewEntry(CacheEntry newEntry, AbstractDomainObject obj) {
CacheEntry entryInCache = putIfAbsent(this.cache, newEntry.key, newEntry);
if (entryInCache == newEntry) {
return obj;
} else {
AbstractDomainObject objInCache = entryInCache.get();
if (objInCache != null) {
return objInCache;
} else {
// the entry in cache was GCed already, so remove it and retry
removeEntry(entryInCache);
return cacheNewEntry(newEntry, obj);
}
}
}
@Override
public AbstractDomainObject lookup(Object key) {
processQueue();
CacheEntry entry = this.cache.get(key);
if (entry != null) {
AbstractDomainObject result = entry.get();
if (result != null) {
return result;
} else {
removeEntry(entry);
return null;
}
} else {
return null;
}
}
private void removeEntry(CacheEntry entry) {
this.cache.remove(entry.key, entry);
}
/* This method stores the new value if an older one didn't exist already. In either case it returns the value that was left
* in the cache. This implementation works as intended because we assume that we never store a null value in the
* cache. Otherwise, map.putIfAbsent would not put a new value over a null entry.
*
* This method's behaviour is very important to ensure that we do not inadvertently permit more than one reference to the same
* domain object to wander around in the system.
*/
private static <K,V> V putIfAbsent(ConcurrentHashMap<K,V> map, K key, V value) {
V oldValue = map.putIfAbsent(key, value);
return ((oldValue == null) ? value : oldValue);
}
private void processQueue() {
CacheEntry gcedEntry = (CacheEntry)refQueue.poll();
while (gcedEntry != null) {
removeEntry(gcedEntry);
gcedEntry = (CacheEntry)refQueue.poll();
}
}
private static class CacheEntry extends SoftReference<AbstractDomainObject> {
private final Object key;
CacheEntry(AbstractDomainObject object, Object key, ReferenceQueue q) {
super(object, q);
this.key = key;
}
}
}