001    package data.swing;
002    
003    import data.*;
004    import data.events.*;
005    
006    import util.*;
007    import util.swing.*;
008    
009    import java.util.*;
010    import java.io.*;
011    
012    /**
013     * A {@link javax.swing.table.TableModel} that models the contents of a {@link Stock}, representing each
014     * {@link StockItem} as an individual record.
015     *
016     * @author Steffen Zschaler
017     * @version 2.0 23/08/1999
018     * @since v2.0
019     */
020    public class StoringStockTableModel extends util.swing.AbstractTableModel implements HelpableListener,
021            StockChangeListener, Serializable {
022    
023        /**
024             * ID for serialization.
025             */
026            private static final long serialVersionUID = -4928442983995832424L;
027    
028            /**
029         * The Stock that is being modelled.
030         *
031         * @serial
032         */
033        protected Stock m_stModel;
034    
035        /**
036         * The DataBasket used to determine visibility.
037         *
038         * @serial
039         */
040        protected DataBasket m_dbBasket;
041    
042        /**
043         * The Comparator that defines the sorting order of records in the model. It compares
044         * {@link StockItem StockItems}.
045         *
046         * @serial
047         */
048        protected Comparator<StockItem> m_cmpComparator = new NaturalComparator<StockItem>();
049    
050        /**
051         * The internal model. A list of StockItems.
052         *
053         * @serial
054         */
055        protected List<StockItem> m_lItems;
056        
057        /**
058         * Set the table's data. Data is {@link data.StoringStock}
059         */
060            public void setData(Object n_stModel) {
061                    m_stModel = (StoringStock) n_stModel;
062                    updateModel();
063                    fireTableDataChanged();
064            }
065    
066        /**
067         * Create a new StoringStockTableModel.
068         *
069         * @param st the Stock to be modelled.
070         * @param db the DataBasket to be used to determine visibility.
071         * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered
072         * according to the natural ordering of the StockItems.
073         * @param ted a TableEntryDescriptor that can split individual StockItems into a table's cells.
074         */
075        public StoringStockTableModel(Stock st, DataBasket db, Comparator<StockItem> cmp, TableEntryDescriptor ted) {
076            super(ted);
077            m_stModel = st;
078            m_dbBasket = db;
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 StockItem} to be displayed at the given index. May return <code>null</code> if
094         * there is no record at the indicated position.
095         *
096         * @override Never
097         */
098        public Object getRecord(int row) {
099            ((ListenerHelper)listenerList).needModelUpdate();
100    
101            if ((row > -1) && (row < m_lItems.size())) {
102                return m_lItems.get(row);
103            } else {
104                return null;
105            }
106        }
107    
108        /**
109         * Get the number of records in this model.
110         *
111         * @override Never
112         */
113        public int getRowCount() {
114            ((ListenerHelper)listenerList).needModelUpdate();
115    
116            return m_lItems.size();
117        }
118    
119        // HelpableListener interface methods
120    
121        /**
122         * Subscribe as a listener to the model. If the modelled {@link Stock} is a {@link ListenableStock},
123         * subscribe as a listener.
124         *
125         * @override Never
126         */
127        public void subscribe() {
128            if (m_stModel instanceof ListenableStock) {
129                ((ListenableStock)m_stModel).addStockChangeListener(this);
130            }
131        }
132    
133        /**
134         * Un-Subscribe as a listener from the model. If the modelled {@link Stock} is a {@link ListenableStock},
135         * un-subscribe as a listener.
136         *
137         * @override Never
138         */
139        public void unsubscribe() {
140            if (m_stModel instanceof ListenableStock) {
141                ((ListenableStock)m_stModel).removeStockChangeListener(this);
142            }
143        }
144    
145        /**
146         * Update the internal model based on the modelled {@link Stock}.
147         *
148         * @override Never
149         */
150        public void updateModel() {
151            List<StockItem> lItems = new LinkedList<StockItem>();
152    
153            for (Iterator<StockItem> i = m_stModel.iterator(m_dbBasket, false); i.hasNext(); ) {
154                lItems.add(i.next());
155            }
156    
157            Collections.sort(lItems, m_cmpComparator);
158    
159            m_lItems = lItems;
160        }
161    
162        // StockChangeListener interface methods
163    
164        /**
165         * Update the internal model and inform any listeners according to the received event.
166         *
167         * <p>This method is public as an implementation detail and must not be called directly.</p>
168         *
169         * @override Never
170         */
171        public synchronized void addedStockItems(StockChangeEvent e) {
172            if (e.getBasket() == m_dbBasket) {
173                checkAdd(e);
174            }
175        }
176    
177        /**
178         * Update the internal model and inform any listeners according to the received event.
179         *
180         * <p>This method is public as an implementation detail and must not be called directly.</p>
181         *
182         * @override Never
183         */
184        public void commitAddStockItems(StockChangeEvent e) {
185            if (e.getBasket() != m_dbBasket) {
186                checkAdd(e);
187            }
188        }
189    
190        /**
191         * Update the internal model and inform any listeners according to the received event.
192         *
193         * <p>This method is public as an implementation detail and must not be called directly.</p>
194         *
195         * @override Never
196         */
197        public void rollbackAddStockItems(StockChangeEvent e) {
198            if (e.getBasket() == m_dbBasket) {
199                checkRemove(e);
200            }
201        }
202    
203        /**
204         * Update the internal model and inform any listeners according to the received event.
205         *
206         * <p>This method is public as an implementation detail and must not be called directly.</p>
207         *
208         * @override Never
209         */
210        public void canRemoveStockItems(StockChangeEvent e) throws VetoException {}
211    
212        /**
213         * Update the internal model and inform any listeners according to the received event.
214         *
215         * <p>This method is public as an implementation detail and must not be called directly.</p>
216         *
217         * @override Never
218         */
219        public void noRemoveStockItems(StockChangeEvent e) {}
220    
221        /**
222         * Update the internal model and inform any listeners according to the received event.
223         *
224         * <p>This method is public as an implementation detail and must not be called directly.</p>
225         *
226         * @override Never
227         */
228        public void removedStockItems(StockChangeEvent e) {
229            checkRemove(e);
230        }
231    
232        /**
233         * Update the internal model and inform any listeners according to the received event.
234         *
235         * <p>This method is public as an implementation detail and must not be called directly.</p>
236         *
237         * @override Never
238         */
239        public void commitRemoveStockItems(StockChangeEvent e) {}
240    
241        /**
242         * Update the internal model and inform any listeners according to the received event.
243         *
244         * <p>This method is public as an implementation detail and must not be called directly.</p>
245         *
246         * @override Never
247         */
248        public void rollbackRemoveStockItems(StockChangeEvent e) {
249            checkAdd(e);
250        }
251    
252        /**
253         * Update the internal model and inform any listeners according to the received event.
254         *
255         * <p>This method is public as an implementation detail and must not be called directly.</p>
256         *
257         * @override Never
258         */
259        public void canEditStockItems(StockChangeEvent e) throws VetoException {}
260    
261        /**
262         * Update the internal model and inform any listeners according to the received event.
263         *
264         * <p>This method is public as an implementation detail and must not be called directly.</p>
265         *
266         * @override Never
267         */
268        public void noEditStockItems(StockChangeEvent e) {}
269    
270        /**
271         * Update the internal model and inform any listeners according to the received event.
272         *
273         * <p>This method is public as an implementation detail and must not be called directly.</p>
274         *
275         * @override Never
276         */
277        public void editingStockItems(StockChangeEvent e) {
278            if (e.getBasket() != m_dbBasket) {
279                checkRemove(e);
280            }
281        }
282    
283        /**
284         * Update the internal model and inform any listeners according to the received event.
285         *
286         * <p>This method is public as an implementation detail and must not be called directly.</p>
287         *
288         * @override Never
289         */
290        public void commitEditStockItems(StockChangeEvent e) {
291            if (e.getBasket() != m_dbBasket) {
292                checkAdd(e);
293            } else {
294                checkUpdate(e);
295            }
296        }
297    
298        /**
299         * Update the internal model and inform any listeners according to the received event.
300         *
301         * <p>This method is public as an implementation detail and must not be called directly.</p>
302         *
303         * @override Never
304         */
305        public void rollbackEditStockItems(StockChangeEvent e) {
306            if (e.getBasket() != m_dbBasket) {
307                checkAdd(e);
308            } else {
309                checkUpdate(e);
310            }
311        }
312    
313        /**
314         * Internal helper method. Check where, if at all, the indicated StockItems have been added with respect to
315         * the internal model.
316         *
317         * @override Never
318         */
319        protected void checkAdd(StockChangeEvent e) {
320            updateModel();
321            if (m_stModel instanceof CountingStock) {
322                fireTableDataChanged(); // for CountingStocks, we cannot clearly identify the rows that changed!
323            } else {
324                int nIdx1 = Integer.MAX_VALUE;
325                int nIdx2 = -1;
326    
327                for (Iterator<StockItem> i = e.getAffectedItems(); i.hasNext(); ) {
328                    int nIdx = m_lItems.indexOf(i.next());
329    
330                    if (nIdx < nIdx1) {
331                        nIdx1 = nIdx;
332                    }
333    
334                    if (nIdx > nIdx2) {
335                        nIdx2 = nIdx;
336                    }
337                }
338    
339                if (nIdx2 > -1) {
340                    fireTableRowsInserted(nIdx1, nIdx2);
341                }
342            }
343        }
344    
345        /**
346         * Internal helper method. Check from where, if at all, the indicated StockItems have been removed with
347         * respect to the internal model.
348         *
349         * @override Never
350         */
351        protected void checkRemove(StockChangeEvent e) {
352            int nIdx1 = Integer.MAX_VALUE;
353            int nIdx2 = -1;
354    
355            if (!(m_stModel instanceof CountingStock)) {
356                for (Iterator<StockItem> i = e.getAffectedItems(); i.hasNext(); ) {
357                    int nIdx = m_lItems.indexOf(i.next());
358    
359                    if (nIdx < nIdx1) {
360                        nIdx1 = nIdx;
361                    }
362    
363                    if (nIdx > nIdx2) {
364                        nIdx2 = nIdx;
365                    }
366                }
367            }
368    
369            updateModel();
370    
371            if (m_stModel instanceof CountingStock) {
372                fireTableDataChanged(); // for CountingStocks, we cannot clearly identify the rows that changed!
373            } else {
374                if (nIdx2 > -1) {
375                    fireTableRowsDeleted(nIdx1, nIdx2);
376                }
377            }
378        }
379    
380        /**
381         * Internal helper method. Check for an update in the indicated StockItems.
382         *
383         * @override Never
384         */
385        protected void checkUpdate(StockChangeEvent e) {
386            checkRemove(e);
387            checkAdd(e);
388        }
389    }