package org.hypergraphdb.transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGRandomAccessResult;
import org.hypergraphdb.util.CloneMe;
import org.hypergraphdb.util.HGSortedSet;
/**
* <p>
* A transactional {@link HGSortedSet} that implements MVCC for concurrency instead of
* locking. It uses an underlying implementation that is copied-on-write: when a transaction
* attempts to modify the set, it is cloned into a local copy and that copy receives all
* the writes. In addition, each write operation is recorded in a write log. At commit time,
* if there's no conflict, the write log is replayed on the latest committed version of the
* set (which may be different from the copy the transaction started from). This may sound
* a bit counter-intuitive: if the set is only being written to during a transaction, there won't
* be a conflict, even if another transaction modified and committed in the meantime. A conflict
* will occur only if the set has been read, but somebody else in the meantime modified it.
* </p>
*
* <p>
* Assuming the underlying set implementation is only accessed in the context of HGDB
* transactions, it doesn't need to be thread-safe, it doesn't need any locks.
* </p>
*
* @author Borislav Iordanov
*
* @param <E>
*/
public class TxSet<E> implements HGSortedSet<E>
{
// Set modif operations, recorded in the write log.
private static final int ADD = 0;
private static final int REMOVE = 1;
private static final int CLEAR = 2;
private static final int ADDALL = 3;
private static final int REMOVEALL = 4;
private static final int RETAINALL = 5;
static class LogEntry
{
int op;
Object el;
}
HGTransactionManager txManager;
SetTxBox<E> S;
void log(int op, Object el)
{
LogEntry entry = new LogEntry();
entry.op = op;
entry.el = el;
List<LogEntry> log = txManager.getContext().getCurrent().getTopLevel().getAttribute(S);
if (log == null)
{
log = new ArrayList<LogEntry>();
txManager.getContext().getCurrent().getTopLevel().setAttribute(S, log);
}
log.add(entry);
}
@SuppressWarnings("unchecked")
void applyLog(List<LogEntry> log, HGSortedSet<E> set)
{
for (LogEntry e : log)
switch (e.op)
{
case ADD: { set.add((E)e.el); break; }
case REMOVE: { set.remove((E)e.el); break; }
case CLEAR: { set.clear(); break; }
case ADDALL: { set.addAll((Collection<E>)e.el); break; }
case REMOVEALL: { set.removeAll((Collection<E>)e.el); break; }
case RETAINALL: { set.retainAll((Collection<E>)e.el); break; }
}
}
@SuppressWarnings("unchecked")
HGSortedSet<E> cloneSet(HGSortedSet<E> S)
{
if (S instanceof CloneMe)
return ((CloneMe)S).duplicate();
else
{
try
{
HGSortedSet<E> S2 = S.getClass().newInstance();
S2.addAll(S);
return S2;
}
catch (Exception ex)
{
throw new HGException(ex);
}
}
}
HGSortedSet<E> read()
{
return S.get();
}
HGSortedSet<E> write()
{
List<LogEntry> log = txManager.getContext().getCurrent().getTopLevel().getAttribute(S);
if (log == null) // should we copy-on-write?
{
HGSortedSet<E> readOnly = S.get(); // S.getForWrite();
HGSortedSet<E> writeable = cloneSet(readOnly);
S.put(writeable);
}
return S.get();
}
protected TxSet()
{
}
public TxSet(final HGTransactionManager txManager, final HGSortedSet<E> backingSet)
{
this.txManager = txManager;
this.S = new SetTxBox<E>(txManager, backingSet, this);
}
// public boolean isInTransaction()
// {
// return S.getTxCount() > 0;
// }
public HGRandomAccessResult<E> getSearchResult()
{
return read().getSearchResult();
}
public Comparator<? super E> comparator()
{
return read().comparator();
}
public E first()
{
return read().first();
}
public SortedSet<E> headSet(E toElement)
{
return read().headSet(toElement);
}
public E last()
{
return read().last();
}
public SortedSet<E> subSet(E fromElement, E toElement)
{
return read().subSet(fromElement, toElement);
}
public SortedSet<E> tailSet(E fromElement)
{
return read().tailSet(fromElement);
}
public boolean add(E e)
{
boolean b = write().add(e);
if (b)
log(ADD, e);
return b;
}
public boolean addAll(Collection<? extends E> c)
{
boolean b = write().addAll(c);
if (b)
log(ADDALL, c);
return b;
}
public void clear()
{
write().clear();
log(CLEAR, null);
}
public boolean contains(Object o)
{
return read().contains(o);
}
public boolean containsAll(Collection<?> c)
{
return read().containsAll(c);
}
public boolean isEmpty()
{
return read().isEmpty();
}
public Iterator<E> iterator()
{
return read().iterator();
}
public boolean remove(Object o)
{
boolean b = write().remove(o);
if (b)
log(REMOVE, o);
return b;
}
public boolean removeAll(Collection<?> c)
{
boolean b = write().removeAll(c);
if (b)
log(REMOVEALL, c);
return b;
}
public boolean retainAll(Collection<?> c)
{
boolean b = write().retainAll(c);
if (b)
log(RETAINALL, c);
return b;
}
public int size()
{
return read().size();
}
public Object[] toArray()
{
return read().toArray();
}
public <T> T[] toArray(T[] a)
{
return read().toArray(a);
}
public static class SetTxBox<E> extends VBox<HGSortedSet<E>>
{
// private Map<HGTransaction, Boolean> txs =
// new ConcurrentHashMap<HGTransaction, Boolean>();
TxSet<E> thisSet;
SetTxBox(final HGTransactionManager txManager,
final HGSortedSet<E> backingSet,
final TxSet<E> thisSet)
{
super(txManager, backingSet);
this.thisSet = thisSet;
}
// int getTxCount()
// {
// return txs.size();
// }
//
// @Override
// public HGSortedSet<E> get()
// {
// HGTransaction tx = txManager.getContext().getCurrent();
// if (tx != null)
// txs.put(tx, Boolean.TRUE);
// return super.get();
// }
//
// @Override
// public HGSortedSet<E> getForWrite()
// {
// HGTransaction tx = txManager.getContext().getCurrent();
// if (tx != null)
// txs.put(tx, Boolean.TRUE);
// return super.getForWrite();
// }
HGSortedSet<E> getLastCommitted(HGTransaction tx)
{
return body.getBody(tx.getNumber()).value;
}
@Override
public VBoxBody<HGSortedSet<E>> commit(HGTransaction tx, HGSortedSet<E> newvalue, long txNumber)
{
if (tx != null)
{
HGSortedSet<E> lastCommitted = getLastCommitted(tx);
List<LogEntry> log = tx.getTopLevel().getAttribute(this);
if (log != null) // did we do any modifications to the set?
{
lastCommitted = thisSet.cloneSet(lastCommitted);
thisSet.applyLog(log, lastCommitted);
return super.commit(tx, lastCommitted, txNumber);
}
else
return body; // nothing to do, we've just been reading
}
else
{
return super.commit(tx, newvalue, txNumber); // hope caller knows what they are doing
}
}
// @Override
// public void finish(HGTransaction tx)
// {
// if (tx != null)
// txs.remove(tx);
// }
};
}