001    package data.swing;
002    
003    import data.*;
004    import data.events.*;
005    
006    import util.*;
007    import util.swing.*;
008    
009    import java.beans.*;
010    import java.util.*;
011    import java.io.*;
012    
013    /**
014     * A {@link javax.swing.table.TableModel} that models the contents of a {@link CountingStock}.
015     *
016     * @author Steffen Zschaler
017     * @version 2.0 23/08/1999
018     * @since v2.0
019     */
020    public class CountingStockTableModel extends AbstractTableModel implements HelpableListener,
021            StockChangeListener, CatalogChangeListener, PropertyChangeListener, Serializable {
022    
023        /**
024             * ID for serialization.
025             */
026            private static final long serialVersionUID = -7326063182237043116L;
027    
028            /**
029         * The DataBasket used to determine visibility.
030         *
031         * @serial
032         */
033        protected DataBasket m_dbBasket;
034    
035        /**
036         * The CountingStock being modelled. May be {@link data.filters.CountingStockFilter filtered}.
037         *
038         * @serial
039         */
040        protected CountingStock m_csModel;
041    
042        /**
043         * The Comparator that defines the sorting order of records in the model. It compares the keys of the
044         * actual items.
045         *
046         * @serial
047         */
048        protected Comparator<CatalogItem> m_cmpComparator = new NaturalComparator<CatalogItem>();
049    
050        /**
051         * If true, show lines informing about a zero amount of objects.
052         *
053         * @serial
054         */
055        protected boolean m_fShowZeros;
056    
057        /**
058         * The internal model. A list of the items' keys.
059         *
060         * @serial
061         */
062        protected List<String> m_lKeys;
063        
064        /**
065         * set the table's data. Data is {@link data.CountingStock}
066         */
067        public void setData(Object n_csModel) {
068            m_csModel = (CountingStock) n_csModel;
069            updateModel();
070            fireTableDataChanged();         
071        }
072    
073        /**
074         * Create a new CountingStockTableModel.
075         *
076         * @param cs the Stock to be modelled.
077         * @param db the DataBasket to be used to determine visibility.
078         * @param cmp the Comparator defining the sorting order. If <code>null</code> the records will be sorted
079         * according to the natural ordering of their keys.
080         * @param fShowZeros if true, lines informing about a zero amount of objects will be shown.
081         * @param ted a TableEntryDescriptor that can split a {@link Record} into a table's cells.
082         */
083        public CountingStockTableModel(CountingStock cs, DataBasket db, Comparator<CatalogItem> cmp, boolean fShowZeros,
084                TableEntryDescriptor ted) {
085            super(ted);
086    
087            m_dbBasket = db;
088            m_csModel = cs;
089    
090            if (cmp != null) {
091                m_cmpComparator = cmp;
092            }
093    
094            m_fShowZeros = fShowZeros;
095    
096            listenerList = new ListenerHelper(this);
097    
098            updateModel();
099        }
100    
101        /**
102         * A {@link CountingStockTableModel}'s record.
103         *
104         * <p>The record is basically a combination of a {@link CatalogItem} and a number indicating the number of
105         * objects available.</p>
106         *
107         * @author Steffen Zschaler
108         * @version 2.0 23/08/1999
109         * @since v2.0
110         */
111        public static class Record implements Comparable<Record> {
112    
113            /**
114             * The CatalogItem part of the record.
115             */
116            // Changed 11/09/2000-STEFFEN to private to fix F5.
117            private CatalogItem m_ciDescriptor;
118    
119            /**
120             * The number of actually available items.
121             */
122            // Changed 11/09/2000-STEFFEN to private to fix F5.
123            private int m_nCount;
124    
125            /**
126             * Create a new Record.
127             */
128            public Record(CatalogItem ci, int nCount) {
129                super();
130    
131                m_ciDescriptor = ci;
132                m_nCount = nCount;
133            }
134    
135            /**
136             * Compare by descriptor.
137             */
138            public int compareTo(Record r) {
139                return m_ciDescriptor.compareTo(r.getDescriptor());
140            }
141    
142            /**
143             * Get the CatalogItem describing the items represented by this record.
144             */
145            public CatalogItem getDescriptor() {
146                return m_ciDescriptor;
147            }
148    
149            /**
150             * Get the number of items in this record.
151             */
152            public int getCount() {
153                return m_nCount;
154            }
155        }
156    
157        /**
158         * Get the record at the given index.
159         *
160         * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}).
161         * @return the {@link Record} to be displayed at the given index. May return <code>null</code> if
162         * either there is no record at the indicated position or an exception occurs.
163         *
164         * @override Never
165         */
166        public Object getRecord(int row) {
167            ((ListenerHelper)listenerList).needModelUpdate();
168    
169            try {
170                if ((row > -1) && (row < getRowCount())) {
171                    String sKey = (String)m_lKeys.get(row);
172    
173                    return new Record(m_csModel.getCatalog(m_dbBasket).get(sKey, m_dbBasket, false),
174                            m_csModel.countItems(sKey, m_dbBasket));
175                } else {
176                    return null;
177                }
178            }
179            catch (VetoException ve) {
180                return null;
181            }
182        }
183    
184        /**
185         * Get the number of records in this model.
186         *
187         * @override Never
188         */
189        public int getRowCount() {
190            ((ListenerHelper)listenerList).needModelUpdate();
191    
192            return m_lKeys.size();
193        }
194    
195        // HelpableListener interface methods
196        /**
197         * Subscribe as a listener to the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
198         * subscribe as a listener. If the modelled {@link CountingStock} is a {@link ListenableStock}, subscribe as
199         * a listener.
200         *
201         * @override Never
202         */
203        public void subscribe() {
204            if (m_csModel instanceof ListenableStock) {
205                ((ListenableStock)m_csModel).addStockChangeListener(this);
206            }
207    
208            if (m_csModel.getCatalog(m_dbBasket)instanceof ListenableCatalog) {
209                ((ListenableCatalog)m_csModel.getCatalog(m_dbBasket)).addCatalogChangeListener(this);
210            }
211        }
212    
213        /**
214         * Un-Subscribe as a listener from the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
215         * un-subscribe as a listener. If the modelled {@link CountingStock} is a {@link ListenableStock},
216         * un-subscribe as a listener.
217         *
218         * @override Never
219         */
220        public void unsubscribe() {
221            if (m_csModel instanceof ListenableStock) {
222                ((ListenableStock)m_csModel).removeStockChangeListener(this);
223            }
224    
225            if (m_csModel.getCatalog(m_dbBasket)instanceof ListenableCatalog) {
226                ((ListenableCatalog)m_csModel.getCatalog(m_dbBasket)).removeCatalogChangeListener(this);
227            }
228        }
229    
230        /**
231         * Update the internal model based on the modelled {@link CountingStock}.
232         *
233         * @override Never
234         */
235        public synchronized void updateModel() {
236    
237            //Use catalog's keys here, as Stock's keys are deleted if an item's count is 0 (this would cause empty
238            //items not to be shown, even if fShowZeros is true)
239            List<String> lKeys = new LinkedList<String>(m_csModel.getCatalog(m_dbBasket).keySet(m_dbBasket));
240            Collections.sort(lKeys, new Comparator<String>() {
241                public int compare(String s1, String s2) {
242                    try {
243                        return m_cmpComparator.compare(m_csModel.getCatalog(null).get(s1, m_dbBasket, false),
244                                m_csModel.getCatalog(null).get(s2, m_dbBasket, false));
245                    }
246                    catch (VetoException ve) {
247                        System.err.println(ve);
248                        return 0;
249                    }
250                }
251            });
252            if (!m_fShowZeros) {
253                for (Iterator i = lKeys.iterator(); i.hasNext(); ) {
254                    String sKey = (String)i.next();
255    
256                    if (m_csModel.countItems(sKey, m_dbBasket) == 0) {
257                        i.remove();
258                    }
259                }
260            }
261    
262            m_lKeys = lKeys;
263        }
264    
265        // StockChangeListener interface methods
266    
267        /**
268         * Update the internal model and inform any listeners according to the received event.
269         *
270         * <p>This method is public as an implementation detail and must not be called directly.</p>
271         *
272         * @override Never
273         */
274        public void addedStockItems(StockChangeEvent e) {
275            if ((e.getBasket() == null) || (e.getBasket() == m_dbBasket)) {
276                checkUpdate(e.getAffectedKey());
277            }
278        }
279    
280        /**
281         * Update the internal model and inform any listeners according to the received event.
282         *
283         * <p>This method is public as an implementation detail and must not be called directly.</p>
284         *
285         * @override Never
286         */
287        public void commitAddStockItems(StockChangeEvent e) {
288            checkUpdate(e.getAffectedKey());
289        }
290    
291        /**
292         * Update the internal model and inform any listeners according to the received event.
293         *
294         * <p>This method is public as an implementation detail and must not be called directly.</p>
295         *
296         * @override Never
297         */
298        public void rollbackAddStockItems(StockChangeEvent e) {
299            if (e.getBasket() == m_dbBasket) {
300                checkUpdate(e.getAffectedKey());
301            }
302        }
303    
304        /**
305         * Update the internal model and inform any listeners according to the received event.
306         *
307         * <p>This method is public as an implementation detail and must not be called directly.</p>
308         *
309         * @override Never
310         */
311        public void canRemoveStockItems(StockChangeEvent e) throws VetoException {}
312    
313        /**
314         * Update the internal model and inform any listeners according to the received event.
315         *
316         * <p>This method is public as an implementation detail and must not be called directly.</p>
317         *
318         * @override Never
319         */
320        public void noRemoveStockItems(StockChangeEvent e) {}
321    
322        /**
323         * Update the internal model and inform any listeners according to the received event.
324         *
325         * <p>This method is public as an implementation detail and must not be called directly.</p>
326         *
327         * @override Never
328         */
329        public void removedStockItems(StockChangeEvent e) {
330            checkUpdate(e.getAffectedKey());
331        }
332    
333        /**
334         * Update the internal model and inform any listeners according to the received event.
335         *
336         * <p>This method is public as an implementation detail and must not be called directly.</p>
337         *
338         * @override Never
339         */
340        public void commitRemoveStockItems(StockChangeEvent e) {}
341    
342        /**
343         * Update the internal model and inform any listeners according to the received event.
344         *
345         * <p>This method is public as an implementation detail and must not be called directly.</p>
346         *
347         * @override Never
348         */
349        public void rollbackRemoveStockItems(StockChangeEvent e) {
350            checkUpdate(e.getAffectedKey());
351        }
352    
353        /**
354         * Update the internal model and inform any listeners according to the received event.
355         *
356         * <p>This method is public as an implementation detail and must not be called directly.</p>
357         *
358         * @override Never
359         */
360        public void canEditStockItems(StockChangeEvent e) throws VetoException {}
361    
362        /**
363         * Update the internal model and inform any listeners according to the received event.
364         *
365         * <p>This method is public as an implementation detail and must not be called directly.</p>
366         *
367         * @override Never
368         */
369        public void noEditStockItems(StockChangeEvent e) {}
370    
371        /**
372         * Update the internal model and inform any listeners according to the received event.
373         *
374         * <p>This method is public as an implementation detail and must not be called directly.</p>
375         *
376         * @override Never
377         */
378        public void editingStockItems(StockChangeEvent e) {
379            // never fired, we talk about CountingStocks!
380        }
381    
382        /**
383         * Update the internal model and inform any listeners according to the received event.
384         *
385         * <p>This method is public as an implementation detail and must not be called directly.</p>
386         *
387         * @override Never
388         */
389        public void commitEditStockItems(StockChangeEvent e) {
390            // never fired!
391        }
392    
393        /**
394         * Update the internal model and inform any listeners according to the received event.
395         *
396         * <p>This method is public as an implementation detail and must not be called directly.</p>
397         *
398         * @override Never
399         */
400        public void rollbackEditStockItems(StockChangeEvent e) {
401            // never fired!
402        }
403    
404        // CatalogChangeListener interface methods
405    
406        /**
407         * Update the internal model and inform any listeners according to the received event.
408         *
409         * <p>This method is public as an implementation detail and must not be called directly.</p>
410         *
411         * @override Never
412         */
413        public void addedCatalogItem(CatalogChangeEvent e) {
414            if ((e.getBasket() == null) || (e.getBasket() == m_dbBasket)) {
415                checkAdd(e.getAffectedItem().getName());
416            }
417        }
418    
419        /**
420         * Update the internal model and inform any listeners according to the received event.
421         *
422         * <p>This method is public as an implementation detail and must not be called directly.</p>
423         *
424         * @override Never
425         */
426        public void commitedAddCatalogItem(CatalogChangeEvent e) {
427            if (e.getBasket() != m_dbBasket) {
428                checkAdd(e.getAffectedItem().getName());
429            }
430        }
431    
432        /**
433         * Update the internal model and inform any listeners according to the received event.
434         *
435         * <p>This method is public as an implementation detail and must not be called directly.</p>
436         *
437         * @override Never
438         */
439        public void rolledbackAddCatalogItem(CatalogChangeEvent e) {
440            if (e.getBasket() == m_dbBasket) {
441                checkRemove(e.getAffectedItem().getName());
442            }
443        }
444    
445        /**
446         * Update the internal model and inform any listeners according to the received event.
447         *
448         * <p>This method is public as an implementation detail and must not be called directly.</p>
449         *
450         * @override Never
451         */
452        public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException {}
453    
454        /**
455         * Update the internal model and inform any listeners according to the received event.
456         *
457         * <p>This method is public as an implementation detail and must not be called directly.</p>
458         *
459         * @override Never
460         */
461        public void noRemoveCatalogItem(CatalogChangeEvent e) {}
462    
463        /**
464         * Update the internal model and inform any listeners according to the received event.
465         *
466         * <p>This method is public as an implementation detail and must not be called directly.</p>
467         *
468         * @override Never
469         */
470        public void removedCatalogItem(CatalogChangeEvent e) {
471            checkRemove(e.getAffectedItem().getName());
472        }
473    
474        /**
475         * Update the internal model and inform any listeners according to the received event.
476         *
477         * <p>This method is public as an implementation detail and must not be called directly.</p>
478         *
479         * @override Never
480         */
481        public void commitedRemoveCatalogItem(CatalogChangeEvent e) {}
482    
483        /**
484         * Update the internal model and inform any listeners according to the received event.
485         *
486         * <p>This method is public as an implementation detail and must not be called directly.</p>
487         *
488         * @override Never
489         */
490        public void rolledbackRemoveCatalogItem(CatalogChangeEvent e) {
491            checkAdd(e.getAffectedItem().getName());
492        }
493    
494        /**
495         * Update the internal model and inform any listeners according to the received event.
496         *
497         * <p>This method is public as an implementation detail and must not be called directly.</p>
498         *
499         * @override Never
500         */
501        public void canEditCatalogItem(CatalogChangeEvent e) throws VetoException {}
502    
503        /**
504         * Update the internal model and inform any listeners according to the received event.
505         *
506         * <p>This method is public as an implementation detail and must not be called directly.</p>
507         *
508         * @override Never
509         */
510        public void noEditCatalogItem(CatalogChangeEvent e) {}
511    
512        /**
513         * Update the internal model and inform any listeners according to the received event.
514         *
515         * <p>This method is public as an implementation detail and must not be called directly.</p>
516         *
517         * @override Never
518         */
519        public void editingCatalogItem(CatalogChangeEvent e) {
520            if (e.getBasket() != m_dbBasket) {
521                checkRemove(e.getAffectedItem().getName());
522            } else {
523                e.getAffectedItem().addPropertyChangeListener(this);
524            }
525        }
526    
527        /**
528         * Update the internal model and inform any listeners according to the received event.
529         *
530         * <p>This method is public as an implementation detail and must not be called directly.</p>
531         *
532         * @override Never
533         */
534        public void commitEditCatalogItem(CatalogChangeEvent e) {
535            if (e.getBasket() != m_dbBasket) {
536                checkAdd(e.getAffectedItem().getName());
537            } else {
538                e.getAffectedItem().removePropertyChangeListener(this);
539    
540                updateModel();
541                fireTableDataChanged();
542            }
543        }
544    
545        /**
546         * Update the internal model and inform any listeners according to the received event.
547         *
548         * <p>This method is public as an implementation detail and must not be called directly.</p>
549         *
550         * @override Never
551         */
552        public void rollbackEditCatalogItem(CatalogChangeEvent e) {
553            if (e.getBasket() != m_dbBasket) {
554                checkAdd(e.getAffectedItem().getName());
555            } else {
556                e.getAffectedItem().removePropertyChangeListener(this);
557    
558                updateModel();
559                fireTableDataChanged();
560            }
561        }
562    
563        /**
564         * Update the internal model and inform any listeners according to the received event.
565         *
566         * <p>This method is public as an implementation detail and must not be called directly.</p>
567         *
568         * @override Never
569         */
570        public void propertyChange(PropertyChangeEvent e) {
571            if (e.getSource()instanceof CatalogItem) {
572                checkUpdate(((CatalogItem)e.getSource()).getName());
573            }
574        }
575    
576        /**
577         * Internal helper method. Check where, if at all, the given CatalogItem has been added with respect to the
578         * internal model.
579         *
580         * @param ci the added CatalogItem
581         *
582         * @override Never
583         */
584        protected synchronized void checkAdd(String sKey) {
585            updateModel();
586    
587            int nIdx = m_lKeys.indexOf(sKey);
588    
589            if (nIdx > -1) {
590                fireTableRowsInserted(nIdx, nIdx);
591            }
592        }
593    
594        /**
595         * Internal helper method. Check from where, if at all, the given CatalogItem has been removed with respect
596         * to the internal model.
597         *
598         * @param ci the removed CatalogItem
599         *
600         * @override Never
601         */
602        protected synchronized void checkRemove(String sKey) {
603            int nIdx = m_lKeys.indexOf(sKey);
604    
605            updateModel();
606    
607            if (nIdx > -1) {
608                fireTableRowsDeleted(nIdx, nIdx);
609            }
610        }
611    
612        /**
613         * Internal helper method. Check for updates in the given CatalogItem.
614         *
615         * @param ci the updated CatalogItem
616         *
617         * @override Never
618         */
619        protected synchronized void checkUpdate(String sKey) {
620            int nIdx1 = m_lKeys.indexOf(sKey);
621    
622            updateModel();
623    
624            int nIdx2 = m_lKeys.indexOf(sKey);
625    
626            if (nIdx1 == -1) {
627                if (nIdx2 > -1) {
628                    fireTableRowsInserted(nIdx2, nIdx2);
629                } else {
630                    return;
631                }
632            } else {
633                if (nIdx2 > -1) {
634                    if (nIdx1 > nIdx2) {
635                        int nTemp = nIdx2;
636                        nIdx2 = nIdx1;
637                        nIdx1 = nTemp;
638                    }
639    
640                    fireTableRowsUpdated(nIdx1, nIdx2);
641                } else {
642                    fireTableRowsDeleted(nIdx1, nIdx1);
643                }
644            }
645        }
646    }
647    /**
648     * Changes:
649     *
650     * 2003/02/27 by ab023578
651     * Changed updateModel(), comparator now returns stockitem, not only stockitem's id
652     */