ca.odell.glazedlists.TransactionList Maven / Gradle / Ivy
Show all versions of glazedlists_java15 Show documentation
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;
import ca.odell.glazedlists.event.ListEvent;
import java.util.List;
import java.util.ArrayList;
/**
* A list transformation that presents traditional transaction semantics.
* Typical usage resembles one of two methods:
*
*
* EventList source = ...
* TransactionList txList = new TransactionList(source);
*
* // begin a transaction in which all ListEvents are collected by txList
* // into a single "super ListEvent", which is fired on commit
* txList.beginEvent(true);
*
* // fill in the details of the transaction
* // (these operations actually physically change ONLY txList and its source)
* txList.add("A new element");
* txList.set(0, "A changed element");
* txList.remove(6);
*
* // commit the transaction, which will broadcast a single ListEvent from
* // txList describing the aggregate of all changes made during the transaction
* // (this returns the entire list pipeline to a consistent state)
* txList.commitEvent();
*
*
* In this usage, all ListEventListeners "downstream" of TransactionList remain
* clueless about changes made during a transaction. As a result, the
* "list pipeline" is allowed to temporarily fall into an inconsistent state
* because only a portion of the pipeline (TransactionList and lower) has seen
* changes made during the transaction. Users must ensure that they do not
* read or write through any "downstream" EventList that depends on the
* TransactionList during a transaction. Typically this is done using the
* built-in {@link #getReadWriteLock() locks}.
*
* If the transaction was rolled back instead of committed, the txList would
* not produce a ListEvent, since none of its listeners would be aware of any
* changes made during the transaction.
*
* The second popular usage resembles this:
*
*
* EventList source = ...
* TransactionList txList = new TransactionList(source);
*
* // begin a transaction in which we change the ListEvent
* txList.beginEvent(); // this is the same as txList.beginEvent(false);
*
* // fill in the details of the transaction
* // (these operations actually physically change the ENTIRE PIPELINE)
* txList.add("A new element");
* txList.set(0, "A changed element");
* txList.remove(6);
*
* // commit the transaction, which will NOT broadcast a ListEvent from
* // txList because all of its listeners are already aware of the changes
* // made during the transaction
* txList.commitEvent();
*
*
* In this case, the "list pipeline" always remains consistent and reads/writes
* may occur through any part EventList in the pipeline without error.
*
* If the transaction is rolled back instead of committed, the txList
* produces a ListEvent describing the rollback, since its listeners are fully
* aware of the changes made during the transaction and must also be given a
* chance to undo their changes.
*
*
Transactions may be nested arbitrarily deep using code that resembles:
*
* txList.beginEvent();
* txList.add("A");
*
* txList.beginEvent();
* txList.set(0, "B");
* txList.commitEvent();
*
* txList.beginEvent();
* txList.add("C");
* txList.commitEvent();
* txList.commitEvent();
*
*
* @author James Lemieux
*/
public class TransactionList extends TransformedList {
/** produces {@link UndoRedoSupport.Edit}s which are collected during a transaction to support rollback */
private UndoRedoSupport rollbackSupport;
/** A stack of transactions contexts; one for each layer of nested transaction */
private final List txContextStack = new ArrayList();
/**
* Constructs a TransactionList
that provides traditional
* transaction semantics over the given source
.
*
* @param source the EventList over which to provide a transactional view
*/
public TransactionList(EventList source) {
this(source, true);
}
/**
* Constructs a TransactionList
that provides traditional
* transaction semantics over the given source
.
*
* If rollbackSupport
is true then this
* TransactionList supports calling {@link #rollbackEvent()} during a
* transaction. This constructor exists solely to break the constructor
* cycle between UndoRedoSupport and TransactionList and should only be
* used internally by Glazed Lists.
*
* @param source the EventList over which to provide a transactional view
* @param rollbackSupport true indicates this TransactionList must
* support the rollback ability; false indicates it is not
* necessary
*/
TransactionList(EventList source, boolean rollbackSupport) {
super(source);
// if rollback support is requested, build the necessary infrastructure
if (rollbackSupport) {
this.rollbackSupport = UndoRedoSupport.install(source);
this.rollbackSupport.addUndoSupportListener(new RollbackSupportListener());
}
source.addListEventListener(this);
}
/**
* Demarks the beginning of a transaction which accumulates all ListEvents
* received during the transaction and fires a single aggregate ListEvent
* on {@link #commitEvent()}.
*/
public void beginEvent() {
beginEvent(true);
}
/**
* Demarks the beginning of a transaction. If buffered
is
* true then all ListEvents received during the transaction are
* accumulated and fired as a single aggregate ListEvent on
* {@link #commitEvent()}. If buffered
is false then
* all ListEvents received during the transaction are forwarded immediately
* and {@link #commitEvent()} produces no ListEvent of its own.
*
* @param buffered true indicates ListEvents should be buffered and
* sent on {@link #commitEvent()}; false indicates they should
* be sent on immediately
*/
public void beginEvent(boolean buffered) {
// start a nestable ListEvent if we're supposed to buffer them
if (buffered)
updates.beginEvent(true);
// push a new context onto the stack describing this new transaction
txContextStack.add(new Context(buffered));
}
/**
* Demarks the successful completion of a transaction. If changes were
* buffered during the transaction by calling {@link #beginEvent(boolean) beginEvent(true)}
* then a single ListEvent will be fired from this TransactionList
* describing the changes accumulated during the transaction.
*/
public void commitEvent() {
// verify that there is a transaction to roll back
if (rollbackSupport != null && txContextStack.isEmpty())
throw new IllegalStateException("No ListEvent exists to commit");
// pop the last context off the stack and ask it to commit
txContextStack.remove(txContextStack.size()-1).commit();
}
/**
* Demarks the unsuccessful completion of a transaction. If changes were
* NOT buffered during the transaction by calling {@link #beginEvent(boolean) beginEvent(false)}
* then a single ListEvent will be fired from this TransactionList
* describing the rollback of the changes accumulated during the transaction.
*/
public void rollbackEvent() {
// check if this TransactionList was created with rollback abilities
if (rollbackSupport == null)
throw new IllegalStateException("This TransactionList does not support rollback");
// check if a transaction exists to rollback
if (txContextStack.isEmpty())
throw new IllegalStateException("No ListEvent exists to roll back");
// pop the last context off the stack and ask it to rollback
txContextStack.remove(txContextStack.size()-1).rollback();
}
/** {@inheritDoc} */
@Override
protected boolean isWritable() {
return true;
}
/** @inheritDoc */
@Override
public void dispose() {
if (rollbackSupport != null)
rollbackSupport.uninstall();
rollbackSupport = null;
txContextStack.clear();
super.dispose();
}
/**
* Simply forwards all of the listChanges
since TransactionList
* doesn't transform the source data in any way.
*/
@Override
public void listChanged(ListEvent listChanges) {
updates.forwardEvent(listChanges);
}
/**
* Accumulates all of the small Edits that occur during a transaction
* within a CompositeEdit that can be undone to support rollback, if
* necessary.
*/
private class RollbackSupportListener implements UndoRedoSupport.Listener {
public void undoableEditHappened(UndoRedoSupport.Edit edit) {
// if a tx context exists we are in the middle of a transaction
if (!txContextStack.isEmpty())
txContextStack.get(txContextStack.size()-1).add(edit);
}
}
/**
* A small object describing the details about the transaction that was
* started so that it can be properly committed or rolled back at a later
* time. Specifically it tracks:
*
*
* - a CompositeEdit which can be used to undo the transaction's changes
* in the case of a rollback
* - a flag indicating wether a ListEvent was started when the
* transaction began (and thus must be committed or discarded later)
*
*/
private final class Context {
/** collects the smaller intermediate Edits that occur during a transaction; null
if no transaction exists */
private UndoRedoSupport.CompositeEdit rollbackEdit = rollbackSupport == null ? null : rollbackSupport.new CompositeEdit();
/**
* true indicates a ListEvent was started when this Context
* was created and must be committed or rolled back later.
*/
private boolean eventStarted = false;
public Context(boolean eventStarted) {
this.eventStarted = eventStarted;
}
/**
* Add the given edit into this Context to support its possible rollback.
*/
public void add(UndoRedoSupport.Edit edit) {
if (rollbackEdit != null)
rollbackEdit.add(edit);
}
/**
* Commit the changes associated with this transaction.
*/
public void commit() {
rollbackEdit = null;
if (eventStarted)
updates.commitEvent();
}
/**
* Rollback the changes associated with this transaction.
*/
public void rollback() {
if (rollbackEdit != null) {
// rollback all changes from the transaction as a single ListEvent
updates.beginEvent(true);
try {
rollbackEdit.undo();
} finally {
updates.commitEvent();
}
rollbackEdit = null;
}
// throw away the ListEvent if we started one
if (eventStarted)
updates.discardEvent();
}
}
}