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