001 package data.swing;
002
003 import java.util.*;
004 import java.io.*;
005 import java.beans.*;
006
007 import util.*;
008 import util.swing.*;
009
010 import data.*;
011 import data.events.*;
012
013 /**
014 * A {@link javax.swing.table.TableModel} that models the contents of a {@link Catalog}.
015 *
016 * @author Steffen Zschaler
017 * @version 2.0 23/08/1999
018 * @since v2.0
019 */
020 public class CatalogTableModel extends AbstractTableModel implements HelpableListener, CatalogChangeListener,
021 PropertyChangeListener, Serializable {
022
023 /**
024 * ID for serialization.
025 */
026 private static final long serialVersionUID = -8033175225350079642L;
027
028 /**
029 * The DataBasket used to determine visibility.
030 *
031 * @serial
032 */
033 protected DataBasket m_dbBasket;
034
035 /**
036 * The Catalog that is being modelled.
037 *
038 * @serial
039 */
040 protected Catalog<?> m_cModel;
041
042 /**
043 * The Comparator that defines the sorting order of records in the model. It compares
044 * {@link CatalogItem CatalogItems}.
045 *
046 * @serial
047 */
048 protected Comparator<CatalogItem> m_cmpComparator = new NaturalComparator<CatalogItem>();
049
050 /**
051 * The internal model. A list of the CatalogItems' keys.
052 *
053 * @serial
054 */
055 protected List<String> m_lKeys;
056
057 /**
058 * Set the table's data. Data is {@link data.Catalog}
059 */
060 public void setData(Object n_cModel) {
061 m_cModel = (Catalog) n_cModel;
062 updateModel();
063 fireTableDataChanged();
064 }
065
066 public Comparator<CatalogItem> getComparator() {
067 return m_cmpComparator;
068 }
069
070 /**
071 * Create a new CatalogTableModel.
072 *
073 * @param c the Catalog to be modelled. May be {@link data.filters.CatalogFilter filtered}.
074 * @param db the DataBasket to be used to determine visibility.
075 * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered
076 * according to the natural ordering of the CatalogItems.
077 * @param ted a TableEntryDescriptor that can split individual CatalogItems into a table's cells.
078 */
079 public CatalogTableModel(Catalog c, DataBasket db, Comparator<CatalogItem> cmp, TableEntryDescriptor ted) {
080 super(ted);
081
082 m_dbBasket = db;
083 m_cModel = c;
084
085 if (cmp != null) {
086 m_cmpComparator = cmp;
087 }
088
089 listenerList = new ListenerHelper(this);
090
091 updateModel();
092 }
093
094 /**
095 * Get the record at the given index.
096 *
097 * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}).
098 * @return the {@link CatalogItem} to be displayed at the given index. May return <code>null</code> if
099 * either there is no record at the indicated position or an exception occurs.
100 *
101 * @override Never
102 */
103 public Object getRecord(int row) {
104 ((ListenerHelper)listenerList).needModelUpdate();
105
106 try {
107 if ((row > -1) && (row < getRowCount())) {
108 return m_cModel.get((String)m_lKeys.get(row), m_dbBasket, false);
109 } else {
110 return null;
111 }
112 }
113 catch (VetoException ex) {
114 return null;
115 }
116 }
117
118 /**
119 * Get the number of records in this model.
120 *
121 * @override Never
122 */
123 public int getRowCount() {
124 ((ListenerHelper)listenerList).needModelUpdate();
125
126 return m_lKeys.size();
127 }
128
129 // HelpableListener interface methods
130
131 /**
132 * Subscribe as a listener to the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
133 * subscribe as a listener.
134 *
135 * @override Never
136 */
137 public void subscribe() {
138 if (m_cModel instanceof ListenableCatalog) {
139 ((ListenableCatalog<?>)m_cModel).addCatalogChangeListener(this);
140 }
141 }
142
143 /**
144 * Un-Subscribe as a listener from the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
145 * un-subscribe as a listener.
146 *
147 * @override Never
148 */
149 public void unsubscribe() {
150 if (m_cModel instanceof ListenableCatalog) {
151 ((ListenableCatalog<?>)m_cModel).removeCatalogChangeListener(this);
152 }
153 }
154
155 /**
156 * Update the internal model based on the modelled {@link Catalog}.
157 *
158 * @override Never
159 */
160 public synchronized void updateModel() {
161 List<String> lKeys = new LinkedList<String>(m_cModel.keySet(m_dbBasket));
162 Collections.sort(lKeys, new Comparator<String>() {
163 public int compare(String s1, String s2) {
164 try {
165 return m_cmpComparator.compare(m_cModel.get(s1, m_dbBasket, false),
166 m_cModel.get(s2, m_dbBasket, false));
167 }
168 catch (VetoException ex) {
169 return 0;
170 }
171 }
172 });
173 m_lKeys = lKeys;
174 }
175
176 // CatalogChangeListener interface methods
177
178 /**
179 * Update the internal model and inform any listeners according to the received event.
180 *
181 * <p>This method is public as an implementation detail and must not be called directly.</p>
182 *
183 * @override Never
184 */
185 public void addedCatalogItem(CatalogChangeEvent e) {
186 if ((e.getBasket() == m_dbBasket) || (e.getBasket() == null)) {
187 checkAdd(e.getAffectedItem());
188 }
189 }
190
191 /**
192 * Update the internal model and inform any listeners according to the received event.
193 *
194 * <p>This method is public as an implementation detail and must not be called directly.</p>
195 *
196 * @override Never
197 */
198 public void commitedAddCatalogItem(CatalogChangeEvent e) {
199 checkAdd(e.getAffectedItem());
200 }
201
202 /**
203 * Update the internal model and inform any listeners according to the received event.
204 *
205 * <p>This method is public as an implementation detail and must not be called directly.</p>
206 *
207 * @override Never
208 */
209 public void rolledbackAddCatalogItem(CatalogChangeEvent e) {
210 if (e.getBasket() == m_dbBasket) {
211 checkRemove(e.getAffectedItem());
212 }
213 }
214
215 /**
216 * Update the internal model and inform any listeners according to the received event.
217 *
218 * <p>This method is public as an implementation detail and must not be called directly.</p>
219 *
220 * @override Never
221 */
222 public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException {}
223
224 /**
225 * Update the internal model and inform any listeners according to the received event.
226 *
227 * <p>This method is public as an implementation detail and must not be called directly.</p>
228 *
229 * @override Never
230 */
231 public void noRemoveCatalogItem(CatalogChangeEvent e) {}
232
233 /**
234 * Update the internal model and inform any listeners according to the received event.
235 *
236 * <p>This method is public as an implementation detail and must not be called directly.</p>
237 *
238 * @override Never
239 */
240 public void removedCatalogItem(CatalogChangeEvent e) {
241 checkRemove(e.getAffectedItem());
242 }
243
244 /**
245 * Update the internal model and inform any listeners according to the received event.
246 *
247 * <p>This method is public as an implementation detail and must not be called directly.</p>
248 *
249 * @override Never
250 */
251 public void commitedRemoveCatalogItem(CatalogChangeEvent e) {}
252
253 /**
254 * Update the internal model and inform any listeners according to the received event.
255 *
256 * <p>This method is public as an implementation detail and must not be called directly.</p>
257 *
258 * @override Never
259 */
260 public void rolledbackRemoveCatalogItem(CatalogChangeEvent e) {
261 checkAdd(e.getAffectedItem());
262 }
263
264 /**
265 * Update the internal model and inform any listeners according to the received event.
266 *
267 * <p>This method is public as an implementation detail and must not be called directly.</p>
268 *
269 * @override Never
270 */
271 public void canEditCatalogItem(CatalogChangeEvent e) throws VetoException {}
272
273 /**
274 * Update the internal model and inform any listeners according to the received event.
275 *
276 * <p>This method is public as an implementation detail and must not be called directly.</p>
277 *
278 * @override Never
279 */
280 public void noEditCatalogItem(CatalogChangeEvent e) {}
281
282 /**
283 * Update the internal model and inform any listeners according to the received event.
284 *
285 * <p>This method is public as an implementation detail and must not be called directly.</p>
286 *
287 * @override Never
288 */
289 public void editingCatalogItem(CatalogChangeEvent e) {
290 if (e.getBasket() != m_dbBasket) {
291 checkRemove(e.getAffectedItem());
292 } else {
293 e.getAffectedItem().addPropertyChangeListener(this);
294 }
295 }
296
297 /**
298 * Update the internal model and inform any listeners according to the received event.
299 *
300 * <p>This method is public as an implementation detail and must not be called directly.</p>
301 *
302 * @override Never
303 */
304 public void commitEditCatalogItem(CatalogChangeEvent e) {
305 if (e.getBasket() != m_dbBasket) {
306 checkAdd(e.getAffectedItem());
307 } else {
308 e.getAffectedItem().removePropertyChangeListener(this);
309
310 updateModel();
311 }
312 }
313
314 /**
315 * Update the internal model and inform any listeners according to the received event.
316 *
317 * <p>This method is public as an implementation detail and must not be called directly.</p>
318 *
319 * @override Never
320 */
321 public void rollbackEditCatalogItem(CatalogChangeEvent e) {
322 if (e.getBasket() != m_dbBasket) {
323 checkAdd(e.getAffectedItem());
324 } else {
325 e.getAffectedItem().removePropertyChangeListener(this);
326
327 updateModel();
328 fireTableDataChanged();
329 }
330 }
331
332 /**
333 * Update the internal model and inform any listeners according to the received event.
334 *
335 * <p>This method is public as an implementation detail and must not be called directly.</p>
336 *
337 * @override Never
338 */
339 public void propertyChange(PropertyChangeEvent e) {
340 if (e.getSource()instanceof CatalogItem) {
341 checkUpdate((CatalogItem)e.getSource());
342 }
343 }
344
345 /**
346 * Internal helper method. Check where, if at all, the given CatalogItem has been added with respect to the
347 * internal model.
348 *
349 * @param ci the added CatalogItem
350 *
351 * @override Never
352 */
353 protected synchronized void checkAdd(CatalogItem ci) {
354 updateModel();
355
356 int nIdx = m_lKeys.indexOf(ci.getName());
357
358 if (nIdx > -1) {
359 fireTableRowsInserted(nIdx, nIdx);
360 }
361 }
362
363 /**
364 * Internal helper method. Check from where, if at all, the given CatalogItem has been removed with respect
365 * to the internal model.
366 *
367 * @param ci the removed CatalogItem
368 *
369 * @override Never
370 */
371 protected synchronized void checkRemove(CatalogItem ci) {
372 int nIdx = m_lKeys.indexOf(ci.getName());
373
374 updateModel();
375
376 if (nIdx > -1) {
377 fireTableRowsDeleted(nIdx, nIdx);
378 }
379 }
380
381 /**
382 * Internal helper method. Check for updates in the given CatalogItem.
383 *
384 * @param ci the updated CatalogItem
385 *
386 * @override Never
387 */
388 protected synchronized void checkUpdate(CatalogItem ci) {
389 int nIdx1 = m_lKeys.indexOf(ci.getName());
390
391 updateModel();
392
393 int nIdx2 = m_lKeys.indexOf(ci.getName());
394
395 if (nIdx1 == -1) {
396 if (nIdx2 > -1) {
397 fireTableRowsInserted(nIdx2, nIdx2);
398 } else {
399 return;
400 }
401 } else {
402 if (nIdx2 == -1) {
403 fireTableRowsDeleted(nIdx1, nIdx1);
404 } else {
405 if (nIdx1 > nIdx2) {
406 nIdx2 = nIdx1;
407 nIdx1 = nIdx2;
408 }
409
410 fireTableRowsUpdated(nIdx1, nIdx2);
411 }
412 }
413 }
414 }