1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
/**
* A DefaultEventListModel adapts an EventList to the ListModel interface making it
* appropriate for use with a {@link JList}. Each element of the list
* corresponds to an element in the {@link ListModel}.
*
* <p>The DefaultEventListModel class is <strong>not thread-safe</strong>. Unless
* otherwise noted, all methods are only safe to be called from the event
* dispatch thread. To do this programmatically, use {@link SwingUtilities#invokeAndWait(Runnable)}
* and wrap the source list (or some part of the source list's pipeline) using
* {@link GlazedListsSwing#swingThreadProxyList(EventList)}.
*
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=14">Bug 14</a>
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=146">Bug 146</a>
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=177">Bug 177</a>
* @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=228">Bug 228</a>
* @see SwingUtilities#invokeAndWait(Runnable)
*
* @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
* @author Holger Brands
*/
public class DefaultEventListModel<E> implements ListEventListener<E>, ListModel {
/** the source EventList */
protected EventList<E> source;
/** indicator to dispose source list */
private boolean disposeSource;
/** whom to notify of data changes */
private final List<ListDataListener> listeners = new ArrayList<ListDataListener>();
/** recycle the list data event to prevent unnecessary object creation */
protected final MutableListDataEvent listDataEvent = new MutableListDataEvent(this);
/**
* Creates a new model that contains all objects located in the given
* <code>source</code> and reacts to any changes in the given
* <code>source</code>.
*
* @param source the EventList that provides the elements
*/
public DefaultEventListModel(EventList<E> source) {
this(source, false);
}
/**
* Creates a new model that contains all objects located in the given
* <code>source</code> and reacts to any changes in the given
* <code>source</code>.
*
* @param source the EventList that provides the elements
* @param diposeSource <code>true</code> if the source list should be disposed when disposing
* this model, <code>false</code> otherwise
*/
protected DefaultEventListModel(EventList<E> source, boolean disposeSource) {
this.source = source;
this.disposeSource = disposeSource;
this.source.addListEventListener(this);
}
/**
* For implementing the ListEventListener interface. This sends changes
* to the table which can repaint the table cells. It's checked that all natural
* calls to this method arrive on the Swing thread.
*
* <p>This always sends discrete changes for the complete size of the list.
* It may be more efficient to implement a threshhold where a large list
* of changes are grouped together as a single change. This is how the
* ListTable accepts large change events.
*/
public void listChanged(ListEvent<E> listChanges) {
if (!EventQueue.isDispatchThread())
throw new IllegalStateException("Events to " + this.getClass().getSimpleName() + " must arrive on the EDT - consider adding GlazedListsSwing.swingThreadProxyList(source) somewhere in your list pipeline");
// build an "optimized" ListDataEvent describing the precise range of rows in the first block
listChanges.nextBlock();
final int startIndex = listChanges.getBlockStartIndex();
final int endIndex = listChanges.getBlockEndIndex();
listDataEvent.setRange(startIndex, endIndex);
final int changeType = listChanges.getType();
switch (changeType) {
case ListEvent.INSERT: listDataEvent.setType(ListDataEvent.INTERVAL_ADDED); break;
case ListEvent.DELETE: listDataEvent.setType(ListDataEvent.INTERVAL_REMOVED); break;
case ListEvent.UPDATE: listDataEvent.setType(ListDataEvent.CONTENTS_CHANGED); break;
}
// if another block exists, fallback to using a generic "data changed" ListDataEvent
if (listChanges.nextBlock()) {
listDataEvent.setRange(0, Integer.MAX_VALUE);
listDataEvent.setType(ListDataEvent.CONTENTS_CHANGED);
}
fireListDataEvent(listDataEvent);
}
/**
* Returns the value at the specified index.
*
* @param index the requested index
* @return the value at <code>index</code>
*/
public Object getElementAt(int index) {
source.getReadWriteLock().readLock().lock();
try {
return source.get(index);
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* Gets the size of the list.
*/
public int getSize() {
source.getReadWriteLock().readLock().lock();
try {
return source.size();
} finally {
source.getReadWriteLock().readLock().unlock();
}
}
/**
* Registers the specified ListDataListener to receive updates whenever
* this list changes.
*
* <p>The specified ListDataListener must <strong>not</strong> save a
* reference to the ListDataEvent beyond the end of the notification
* method. This is because the ListDataEvent is re-used to increase
* the performance of this implementation.
*/
public void addListDataListener(ListDataListener listDataListener) {
listeners.add(listDataListener);
}
/**
* Deregisters the specified ListDataListener from receiving updates
* whenever this list changes.
*/
public void removeListDataListener(ListDataListener listDataListener) {
listeners.remove(listDataListener);
}
/**
* Notifies all ListDataListeners about one block of changes in the list.
*/
protected void fireListDataEvent(ListDataEvent listDataEvent) {
// notify all listeners about the event
for(int i = 0, n = listeners.size(); i < n; i++) {
ListDataListener listDataListener = listeners.get(i);
switch (listDataEvent.getType()) {
case ListDataEvent.CONTENTS_CHANGED: listDataListener.contentsChanged(listDataEvent); break;
case ListDataEvent.INTERVAL_ADDED: listDataListener.intervalAdded(listDataEvent); break;
case ListDataEvent.INTERVAL_REMOVED: listDataListener.intervalRemoved(listDataEvent); break;
}
}
}
/**
* Releases the resources consumed by this {@link DefaultEventListModel} so that it
* may eventually be garbage collected.
*
* <p>An {@link DefaultEventListModel} will be garbage collected without a call to
* {@link #dispose()}, but not before its source {@link EventList} is garbage
* collected. By calling {@link #dispose()}, you allow the {@link DefaultEventListModel}
* to be garbage collected before its source {@link EventList}. This is
* necessary for situations where an {@link DefaultEventListModel} is short-lived but
* its source {@link EventList} is long-lived.
*
* <p><strong><font color="#FF0000">Warning:</font></strong> It is an error
* to call any method on an {@link DefaultEventListModel} after it has been disposed.
* As such, this {@link DefaultEventListModel} should be detached from its
* corresponding Component <strong>before</strong> it is disposed.
*/
public void dispose() {
source.removeListEventListener(this);
if (disposeSource) source.dispose();
// this encourages exceptions to be thrown if this model is incorrectly accessed again
source = null;
}
}
|