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    }