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 CatalogItem {
112    
113            /**
114                     * ID for Serialization.
115                     */
116                    private static final long serialVersionUID = 3038096727911390815L;
117    
118                    /**
119             * The CatalogItem part of the record.
120             */
121            // Changed 11/09/2000-STEFFEN to private to fix F5.
122            private CatalogItem m_ciDescriptor;
123    
124            /**
125             * The number of actually available items.
126             */
127            // Changed 11/09/2000-STEFFEN to private to fix F5.
128            private int m_nCount;
129    
130            /**
131             * Create a new Record.
132             */
133            public Record(CatalogItem ci, int nCount) {
134                super();
135    
136                m_ciDescriptor = ci;
137                m_nCount = nCount;
138            }
139    
140            /**
141             * Compare by descriptor.
142             */
143            public int compareTo(Record r) {
144                return m_ciDescriptor.compareTo(r.getDescriptor());
145            }
146    
147            /**
148             * Get the CatalogItem describing the items represented by this record.
149             */
150            public CatalogItem getDescriptor() {
151                return m_ciDescriptor;
152            }
153    
154            /**
155             * Get the number of items in this record.
156             */
157            public int getCount() {
158                return m_nCount;
159            }
160    
161                    public void addNameListener(PropertyChangeListener pcl) {
162                            m_ciDescriptor.addNameListener(pcl);
163                    }
164    
165                    public void addPropertyChangeListener(PropertyChangeListener pcl) {
166                            m_ciDescriptor.addPropertyChangeListener(pcl);
167                    }
168    
169                    public void addValueListener(PropertyChangeListener pcl) {
170                            m_ciDescriptor.addValueListener(pcl);
171                    }
172    
173                    public NameContext attach(NameContext nc) {
174                            return m_ciDescriptor.attach(nc);
175                    }
176    
177                    public int compareTo(Object arg0) {
178                            return m_ciDescriptor.compareTo(arg0);
179                    }
180    
181                    public NameContext detachNC() {
182                            return m_ciDescriptor.detachNC();
183                    }
184    
185                    public Catalog getCatalog() {
186                            return m_ciDescriptor.getCatalog();
187                    }
188    
189                    public String getName() {
190                            return m_ciDescriptor.getName();
191                    }
192    
193                    public Value getValue() {
194                            return m_ciDescriptor.getValue();
195                    }
196    
197                    public void removeNameListener(PropertyChangeListener pcl) {
198                            m_ciDescriptor.removeNameListener(pcl);
199                    }
200    
201                    public void removePropertyChangeListener(PropertyChangeListener pcl) {
202                            m_ciDescriptor.removePropertyChangeListener(pcl);
203                    }
204    
205                    public void removeValueListener(PropertyChangeListener pcl) {
206                            m_ciDescriptor.removeValueListener(pcl);
207                    }
208    
209                    public void setName(String sName, DataBasket db) throws NameContextException {
210                            m_ciDescriptor.setName(sName, db);
211                    }
212        }
213    
214        /**
215         * Get the record at the given index.
216         *
217         * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}).
218         * @return the {@link Record} to be displayed at the given index. May return <code>null</code> if
219         * either there is no record at the indicated position or an exception occurs.
220         *
221         * @override Never
222         */
223        public Object getRecord(int row) {
224            ((ListenerHelper)listenerList).needModelUpdate();
225    
226            try {
227                if ((row > -1) && (row < getRowCount())) {
228                    String sKey = (String)m_lKeys.get(row);
229    
230                    return new Record(m_csModel.getCatalog(m_dbBasket).get(sKey, m_dbBasket, false),
231                            m_csModel.countItems(sKey, m_dbBasket));
232                } else {
233                    return null;
234                }
235            }
236            catch (VetoException ve) {
237                return null;
238            }
239        }
240    
241        /**
242         * Get the number of records in this model.
243         *
244         * @override Never
245         */
246        public int getRowCount() {
247            ((ListenerHelper)listenerList).needModelUpdate();
248    
249            return m_lKeys.size();
250        }
251    
252        // HelpableListener interface methods
253        /**
254         * Subscribe as a listener to the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
255         * subscribe as a listener. If the modelled {@link CountingStock} is a {@link ListenableStock}, subscribe as
256         * a listener.
257         *
258         * @override Never
259         */
260        public void subscribe() {
261            if (m_csModel instanceof ListenableStock) {
262                ((ListenableStock)m_csModel).addStockChangeListener(this);
263            }
264    
265            if (m_csModel.getCatalog(m_dbBasket)instanceof ListenableCatalog) {
266                ((ListenableCatalog)m_csModel.getCatalog(m_dbBasket)).addCatalogChangeListener(this);
267            }
268        }
269    
270        /**
271         * Un-Subscribe as a listener from the model. If the modelled {@link Catalog} is a {@link ListenableCatalog},
272         * un-subscribe as a listener. If the modelled {@link CountingStock} is a {@link ListenableStock},
273         * un-subscribe as a listener.
274         *
275         * @override Never
276         */
277        public void unsubscribe() {
278            if (m_csModel instanceof ListenableStock) {
279                ((ListenableStock)m_csModel).removeStockChangeListener(this);
280            }
281    
282            if (m_csModel.getCatalog(m_dbBasket)instanceof ListenableCatalog) {
283                ((ListenableCatalog)m_csModel.getCatalog(m_dbBasket)).removeCatalogChangeListener(this);
284            }
285        }
286    
287        /**
288         * Update the internal model based on the modelled {@link CountingStock}.
289         *
290         * @override Never
291         */
292        public synchronized void updateModel() {
293    
294            //Use catalog's keys here, as Stock's keys are deleted if an item's count is 0 (this would cause empty
295            //items not to be shown, even if fShowZeros is true)
296            List<String> lKeys = new LinkedList<String>(m_csModel.getCatalog(m_dbBasket).keySet(m_dbBasket));
297            Collections.sort(lKeys, new Comparator<String>() {
298                public int compare(String s1, String s2) {
299                    try {
300                        return m_cmpComparator.compare(m_csModel.getCatalog(null).get(s1, m_dbBasket, false),
301                                m_csModel.getCatalog(null).get(s2, m_dbBasket, false));
302                    }
303                    catch (VetoException ve) {
304                        System.err.println(ve);
305                        return 0;
306                    }
307                }
308            });
309            if (!m_fShowZeros) {
310                for (Iterator i = lKeys.iterator(); i.hasNext(); ) {
311                    String sKey = (String)i.next();
312    
313                    if (m_csModel.countItems(sKey, m_dbBasket) == 0) {
314                        i.remove();
315                    }
316                }
317            }
318    
319            m_lKeys = lKeys;
320        }
321    
322        // StockChangeListener interface methods
323    
324        /**
325         * Update the internal model and inform any listeners according to the received event.
326         *
327         * <p>This method is public as an implementation detail and must not be called directly.</p>
328         *
329         * @override Never
330         */
331        public void addedStockItems(StockChangeEvent e) {
332            if ((e.getBasket() == null) || (e.getBasket() == m_dbBasket)) {
333                checkUpdate(e.getAffectedKey());
334            }
335        }
336    
337        /**
338         * Update the internal model and inform any listeners according to the received event.
339         *
340         * <p>This method is public as an implementation detail and must not be called directly.</p>
341         *
342         * @override Never
343         */
344        public void commitAddStockItems(StockChangeEvent e) {
345            checkUpdate(e.getAffectedKey());
346        }
347    
348        /**
349         * Update the internal model and inform any listeners according to the received event.
350         *
351         * <p>This method is public as an implementation detail and must not be called directly.</p>
352         *
353         * @override Never
354         */
355        public void rollbackAddStockItems(StockChangeEvent e) {
356            if (e.getBasket() == m_dbBasket) {
357                checkUpdate(e.getAffectedKey());
358            }
359        }
360    
361        /**
362         * Update the internal model and inform any listeners according to the received event.
363         *
364         * <p>This method is public as an implementation detail and must not be called directly.</p>
365         *
366         * @override Never
367         */
368        public void canRemoveStockItems(StockChangeEvent e) throws VetoException {}
369    
370        /**
371         * Update the internal model and inform any listeners according to the received event.
372         *
373         * <p>This method is public as an implementation detail and must not be called directly.</p>
374         *
375         * @override Never
376         */
377        public void noRemoveStockItems(StockChangeEvent e) {}
378    
379        /**
380         * Update the internal model and inform any listeners according to the received event.
381         *
382         * <p>This method is public as an implementation detail and must not be called directly.</p>
383         *
384         * @override Never
385         */
386        public void removedStockItems(StockChangeEvent e) {
387            checkUpdate(e.getAffectedKey());
388        }
389    
390        /**
391         * Update the internal model and inform any listeners according to the received event.
392         *
393         * <p>This method is public as an implementation detail and must not be called directly.</p>
394         *
395         * @override Never
396         */
397        public void commitRemoveStockItems(StockChangeEvent e) {}
398    
399        /**
400         * Update the internal model and inform any listeners according to the received event.
401         *
402         * <p>This method is public as an implementation detail and must not be called directly.</p>
403         *
404         * @override Never
405         */
406        public void rollbackRemoveStockItems(StockChangeEvent e) {
407            checkUpdate(e.getAffectedKey());
408        }
409    
410        /**
411         * Update the internal model and inform any listeners according to the received event.
412         *
413         * <p>This method is public as an implementation detail and must not be called directly.</p>
414         *
415         * @override Never
416         */
417        public void canEditStockItems(StockChangeEvent e) throws VetoException {}
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 noEditStockItems(StockChangeEvent e) {}
427    
428        /**
429         * Update the internal model and inform any listeners according to the received event.
430         *
431         * <p>This method is public as an implementation detail and must not be called directly.</p>
432         *
433         * @override Never
434         */
435        public void editingStockItems(StockChangeEvent e) {
436            // never fired, we talk about CountingStocks!
437        }
438    
439        /**
440         * Update the internal model and inform any listeners according to the received event.
441         *
442         * <p>This method is public as an implementation detail and must not be called directly.</p>
443         *
444         * @override Never
445         */
446        public void commitEditStockItems(StockChangeEvent e) {
447            // never fired!
448        }
449    
450        /**
451         * Update the internal model and inform any listeners according to the received event.
452         *
453         * <p>This method is public as an implementation detail and must not be called directly.</p>
454         *
455         * @override Never
456         */
457        public void rollbackEditStockItems(StockChangeEvent e) {
458            // never fired!
459        }
460    
461        // CatalogChangeListener interface methods
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 addedCatalogItem(CatalogChangeEvent e) {
471            if ((e.getBasket() == null) || (e.getBasket() == m_dbBasket)) {
472                checkAdd(e.getAffectedItem().getName());
473            }
474        }
475    
476        /**
477         * Update the internal model and inform any listeners according to the received event.
478         *
479         * <p>This method is public as an implementation detail and must not be called directly.</p>
480         *
481         * @override Never
482         */
483        public void commitedAddCatalogItem(CatalogChangeEvent e) {
484            if (e.getBasket() != m_dbBasket) {
485                checkAdd(e.getAffectedItem().getName());
486            }
487        }
488    
489        /**
490         * Update the internal model and inform any listeners according to the received event.
491         *
492         * <p>This method is public as an implementation detail and must not be called directly.</p>
493         *
494         * @override Never
495         */
496        public void rolledbackAddCatalogItem(CatalogChangeEvent e) {
497            if (e.getBasket() == m_dbBasket) {
498                checkRemove(e.getAffectedItem().getName());
499            }
500        }
501    
502        /**
503         * Update the internal model and inform any listeners according to the received event.
504         *
505         * <p>This method is public as an implementation detail and must not be called directly.</p>
506         *
507         * @override Never
508         */
509        public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException {}
510    
511        /**
512         * Update the internal model and inform any listeners according to the received event.
513         *
514         * <p>This method is public as an implementation detail and must not be called directly.</p>
515         *
516         * @override Never
517         */
518        public void noRemoveCatalogItem(CatalogChangeEvent e) {}
519    
520        /**
521         * Update the internal model and inform any listeners according to the received event.
522         *
523         * <p>This method is public as an implementation detail and must not be called directly.</p>
524         *
525         * @override Never
526         */
527        public void removedCatalogItem(CatalogChangeEvent e) {
528            checkRemove(e.getAffectedItem().getName());
529        }
530    
531        /**
532         * Update the internal model and inform any listeners according to the received event.
533         *
534         * <p>This method is public as an implementation detail and must not be called directly.</p>
535         *
536         * @override Never
537         */
538        public void commitedRemoveCatalogItem(CatalogChangeEvent e) {}
539    
540        /**
541         * Update the internal model and inform any listeners according to the received event.
542         *
543         * <p>This method is public as an implementation detail and must not be called directly.</p>
544         *
545         * @override Never
546         */
547        public void rolledbackRemoveCatalogItem(CatalogChangeEvent e) {
548            checkAdd(e.getAffectedItem().getName());
549        }
550    
551        /**
552         * Update the internal model and inform any listeners according to the received event.
553         *
554         * <p>This method is public as an implementation detail and must not be called directly.</p>
555         *
556         * @override Never
557         */
558        public void canEditCatalogItem(CatalogChangeEvent e) throws VetoException {}
559    
560        /**
561         * Update the internal model and inform any listeners according to the received event.
562         *
563         * <p>This method is public as an implementation detail and must not be called directly.</p>
564         *
565         * @override Never
566         */
567        public void noEditCatalogItem(CatalogChangeEvent e) {}
568    
569        /**
570         * Update the internal model and inform any listeners according to the received event.
571         *
572         * <p>This method is public as an implementation detail and must not be called directly.</p>
573         *
574         * @override Never
575         */
576        public void editingCatalogItem(CatalogChangeEvent e) {
577            if (e.getBasket() != m_dbBasket) {
578                checkRemove(e.getAffectedItem().getName());
579            } else {
580                e.getAffectedItem().addPropertyChangeListener(this);
581            }
582        }
583    
584        /**
585         * Update the internal model and inform any listeners according to the received event.
586         *
587         * <p>This method is public as an implementation detail and must not be called directly.</p>
588         *
589         * @override Never
590         */
591        public void commitEditCatalogItem(CatalogChangeEvent e) {
592            if (e.getBasket() != m_dbBasket) {
593                checkAdd(e.getAffectedItem().getName());
594            } else {
595                e.getAffectedItem().removePropertyChangeListener(this);
596    
597                updateModel();
598                fireTableDataChanged();
599            }
600        }
601    
602        /**
603         * Update the internal model and inform any listeners according to the received event.
604         *
605         * <p>This method is public as an implementation detail and must not be called directly.</p>
606         *
607         * @override Never
608         */
609        public void rollbackEditCatalogItem(CatalogChangeEvent e) {
610            if (e.getBasket() != m_dbBasket) {
611                checkAdd(e.getAffectedItem().getName());
612            } else {
613                e.getAffectedItem().removePropertyChangeListener(this);
614    
615                updateModel();
616                fireTableDataChanged();
617            }
618        }
619    
620        /**
621         * Update the internal model and inform any listeners according to the received event.
622         *
623         * <p>This method is public as an implementation detail and must not be called directly.</p>
624         *
625         * @override Never
626         */
627        public void propertyChange(PropertyChangeEvent e) {
628            if (e.getSource()instanceof CatalogItem) {
629                checkUpdate(((CatalogItem)e.getSource()).getName());
630            }
631        }
632    
633        /**
634         * Internal helper method. Check where, if at all, the given CatalogItem has been added with respect to the
635         * internal model.
636         *
637         * @param sKey the key of the added CatalogItem
638         *
639         * @override Never
640         */
641        protected synchronized void checkAdd(String sKey) {
642            updateModel();
643    
644            int nIdx = m_lKeys.indexOf(sKey);
645    
646            if (nIdx > -1) {
647                fireTableRowsInserted(nIdx, nIdx);
648            }
649        }
650    
651        /**
652         * Internal helper method. Check from where, if at all, the given CatalogItem has been removed with respect
653         * to the internal model.
654         *
655         * @param sKey the key of the removed CatalogItem
656         *
657         * @override Never
658         */
659        protected synchronized void checkRemove(String sKey) {
660            int nIdx = m_lKeys.indexOf(sKey);
661    
662            updateModel();
663    
664            if (nIdx > -1) {
665                fireTableRowsDeleted(nIdx, nIdx);
666            }
667        }
668    
669        /**
670         * Internal helper method. Check for updates in the given CatalogItem.
671         *
672         * @param sKey the key of the updated CatalogItem
673         *
674         * @override Never
675         */
676        protected synchronized void checkUpdate(String sKey) {
677            int nIdx1 = m_lKeys.indexOf(sKey);
678    
679            updateModel();
680    
681            int nIdx2 = m_lKeys.indexOf(sKey);
682    
683            if (nIdx1 == -1) {
684                if (nIdx2 > -1) {
685                    fireTableRowsInserted(nIdx2, nIdx2);
686                } else {
687                    return;
688                }
689            } else {
690                if (nIdx2 > -1) {
691                    if (nIdx1 > nIdx2) {
692                        int nTemp = nIdx2;
693                        nIdx2 = nIdx1;
694                        nIdx1 = nTemp;
695                    }
696    
697                    fireTableRowsUpdated(nIdx1, nIdx2);
698                } else {
699                    fireTableRowsDeleted(nIdx1, nIdx1);
700                }
701            }
702        }
703    }
704    /**
705     * Changes:
706     *
707     * 2003/02/27 by ab023578
708     * Changed updateModel(), comparator now returns stockitem, not only stockitem's id
709     */