ca.odell.glazedlists.DebugList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glazedlists_java15 Show documentation
Show all versions of glazedlists_java15 Show documentation
Event-driven lists for dynamically filtered and sorted tables
/* 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 ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.event.ListEventPublisher;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;
import ca.odell.glazedlists.util.concurrent.Lock;
import ca.odell.glazedlists.util.concurrent.LockFactory;
import java.util.*;
/**
* DebugList is meant to be used as a drop-in replacement for
* {@link BasicEventList} at the root of pipelines of {@link EventList}s during
* development. It provides methods for turning on various types of assertions
* which throw {@link RuntimeException}s when they are violated. The goal is to
* detect and fail fast on error conditions in much the same way Iterators
* commonly throw {@link java.util.ConcurrentModificationException}s.
*
* Some of the assertions that are controlled by this DebugList include:
*
*
* - {@link #setLockCheckingEnabled(boolean)} toggles whether this DebugList
* asserts that all read operations are guarded by read locks and all
* write operations are guarded by write locks.
*
*
- {@link #getSanctionedReaderThreads()} is the Set of Threads which are
* allowed to read from the DebugList. If the Set is empty then
* ALL Threads are assumed to be sanctioned readers.
*
*
- {@link #getSanctionedWriterThreads()} is the Set of Threads which are
* allowed to write to the DebugList. If the Set is empty then
* ALL Threads are assumed to be sanctioned writers.
*
*
* This class is left non-final to allow subclassing but since it is a
* debugging tool, we make no guarantees about backward compatibility between
* releases. It is meant to evolve as users discover new assertions to be added.
*
* @author James Lemieux
*/
public class DebugList extends AbstractEventList {
/** A flag controlling whether we check locks before performing reads and writes. */
private boolean lockCheckingEnabled;
/** The Set of Threads that are allowed to read from this DebugList; empty Set indicates all Threads may read. */
private final Set sanctionedReaderThreads = new HashSet();
/** The Set of Threads that are allowed to write to this DebugList; empty Set indicates all Threads may write. */
private final Set sanctionedWriterThreads = new HashSet();
/** A delegate EventList that implements the actual List operations. */
private final EventList delegate;
/** A special ReadWriteLock that reports the Threads that own the read lock and write lock at any given time. */
private final DebugReadWriteLock debugReadWriteLock = new DebugReadWriteLock();
/**
* Constructs a DebugList which, by default, performs no debugging. It must
* be customized after construction to turn on the assertions which make
* sense for the list pipeline.
*/
public DebugList() {
// use a normal BasicEventList as the delegate implementation
this.delegate = new BasicEventList(this.debugReadWriteLock);
this.delegate.addListEventListener(new ListEventForwarder());
}
/**
* This private ListEventListener simply forwards updates to the delegate
* BasicEventList since we're only decorating the BasicEventList.
*/
private class ListEventForwarder implements ListEventListener {
public void listChanged(ListEvent listChanges) {
updates.forwardEvent(listChanges);
}
}
/**
* Returns true if DebugList is currently checking the calling
* Thread for lock ownership before each read and write operation.
*/
public boolean isLockCheckingEnabled() {
return lockCheckingEnabled;
}
/**
* If lockCheckingEnabled
is true this DebugList will
* check the calling Thread for lock ownership before each read and write
* operation; false indicates this check shouldn't be performed.
*/
public void setLockCheckingEnabled(boolean lockCheckingEnabled) {
this.lockCheckingEnabled = lockCheckingEnabled;
}
/**
* Returns the {@link Set} of Threads that are allowed to perform reads on
* this DebugList. If the {@link Set} is empty, all Threads are allowed to
* read from this DebugList. Users are expected to add and remove Threads
* directly on this {@link Set}.
*/
public Set getSanctionedReaderThreads() {
return sanctionedReaderThreads;
}
/**
* Returns the {@link Set} of Threads that are allowed to perform writes on
* this DebugList. If the {@link Set} is empty, all Threads are allowed to
* write to this DebugList. Users are expected to add and remove Threads
* directly on this {@link Set}.
*/
public Set getSanctionedWriterThreads() {
return sanctionedWriterThreads;
}
/**
* This generic method is called immediately before any read operation is
* invoked. All generic read assertions should take place here.
*/
protected void beforeReadOperation() {
// if a Set of reader Threads have been given, ensure the current Thread is one of them
if (!sanctionedReaderThreads.isEmpty() && !sanctionedReaderThreads.contains(Thread.currentThread()))
throw new IllegalStateException("DebugList detected an unexpected Thread (" + Thread.currentThread() + ") attempting to perform a read operation");
// if lock checking is enabled, ensure the current Thread holds a read or write lock before continuing
if (isLockCheckingEnabled() && !debugReadWriteLock.isThreadHoldingReadOrWriteLock())
throw new IllegalStateException("DebugList detected a failure to acquire the readLock prior to a read operation");
}
/**
* This method is currently a no-op and exists for parity. Subclasses may
* choose to insert extract assertion logic here.
*/
protected void afterReadOperation() { }
/**
* This generic method is called immediately before any write operation is
* invoked. All generic write assertions should take place here.
*/
protected void beforeWriteOperation() {
// if a Set of writer Threads have been given, ensure the current Thread is one of them
if (!sanctionedWriterThreads.isEmpty() && !sanctionedWriterThreads.contains(Thread.currentThread()))
throw new IllegalStateException("DebugList detected an unexpected Thread (" + Thread.currentThread() + ") attempting to perform a write operation");
// if lock checking is enabled, ensure the current Thread holds a write lock before continuing
if (isLockCheckingEnabled() && !debugReadWriteLock.isThreadHoldingWriteLock())
throw new IllegalStateException("DebugList detected a failure to acquire the writeLock prior to a write operation");
}
/**
* This method is currently a no-op and exists for parity. Subclasses may
* choose to insert extract assertion logic here.
*/
protected void afterWriteOperation() { }
/** {@inheritDoc} */
public ReadWriteLock getReadWriteLock() {
return delegate.getReadWriteLock();
}
/** {@inheritDoc} */
public ListEventPublisher getPublisher() {
return delegate.getPublisher();
}
/** {@inheritDoc} */
public E get(int index) {
beforeReadOperation();
try {
return delegate.get(index);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public int size() {
beforeReadOperation();
try {
return delegate.size();
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public boolean contains(Object object) {
beforeReadOperation();
try {
return delegate.contains(object);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public boolean containsAll(Collection> collection) {
beforeReadOperation();
try {
return delegate.containsAll(collection);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public boolean equals(Object object) {
beforeReadOperation();
try {
return delegate.equals(object);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public int hashCode() {
beforeReadOperation();
try {
return delegate.hashCode();
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public int indexOf(Object object) {
beforeReadOperation();
try {
return delegate.indexOf(object);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public int lastIndexOf(Object object) {
beforeReadOperation();
try {
return delegate.lastIndexOf(object);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public boolean isEmpty() {
beforeReadOperation();
try {
return delegate.isEmpty();
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public Object[] toArray() {
beforeReadOperation();
try {
return delegate.toArray();
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public T[] toArray(T[] array) {
beforeReadOperation();
try {
return delegate.toArray(array);
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public String toString() {
beforeReadOperation();
try {
return delegate.toString();
} finally {
afterReadOperation();
}
}
/** {@inheritDoc} */
public boolean add(E value) {
beforeWriteOperation();
try {
return delegate.add(value);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public boolean remove(Object toRemove) {
beforeWriteOperation();
try {
return delegate.remove(toRemove);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public boolean addAll(Collection extends E> values) {
beforeWriteOperation();
try {
return delegate.addAll(values);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public boolean addAll(int index, Collection extends E> values) {
beforeWriteOperation();
try {
return delegate.addAll(index, values);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public boolean removeAll(Collection> values) {
beforeWriteOperation();
try {
return delegate.removeAll(values);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public boolean retainAll(Collection> values) {
beforeWriteOperation();
try {
return delegate.retainAll(values);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public void clear() {
beforeWriteOperation();
try {
delegate.clear();
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public E set(int index, E value) {
beforeWriteOperation();
try {
return delegate.set(index, value);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public void add(int index, E value) {
beforeWriteOperation();
try {
delegate.add(index, value);
} finally {
afterWriteOperation();
}
}
/** {@inheritDoc} */
public E remove(int index) {
beforeWriteOperation();
try {
return delegate.remove(index);
} finally {
afterWriteOperation();
}
}
/**
* This special ReadWriteLock can answer the question of whether the current
* Thread holds the read or write lock.
*/
private static class DebugReadWriteLock implements ReadWriteLock {
private final DebugLock readLock;
private final DebugLock writeLock;
public DebugReadWriteLock() {
// decorate normaly read/write locks with Thread recording
final ReadWriteLock decorated = LockFactory.DEFAULT.createReadWriteLock();
this.readLock = new DebugLock(decorated.readLock());
this.writeLock = new DebugLock(decorated.writeLock());
}
public Lock readLock() { return readLock; }
public Lock writeLock() { return writeLock; }
/**
* Returns true if and only if the current Thread holds the
* write lock.
*/
public boolean isThreadHoldingWriteLock() {
return writeLock.getThreadsHoldingLock().contains(Thread.currentThread());
}
/**
* Returns true if the current Thread holds the read lock or
* write lock.
*/
public boolean isThreadHoldingReadOrWriteLock() {
return readLock.getThreadsHoldingLock().contains(Thread.currentThread()) ||
writeLock.getThreadsHoldingLock().contains(Thread.currentThread());
}
/**
* A special wrapper around a conventional Lock which tracks the
* Threads that current hold the lock.
*/
private static class DebugLock implements Lock {
private final Lock delegate;
private final List threadsHoldingLock = new ArrayList();
public DebugLock(Lock delegate) {
this.delegate = delegate;
}
public void lock() {
delegate.lock();
// record the current Thread as a lock holder
threadsHoldingLock.add(Thread.currentThread());
}
public boolean tryLock() {
final boolean success = delegate.tryLock();
// if the lock was successfully acquired, record the current Thread as a lock holder
if (success) threadsHoldingLock.add(Thread.currentThread());
return success;
}
public void unlock() {
delegate.unlock();
// remove the current Thread as a lock holder
threadsHoldingLock.remove(Thread.currentThread());
}
/**
* Returns the List of Threads holding the lock.
*/
public List getThreadsHoldingLock() {
return threadsHoldingLock;
}
}
}
}