File: DefaultEventTableModel.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 (272 lines) | stat: -rw-r--r-- 10,535 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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/* 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 ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.gui.WritableTableFormat;

import java.awt.EventQueue;

import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;

/**
 * A {@link TableModel} that holds an {@link EventList}. Each element of the list
 * corresponds to a row in the {@link TableModel}. The columns of the table are
 * specified using a {@link TableFormat}.
 *
 * <p>The EventTableModel 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
 * GlazedListsSwing#swingThreadProxyList(EventList).</p>
 *
 * @see <a href="http://publicobject.com/glazedlists/tutorial/">Glazed Lists Tutorial</a>
 *
 * @see GlazedListsSwing#swingThreadProxyList(EventList)
 * @see SwingUtilities#invokeAndWait(Runnable)
 * @see <a href="https://glazedlists.dev.java.net/issues/show_bug.cgi?id=112">Bug 112</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>
 *
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 */
public class DefaultEventTableModel<E> extends AbstractTableModel implements AdvancedTableModel<E>, ListEventListener<E> {

    /** the source of data for this TableModel, which may or may not be {@link #swingThreadSource} */
    protected EventList<E> source;

    /** indicator to dispose source list */
    private boolean disposeSource;

    /** specifies how column data is extracted from each row object */
    private TableFormat<? super E> tableFormat;

    /** reusable TableModelEvent for broadcasting changes */
    private final MutableTableModelEvent tableModelEvent = new MutableTableModelEvent(this);

    /**
     * Creates a new table model that extracts column data from the given
     * <code>source</code> using the the given <code>tableFormat</code>.
     *
     * @param source the EventList that provides the row objects
     * @param tableFormat the object responsible for extracting column data
     *      from the row objects
     */
    public DefaultEventTableModel(EventList<E> source, TableFormat<? super E> tableFormat) {
        this(source, false, tableFormat);
    }

    /**
     * Creates a new table model that extracts column data from the given
     * <code>source</code> using the the given <code>tableFormat</code>.
     *
     * @param source the EventList that provides the row objects
     * @param diposeSource <code>true</code> if the source list should be disposed when disposing
     *            this model, <code>false</code> otherwise
     * @param tableFormat the object responsible for extracting column data
     *      from the row objects
     */
    protected DefaultEventTableModel(EventList<E> source, boolean disposeSource, TableFormat<? super E> tableFormat) {
        this.source = source;
        this.disposeSource = disposeSource;
        this.tableFormat = tableFormat;
        source.addListEventListener(this);
    }

    /**
     * {@inheritDoc}
     */
    public TableFormat<? super E> getTableFormat() {
        return tableFormat;
    }

    /**
     * {@inheritDoc}
     */
    public void setTableFormat(TableFormat<? super E> tableFormat) {
        this.tableFormat = tableFormat;
        tableModelEvent.setStructureChanged();
        fireTableChanged(tableModelEvent);
    }

    /**
     * {@inheritDoc}
     */
    public E getElementAt(int index) {
        source.getReadWriteLock().readLock().lock();
        try {
            return source.get(index);
        } finally {
            source.getReadWriteLock().readLock().unlock();
        }
    }

    /**
     * For implementing the ListEventListener interface. This sends changes
     * to the table which repaints the table cells. Because this class is
     * backed by {@link GlazedListsSwing#swingThreadProxyList}, all natural
     * calls to this method are guaranteed to occur on the Swing EDT.
     */
    public void listChanged(ListEvent<E> listChanges) {
        handleListChange(listChanges);
    }

    /**
     * Default implementation for converting a {@link ListEvent} to
     * TableModelEvents. There will be one TableModelEvent per ListEvent block.
     * Subclasses may choose to implement a different conversion.
     *
     * @param listChanges ListEvent to translate
     */
    protected void handleListChange(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");

        // for all changes, one block at a time
        while (listChanges.nextBlock()) {
            // get the current change info
            int startIndex = listChanges.getBlockStartIndex();
            int endIndex = listChanges.getBlockEndIndex();
            int changeType = listChanges.getType();
            // create a table model event for this block
            tableModelEvent.setValues(startIndex, endIndex, changeType);
            fireTableChanged(tableModelEvent);
        }
    }

    /**
     * @return reusable TableModelEvent for broadcasting changes
     */
    protected final MutableTableModelEvent getMutableTableModelEvent() {
        return tableModelEvent;
    }

    /**
     * Fetch the name for the specified column.
     */
    @Override
    public String getColumnName(int column) {
        return tableFormat.getColumnName(column);
    }

    /**
     * The number of rows equals the number of entries in the source event list.
     */
    public int getRowCount() {
        source.getReadWriteLock().readLock().lock();
        try {
            return source.size();
        } finally {
            source.getReadWriteLock().readLock().unlock();
        }
    }

    /**
     * Get the column count as specified by the table format.
     */
    public int getColumnCount() {
        return tableFormat.getColumnCount();
    }

	/**
     * Gets the class of elements in the specified column. This behaviour can be
     * customized by implementing the {@link AdvancedTableFormat} interface.
	 */
	@Override
    public Class getColumnClass(int columnIndex) {
		// See if the TableFormat is specifies a column class
		if(tableFormat instanceof AdvancedTableFormat) {
			return ((AdvancedTableFormat)tableFormat).getColumnClass(columnIndex);
		// If not, use the default...
		} else {
            return super.getColumnClass(columnIndex);
        }
	}

    /**
     * Retrieves the value at the specified location of the table.
     */
    public Object getValueAt(int row, int column) {
        source.getReadWriteLock().readLock().lock();
        try {
            return tableFormat.getColumnValue(source.get(row), column);
        } finally {
            source.getReadWriteLock().readLock().unlock();
        }
    }

    /**
     * Delegates the question of whether the cell is editable or not to the
     * backing TableFormat if it is a {@link WritableTableFormat}. Otherwise,
     * the column is assumed to be uneditable.
     */
    @Override
    public boolean isCellEditable(int row, int column) {
        if (!(tableFormat instanceof WritableTableFormat))
            return false;

        source.getReadWriteLock().readLock().lock();
        try {
            final E toEdit = source.get(row);
            return ((WritableTableFormat<E>) tableFormat).isEditable(toEdit, column);
        } finally {
            source.getReadWriteLock().readLock().unlock();
        }
    }

    /**
     * Attempts to update the object for the given row with the
     * <code>editedValue</code>. This requires the backing TableFormat
     * be a {@link WritableTableFormat}. {@link WritableTableFormat#setColumnValue}
     * is expected to contain the logic for updating the object at the given
     * <code>row</code> with the <code>editedValue</code> which was in the
     * given <code>column</code>.
     */
    @Override
    public void setValueAt(Object editedValue, int row, int column) {
        // ensure this is a writable table
        if (!(tableFormat instanceof WritableTableFormat))
            throw new UnsupportedOperationException("Unexpected setValueAt() on read-only table");

        source.getReadWriteLock().writeLock().lock();
        try {
            // get the object being edited from the source list
            final E baseObject = source.get(row);

            // tell the table format to set the value based
            final WritableTableFormat<E> writableTableFormat = (WritableTableFormat<E>) tableFormat;
            final E updatedObject = writableTableFormat.setColumnValue(baseObject, editedValue, column);

            // if the edit was discarded we have nothing to do
            if (updatedObject != null) {
                // check if updating the baseObject has caused it to be removed from this
                // TableModel (FilterList) or moved to another location (SortedList)
                final boolean baseObjectHasNotMoved = row < getRowCount() && source.get(row) == baseObject;

                // if the row is still present in its original location, update it to induce a
                // TableModelEvent that will redraw that row in the table
                if (baseObjectHasNotMoved)
                    source.set(row, updatedObject);
            }
        } finally {
            source.getReadWriteLock().writeLock().unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    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;
    }
}