All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.odell.glazedlists.DebugList Maven / Gradle / Ivy

/* 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.Lock;
import ca.odell.glazedlists.util.concurrent.LockFactory;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;

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 EventList delegate; /** Forwards ListEvents received from the {@link #delegate} */ private final ListEventListener delegateWatcher = new ListEventForwarder(); /** A special ReadWriteLock that reports the Threads that own the read lock and write lock at any given time. */ private final DebugReadWriteLock 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() { this(null, new DebugReadWriteLock()); } /** * Creates a {@link DebugList} using the specified * {@link ListEventPublisher} and {@link ReadWriteLock}. This is * particularly useful when multiple {@link DebugList}s are used within a * {@link CompositeList} and must share the same lock and publisher. */ private DebugList(ListEventPublisher publisher, DebugReadWriteLock debugReadWriteLock) { this.debugReadWriteLock = debugReadWriteLock; // use a normal BasicEventList as the delegate implementation this.delegate = new BasicEventList(publisher, this.debugReadWriteLock); this.delegate.addListEventListener(delegateWatcher); } /** * 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; } /** * Returns a new empty {@link DebugList} which shares the same * {@link ListEventListener} and {@link ReadWriteLock} with this DebugList. * This method is particularly useful when debugging a {@link CompositeList} * where some member lists are DebugLists and thus must share an identical * publisher and locks in order to participate in the CompositeList. */ public DebugList createNewDebugList() { return new DebugList(getPublisher(), debugReadWriteLock); } /** * 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} */ @Override public ReadWriteLock getReadWriteLock() { return delegate.getReadWriteLock(); } /** {@inheritDoc} */ @Override public ListEventPublisher getPublisher() { return delegate.getPublisher(); } /** {@inheritDoc} */ @Override public E get(int index) { beforeReadOperation(); try { return delegate.get(index); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public int size() { beforeReadOperation(); try { return delegate.size(); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public boolean contains(Object object) { beforeReadOperation(); try { return delegate.contains(object); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public boolean containsAll(Collection collection) { beforeReadOperation(); try { return delegate.containsAll(collection); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public boolean equals(Object object) { beforeReadOperation(); try { return delegate.equals(object); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public int hashCode() { beforeReadOperation(); try { return delegate.hashCode(); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public int indexOf(Object object) { beforeReadOperation(); try { return delegate.indexOf(object); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public int lastIndexOf(Object object) { beforeReadOperation(); try { return delegate.lastIndexOf(object); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public boolean isEmpty() { beforeReadOperation(); try { return delegate.isEmpty(); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public Object[] toArray() { beforeReadOperation(); try { return delegate.toArray(); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public T[] toArray(T[] array) { beforeReadOperation(); try { return delegate.toArray(array); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public String toString() { beforeReadOperation(); try { return delegate.toString(); } finally { afterReadOperation(); } } /** {@inheritDoc} */ @Override public boolean add(E value) { beforeWriteOperation(); try { return delegate.add(value); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public boolean remove(Object toRemove) { beforeWriteOperation(); try { return delegate.remove(toRemove); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public boolean addAll(Collection values) { beforeWriteOperation(); try { return delegate.addAll(values); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public boolean addAll(int index, Collection values) { beforeWriteOperation(); try { return delegate.addAll(index, values); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public boolean removeAll(Collection values) { beforeWriteOperation(); try { return delegate.removeAll(values); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public boolean retainAll(Collection values) { beforeWriteOperation(); try { return delegate.retainAll(values); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public void clear() { beforeWriteOperation(); try { delegate.clear(); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public E set(int index, E value) { beforeWriteOperation(); try { return delegate.set(index, value); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public void add(int index, E value) { beforeWriteOperation(); try { delegate.add(index, value); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ @Override public E remove(int index) { beforeWriteOperation(); try { return delegate.remove(index); } finally { afterWriteOperation(); } } /** {@inheritDoc} */ public void dispose() { delegate.removeListEventListener(this.delegateWatcher); delegate = null; } /** * 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; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy