public class ObservableList<T> extends AbstractList<T> implements RandomAccess
java.util.List in an observable structure.
This is primarily intended for UI development, so the existing java.awt.event.ListDataListener interface is used to notify observers about changes to this list. (Debatably a better observer interface could be developed, but at least for the time being this doesn't seem necessary.)
This object distinguishes between two types of listeners:
addSynchronizedListener(ListDataListener, boolean)) will be notified in a way that guarantees the list has not been modified since the ListDataEvent occurred. That is: neither this listener nor any preceding synchronized listeners can modify this list.addUnsynchronizedListener(ListDataListener, boolean, boolean)) has the option to modify this list. And because any other preceding unsynchronized listener may have already modified this listener: it is not safe for an unsynchronized listener to assume the incoming ListDataEvent still contains valid indices.The level of detail used to calculate ListDataEvents will vary, depending on whether any attached listener asked for a high level of detail. Often in UIs a developer effectively needs a ChangeListener to indicate something (anything!) changed: if that is all you're interested in, then you can save some computational effort by not receiving highly detailed events. Many operations (such as set(int, Object) or remove(int)) always return precise events because they are trivial to compute, but set operations like retainAll(Collection) or setAll(List) can be expensive for large lists: in these methods the level of detail you asked for is taken into consideration.
This list is designed to support access from multiple threads. Any read-like operation (get(int), indexOf(Object), or even containsAll(Collection)) can run simultaneously with other read operations. However write-like operations (set(int, Object), clear(), setAll(List)) are mutually exclusive. Even read operations are not allowed during part of a write operation. (However once the operation itself is completed -- but before all listeners have completed -- read operations are allowed.)
Because this list uses its own model of synchronization, it is not necessary for the underlying delegate list to be synchronized.
Note however that if you bypass this ObservableList, then you have unsynchronized and unobserved changes:
ArrayList myList = new ArrayList(); ObservableList myObservableList = new ObservableList(myList); myList.addAll(...);
ListModelsAlso for further convenience this object has two methods related to ListModels:
getListModelEDTMirror(): this creates a separate list mirroring this list that is only ever updated in the event dispatch thread. Pro's: thread-safe. Con's: potentially expensive redundancy, and there will be (brief) times when the EDT still refers to elements you already removed.getListModelView(boolean): this provides a real-time view of this list as a ListModel. Pro's: very light weight. Con's: if used in a JList, then this ObservableList should only ever be updated in the EDT.This class implements the RandomAccess interfaces, because most of the time this list is expected to delegate to list that implements RandomAccess, like the java.util.ArrayList. (The ArrayList is the default list model inside this list.) In the future if this becomes a questionable assumption we might introduce factory methods to create the appropriate ObservableList subclass.
| Modifier and Type | Class and Description |
|---|---|
static class |
ObservableList
|
static class |
ObservableList
|
protected static class |
ObservableList
|
static class |
ObservableList
This is thrown when notifying a
ListDataListener resulted in further modifying this list when it shouldn't have.
|
| Modifier and Type | Field and Description |
|---|---|
protected List |
list
|
protected ThreadedSemaphore |
listenerSyncedSemaphore
|
protected ThreadedSemaphore |
listenerUnsyncedSemaphore
|
protected Set |
readOnlyAccess
|
protected Semaphore |
readSemaphore
|
protected Vector |
synchronizedListeners
|
protected Vector |
unsynchronizedListeners
|
protected ReentrantLock |
writeLock
|
modCount| Constructor and Description |
|---|
ObservableList()
Create a ObservableList using an
ArrayList as its implementation.
|
ObservableList(List
Create a ObservableList using the argument provided.
|
ObservableList(T... elements)
Create a ObservableList using an
ArrayList as its implementation with the elements provided.
|
| Modifier and Type | Method and Description |
|---|---|
void |
add(int index, T element)
|
boolean |
add(T element)
|
boolean |
addAll(Collection
|
boolean |
addAll(int index, Collection
|
boolean |
addAll(int index, T... array)
|
boolean |
addAll(T... array)
|
void |
addSynchronizedChangeListener(ChangeListener
Adds a
ChangeListener that will be notified of changes to this list in a synchronized lock.
|
void |
addSynchronizedListener(ListDataListener
Adds a
ListDataListener that will be notified of changes to this list in a synchronized lock.
|
void |
addUnsynchronizedChangeListener(ChangeListener
Add an unsynchronized
ChangeListener that will be notified of changes to this list in an unprotected thread.
|
void |
addUnsynchronizedListener(ListDataListener
Add an unsynchronized
ListDataListener that will be notified of changes to this list in an unprotected thread.
|
void |
clear()
|
Object |
clone()
Creates a
ObservableList with the same list structure, but with no listeners.
|
boolean |
contains(Object
|
boolean |
containsAll(Collection
|
boolean |
containsAll(Object
|
boolean |
equals(Object
|
protected <R> R |
execute(com
This method executes all Operations, keeping thread safety and listeners in mind.
|
protected void |
fireEvent(Vector
|
T |
get(int index)
|
ObservableList |
getListModelEDTMirror()
This returns a non-mutable
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
|
ObservableList |
getListModelEDTMirror(ObservableList
This returns a non-mutable
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
|
ListModel |
getListModelView(boolean updateListenersSynchronously)
This returns a non-mutable
ListModel view of this list.
|
int |
hashCode()
|
int |
indexOf(Object
|
boolean |
isEmpty()
|
int |
lastIndexOf(Object
|
T |
remove(int index)
|
boolean |
remove(Object
|
boolean |
removeAll(Collection
|
boolean |
removeAll(Object
|
void |
removeRange(int index0, int index1)
Remove a range of elements from this list.
|
boolean |
removeSynchronizedChangeListener(ChangeListener
Remove this synchronized listener from this list.
|
boolean |
removeSynchronizedListener(ListDataListener
Remove this synchronized listener from this list.
|
boolean |
removeUnsynchronizedChangeListener(ChangeListener
Remove this unsynchronized listener from this list.
|
boolean |
removeUnsynchronizedListener(ListDataListener
Remove this unsynchronized listener from this list.
|
boolean |
retainAll(Collection
|
boolean |
retainAll(Object
|
T |
set(int index, T newElement)
|
boolean |
setAll(List
|
boolean |
setAll(T... array)
|
int |
size()
|
T[] |
toArray(Class
Create an array returning all the elements of this list.
|
String |
toString()
|
iterator, listIterator, listIterator, subListtoArray, toArrayfinalize, getClass, notify, notifyAll, wait, wait, waitreplaceAll, sort, spliterator, toArray, toArrayparallelStream, removeIf, streamprotected final ReentrantLockwriteLock
protected final SemaphorereadSemaphore
protected final ThreadedSemaphorelistenerSyncedSemaphore
protected final ThreadedSemaphorelistenerUnsyncedSemaphore
protected final Vector<ObservableList .Listener > synchronizedListeners
protected final Vector<ObservableList .Listener > unsynchronizedListeners
public ObservableList()
ArrayList as its implementation.
public ObservableList(T... elements)
ArrayList as its implementation with the elements provided.
public void addSynchronizedListener(ListDataListenerlistener, boolean detailedEvents)
ListDataListener that will be notified of changes to this list in a synchronized lock. This lock guarantees that no other thread will alter the contents of this list until this listener returns control of the thread.
For example: if this list received an addition then the listener's intervalAdded() method will be notified. Other threads may simultaneously poll this list (by calling get(int) or size() or even containsAll(Collection)), but no other thread can modify the contents of this list (for example by calling remove(int) or clear()) until this listener is finished processing the event.
Because of this lock: it is important that synchronized listeners be relatively lightweight. If this listener can trigger a cascade of other listeners and complex operations: it should either:
SwingUtilities.invokeLater(Runnable)), orBecause there might be other ListDataListeners waiting to be notified when this listener returns, and because those listeners are also guaranteed that the list will exactly reflect the ListDataEvent they are receiving: this listener is not allowed to further modify this list. If this listener tries to modify this list (directly or indirectly) a RecursiveListenerModificationException will be thrown.
listener - the listener to add to this list.
detailedEvents - if true then the
ListDataEvents all listeners receive will be as detailed as possible. For example: when a complicated operation is performed (like
retainAll(Collection) or
setAll(List)) calculating a "detailed event" involves inspecting the before and after states of this list, and after reviewing 1,000,000 elements: it might be clear that the event the appropriate event is just a
ListDataEvent.INTERVAL_REMOVED event of 2 elements. However if no listeners are interested in detailed events, then the same operation might notify listeners of a
ListDataEvent.CONTENTS_CHANGED event ranging from elements [0, 1000000]. If you just want to know when something (anything) changed: then
detailedEvents should be false for performance. But if you want to know more specific information, this argument should be
true.
listener - the listener to add to this list.
removeSynchronizedListener(ListDataListener),
addUnsynchronizedListener(ListDataListener, boolean, boolean)
public boolean removeSynchronizedListener(ListDataListenerlistener)
public void addUnsynchronizedListener(ListDataListenerlistener, boolean detailedEvents, boolean allowsModification)
ListDataListener that will be notified of changes to this list in an unprotected thread.
For example: if an addition is made to this list, then the listener's intervalAdded() method is called. However during this method: another thread might call list.clear(), so the ListDataEvent the listener received may not contain specific reliable data anymore.
This method should be used if the listener is not especially interested in specifics, or may be very expensive and cannot risk blocking further modifications for a long period of time.
listener - the listener to add to this list.
detailedEvents - if true then the
ListDataEvents all listeners receive will be as detailed as possible. For example: when a complicated operation is performed (like
retainAll(Collection) or
setAll(List)) calculating a "detailed event" involves inspecting the before and after states of this list, and after reviewing 1,000,000 elements: it might be clear that the event the appropriate event is just a
ListDataEvent.INTERVAL_REMOVED event of 2 elements. However if no listeners are interested in detailed events, then the same operation might notify listeners of a
ListDataEvent.CONTENTS_CHANGED event ranging from elements [0, 1000000]. If you just want to know when something (anything) changed: then
detailedEvents should be false for performance. But if you want to know more specific information, this argument should be
true.
allowsModification - if this is false then and if this listener tries to further modify this list: a
RecursiveListenerModificationException will be thrown. This is largely intended as a safeguard for developers to prevent against unintended looping/cascading listeners.
removeUnsynchronizedListener(ListDataListener),
addSynchronizedListener(ListDataListener, boolean)
public void addSynchronizedChangeListener(ChangeListenerchangeListener)
ChangeListener that will be notified of changes to this list in a synchronized lock. This lock guarantees that no other thread will alter the contents of this list until this listener returns control of the thread.
changeListener - the listener to add to this list.
changeListener - the listener to add to this list.
removeSynchronizedChangeListener(ChangeListener),
addSynchronizedListener(ListDataListener, boolean)
public void addUnsynchronizedChangeListener(ChangeListenerchangeListener, boolean allowsModification)
ChangeListener that will be notified of changes to this list in an unprotected thread.
changeListener - the listener to add to this list.
allowsModification - if this is false then and if this listener tries to further modify this list: a
RecursiveListenerModificationException will be thrown. This is largely intended as a safeguard for developers to prevent against unintended looping/cascading listeners.
removeUnsynchronizedChangeListener(ChangeListener),
addUnsynchronizedListener(ListDataListener, boolean, boolean)
public boolean removeUnsynchronizedChangeListener(ChangeListenerchangeListener)
public boolean removeSynchronizedChangeListener(ChangeListenerchangeListener)
public boolean removeUnsynchronizedListener(ListDataListenerlistener)
protected void fireEvent(Vector<ObservableList .Listener > listeners, ThreadedSemaphore semaphore, ListDataEvent event)
protected <R> R execute(com.bric .util .ObservableList .Operation <R> operation)
R - the parameterized type of the operation
operation - the operation to execute
public boolean add(T element)
public void add(int index,
T element)public boolean addAll(Collection<? extends T> collection)
public boolean addAll(int index,
Collection<? extends T> collection) public boolean addAll(T... array)
public boolean addAll(int index,
T... array)public void clear()
public void removeRange(int index0,
int index1)
removeRange in class
AbstractList<T>
index0 - the first element to remove
index1 - the last element to remove
public boolean remove(Objectelement)
public T remove(int index)
public boolean removeAll(Collection<?> collection)
public boolean removeAll(Object... array)
public boolean retainAll(Collection<?> collection)
public boolean retainAll(Object... array)
public boolean setAll(T... array)
public T get(int index)
public boolean contains(Objectelement)
public boolean containsAll(Collection<?> collection)
public boolean containsAll(Object... array)
public int indexOf(Objectelement)
public boolean isEmpty()
public int lastIndexOf(Objectelement)
public int size()
public ObservableList.EDTMirror <T> getListModelEDTMirror()
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
This is intended for JLists, or other UI elements.
Because this is intentionally multithreaded: it is likely that this mirror will briefly contain outdated elements. That is to say: removing an element from this list does not mean the UI is done interacting with it.
This mirror requires maintaining a separate copy of this list. Most references in Java are 8 bytes, so if you have a 1,024 element list: this list will be approximately 8KB, and each additional EDT mirror you construct will be another 8KB. This may be an acceptable amount (depending on how many of these lists you have), but if you have a 1,000,000-element list, then this list is about 8 MB, and each EDT mirror is another 8 MB. My point is: if this list may have several thousand elements, then this mirror is not trivial to construct and maintain.
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
getListModelView(boolean)
public ObservableList.EDTMirror <T> getListModelEDTMirror(ObservableList .Filter <T> t)
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
This is intended for JLists, or other UI elements.
Because this is intentionally multithreaded: it is likely that this mirror will briefly contain outdated elements. That is to say: removing an element from this list does not mean the UI is done interacting with it.
This mirror requires maintaining a separate copy of this list. Most references in Java are 8 bytes, so if you have a 1,024 element list: this list will be approximately 8KB, and each additional EDT mirror you construct will be another 8KB. This may be an acceptable amount (depending on how many of these lists you have), but if you have a 1,000,000-element list, then this list is about 8 MB, and each EDT mirror is another 8 MB. My point is: if this list may have several thousand elements, then this mirror is not trivial to construct and maintain.
t - an optional filter that restricts what the mirrored list contains.
ListModel mirror of this list that is guaranteed to only update in the event dispatch thread.
getListModelView(boolean)
public ListModelgetListModelView(boolean updateListenersSynchronously)
ListModel view of this list. This
ListModel notifies its listeners of changes exactly as they occur on the original thread.
This view is an interface that interacts directly with this list; it is not a copy of this list like getListModelEDTMirror(). So if this list occupies 8 MB of memory: invoking this method will allocate less than 1 KB of memory.
The downside to this view is: it does not make any guarantees regarding the event dispatch thread. So if this new ListModel is being used in a JList: you need to take responsibility to only update this list in the event dispatch thread.
If the ObservableList implemented the ListModel interface: then this method wouldn't be unnecessary. (That is: the list model view and this list would be the same object.) The decision to not make this class a ListModel was a deliberate design choice to force developers to think about whether they needed a view or an EDT-specific mirror.
getListModelEDTMirror()
public Objectclone()
ObservableList with the same list structure, but with no listeners.
public boolean equals(Objectobj)
public int hashCode()
public StringtoString()
public T[] toArray(Class<T> componentType)
This method uses the existing read/write locking architecture the ObservableList uses, so it is safer than synchronizing this list to extract the elements separately.
componentType - the component type of the array.