package org.hypergraphdb.transaction;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.hypergraphdb.util.RefResolver;
/**
*
* <p>
* A transactional map - every operation on this map is conducted within a transaction and
* becomes void if the transaction is aborted. This map doesn't support <code>null</code>s
* as values - a null indicates absence of a value.
* </p>
*
* @author Borislav Iordanov
*
* @param <K>
* @param <V>
*/
public class TxMap<K, V> implements Map<K, V>
{
//private ConcurrentMap<K, VBox<V>> M = new ConcurrentHashMap<K, VBox<V>>();
protected Map<K, Box> M = null;
protected HGTransactionManager txManager;
protected RefResolver<Object, Box> boxGetter = null;
protected VBox<Integer> sizebox = null;
protected class Box extends VBox<V>
{
WeakReference<K> key;
public Box(HGTransactionManager txManager, K key)
{
super(txManager);
this.key = new WeakReference<K>(key);
}
public VBoxBody<V> makeNewBody(V value, long version, VBoxBody<V> next)
{
return new BoxBody(value, version, next);
}
private class BoxBody extends VBoxBody<V>
{
public BoxBody(V value, long version, VBoxBody<V> next)
{
super(value, version, next);
}
public void clearPrevious()
{
super.clearPrevious();
if (value == null && body == this)
{
synchronized (M)
{
M.remove(key.get());
}
}
}
}
public void finish(HGTransaction tx)
{
if (body.version == 0)
synchronized (M) { M.remove(key.get()); }
}
}
@SuppressWarnings("unchecked")
protected Box getBox(Object key)
{
synchronized (M)
{
Box box = M.get(key);
// Assume calls to this map are synchronized otherwise. If not, a ConcurrentMap needs to be
// used if we want lock free behavior. So the caching needs yet another rework for the
// WeakRef maps to be lock free.
if (box == null)
{
box = new Box(txManager, (K)key);
M.put((K)key, box);
}
return box;
// Use this with a ConcurrentMap:
// return (box == null) ? M.putIfAbsent((K)key, new VBox<V>(txManager.getContext())) : box;
}
}
/**
* <p>
* The constructor expects a {@link HGTransactionManager} and the map to be used
* to hold the entries. The <code>backingMap</code> parameter must be an empty map
* and it is not fully typed since internal object wrappers will be used to manage
* the transactional aspect.
* </p>
*/
@SuppressWarnings("unchecked")
public TxMap(HGTransactionManager tManager, Map backingMap)
{
this.txManager = tManager;
this.M = backingMap == null ? new ConcurrentHashMap<K, Box>(): backingMap;
this.sizebox = new VBox<Integer>(txManager);
this.sizebox.put(0);
if (this.M instanceof ConcurrentMap)
boxGetter = new RefResolver<Object,Box>()
{
private ConcurrentMap<K, Box> cm = (ConcurrentMap<K, Box>)M;
public Box resolve(Object k)
{
Box box = cm.get(k);
if (box == null)
box = new Box(txManager, (K)k);
Box box2 = cm.putIfAbsent((K)k, box);
return box2 != null ? box2 : box;
}
};
else
boxGetter = new RefResolver<Object,Box>()
{
public Box resolve(Object k)
{
return getBox(k);
}
};
}
public boolean containsKey(Object key)
{
return get(key) != null;
}
public V get(Object key)
{
return boxGetter.resolve(key).get();
}
public V put(K key, V value)
{
VBox<V> box = boxGetter.resolve(key);
V old = box.get();
if (old == null)
{
box.put(value);
if (value != null)
sizebox.put(sizebox.get() + 1);
}
else if (old != value)
{
box.put(value);
if (value == null)
sizebox.put(sizebox.get() - 1);
}
return old;
}
public void putAll(Map<? extends K, ? extends V> m)
{
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
public V remove(Object key)
{
return put((K)key, null);
}
public boolean isEmpty()
{
return size() == 0;
}
public int mapSize()
{
return M.size();
}
public int size()
{
return sizebox.get();
}
public Set<K> keySet()
{
return M.keySet();
}
public Collection<V> values()
{
return null;
}
public boolean containsValue(Object value)
{
throw new UnsupportedOperationException();
}
public Set<java.util.Map.Entry<K, V>> entrySet()
{
return null;
}
public void clear()
{
//throw new UnsupportedOperationException();
M.clear();
}
}