File: DefaultEventListModel.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (200 lines) | stat: -rw-r--r-- 8,516 bytes parent folder | download | duplicates (3)
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;
    }
}