OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 41 | Rev 73 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.view.list;
15
 
16
import static org.openconcerto.sql.view.list.ITableModel.SleepState.AWAKE;
17
import static org.openconcerto.sql.view.list.ITableModel.SleepState.HIBERNATING;
18
import static org.openconcerto.sql.view.list.ITableModel.SleepState.SLEEPING;
19
import org.openconcerto.sql.Log;
20
import org.openconcerto.sql.model.SQLFieldsSet;
21
import org.openconcerto.sql.model.SQLTable;
22
import org.openconcerto.sql.model.graph.Path;
23
import org.openconcerto.sql.users.rights.TableAllRights;
24
import org.openconcerto.sql.users.rights.UserRights;
25
import org.openconcerto.sql.users.rights.UserRightsManager;
26
import org.openconcerto.sql.view.list.search.SearchQueue;
27
import org.openconcerto.sql.view.search.SearchSpec;
28
import org.openconcerto.utils.CollectionMap;
29
import org.openconcerto.utils.TableSorter;
30
 
31
import java.beans.PropertyChangeEvent;
32
import java.beans.PropertyChangeListener;
33
import java.beans.PropertyChangeSupport;
34
import java.util.ArrayList;
35
import java.util.Collection;
36
import java.util.Collections;
37
import java.util.List;
38
import java.util.Timer;
39
import java.util.TimerTask;
40
import java.util.concurrent.FutureTask;
41
import java.util.concurrent.atomic.AtomicInteger;
41 ilm 42
import java.util.logging.Level;
17 ilm 43
 
44
import javax.swing.SwingUtilities;
45
import javax.swing.event.TableModelListener;
46
import javax.swing.table.AbstractTableModel;
47
import javax.swing.table.TableModel;
48
 
49
/**
50
 * A model that takes its values from a ListRequest. The request lines can be searched using
51
 * {@link #search(SearchSpec)}. Like all Swing model, it ought too be manipulated in the EDT. ATTN
52
 * as soon as nobody listens to an instance (using addTableModelListener()) it dies and cannot be
53
 * used again.
54
 *
55
 * @author Sylvain CUAZ
56
 */
57
public class ITableModel extends AbstractTableModel {
58
    public static enum SleepState {
59
        /**
60
         * The model processes events as they arrive.
61
         */
62
        AWAKE,
63
        /**
64
         * The events are queued to be executed when {@link #AWAKE}.
65
         */
66
        SLEEPING,
67
        /**
68
         * The model is sleeping plus its list is emptied to release memory.
69
         */
70
        HIBERNATING
71
    }
72
 
73
    private static Timer autoHibernateTimer = null;
74
    private static boolean defaultEditable = true;
75
 
76
    public static Timer getAutoHibernateTimer() {
77
        if (autoHibernateTimer == null)
78
            autoHibernateTimer = new Timer(ITableModel.class.getSimpleName() + " auto-hibernate timer", true);
79
        return autoHibernateTimer;
80
    }
81
 
82
    public static void setDefaultEditable(boolean defaultEditable) {
83
        ITableModel.defaultEditable = defaultEditable;
84
    }
85
 
86
    /**
87
     * Return the line of a JTable at the passed index, handling {@link TableSorter}.
88
     *
89
     * @param m the model of a JTable.
90
     * @param row an index in the JTable.
91
     * @return the line at <code>row</code>.
92
     */
93
    public static ListSQLLine getLine(final TableModel m, int row) {
94
        if (m instanceof ITableModel)
95
            return ((ITableModel) m).getRow(row);
96
        else if (m instanceof TableSorter) {
97
            final TableSorter sorter = (TableSorter) m;
98
            return getLine(sorter.getTableModel(), sorter.modelIndex(row));
99
        } else
100
            throw new IllegalArgumentException("neither ITableModel nor TableSorter : " + m);
101
    }
102
 
103
    // comment remplir la table
104
    private final SQLTableModelLinesSource linesSource;
105
    private final List<String> colNames;
106
    private final PropertyChangeListener colListener;
107
    // la liste des lignes
108
    private final List<ListSQLLine> liste;
109
    // si on est en train de maj liste
110
    private boolean updating;
111
    private boolean filledOnce;
112
 
113
    private final PropertyChangeSupport supp;
114
 
115
    private final UpdateQueue updateQ;
116
    private boolean loading;
117
    // sleep state
118
    private SleepState wantedState;
119
    private SleepState actualState;
21 ilm 120
    private int hibernateDelay;
17 ilm 121
    private TimerTask autoHibernate;
122
    // number of runnables needing our queue to be awake
123
    private final AtomicInteger runSleep;
124
    private final SearchQueue searchQ;
125
    private boolean searching;
126
    private final MoveQueue moveQ;
127
 
128
    // whether we should allow edition
129
    private boolean editable;
130
    private boolean debug;
131
 
132
    public ITableModel(SQLTableModelSource src) {
133
        this.supp = new PropertyChangeSupport(this);
134
 
135
        this.liste = new ArrayList<ListSQLLine>(100);
136
        this.updating = false;
137
        this.filledOnce = false;
138
 
139
        this.editable = defaultEditable;
140
        this.debug = false;
141
 
142
        // don't use CopyUtils.copy() since this prevent the use of anonymous inner class
143
        this.linesSource = src.createLinesSource(this);
144
        this.colNames = new ArrayList<String>();
145
        this.colListener = new PropertyChangeListener() {
146
            @Override
147
            public void propertyChange(PropertyChangeEvent evt) {
148
                updateColNames();
149
            }
150
        };
151
        this.getReq().addColumnListener(this.colListener);
152
        updateColNames();
153
 
154
        this.updateQ = new UpdateQueue(this);
155
        this.loading = false;
156
        this.updateQ.addPropertyChangeListener(new PropertyChangeListener() {
157
            @Override
158
            public void propertyChange(final PropertyChangeEvent evt) {
159
                if (evt.getPropertyName().equals("beingRun")) {
160
                    final boolean isLoading = UpdateQueue.isUpdate((FutureTask<?>) evt.getNewValue());
161
                    SwingUtilities.invokeLater(new Runnable() {
162
                        @Override
163
                        public void run() {
164
                            setLoading(isLoading);
165
                        }
166
                    });
167
                }
168
            }
169
        });
170
        this.actualState = SleepState.AWAKE;
171
        this.wantedState = this.actualState;
21 ilm 172
        this.setHibernateDelay(30);
17 ilm 173
        this.autoHibernate = null;
174
        this.runSleep = new AtomicInteger(0);
175
        this.searchQ = new SearchQueue(new ListAccess(this));
176
        this.searching = false;
177
        this.searchQ.addPropertyChangeListener(new PropertyChangeListener() {
178
            @Override
179
            public void propertyChange(PropertyChangeEvent evt) {
180
                if (evt.getPropertyName().equals("beingRun")) {
181
                    final boolean isSearching = SearchQueue.isSearch((FutureTask<?>) evt.getNewValue());
182
                    SwingUtilities.invokeLater(new Runnable() {
183
                        @Override
184
                        public void run() {
185
                            setSearching(isSearching);
186
                        }
187
                    });
188
                }
189
            }
190
        });
191
        this.moveQ = new MoveQueue(this);
192
 
193
        this.updateAll();
194
    }
195
 
196
    void print(String s) {
41 ilm 197
        print(s, Level.FINE);
17 ilm 198
    }
199
 
41 ilm 200
    void print(String s, Level l) {
201
        Log.get().log(l, this.getTable() + " " + this.hashCode() + " : " + s);
202
    }
203
 
17 ilm 204
    /**
205
     * The passed runnable will be run in the EDT after all current actions in the queue have
206
     * finished.
207
     *
208
     * @param r the runnable to run in Swing.
209
     */
210
    public void invokeLater(final Runnable r) {
211
        if (r == null)
212
            return;
213
        this.runnableAdded();
214
        this.updateQ.put(new Runnable() {
215
            public void run() {
216
                try {
217
                    getSearchQueue().put(new Runnable() {
218
                        public void run() {
219
                            SwingUtilities.invokeLater(r);
220
                        }
221
                    });
222
                } finally {
223
                    runnableCompleted();
224
                }
225
            }
226
        });
227
    }
228
 
229
    // *** refresh
230
 
231
    final UpdateQueue getUpdateQ() {
232
        return this.updateQ;
233
    }
234
 
235
    /**
236
     * Recharge toutes les lignes depuis la base.
237
     */
238
    public void updateAll() {
239
        this.updateQ.putUpdateAll();
240
    }
241
 
242
    public final void setAlwaysUpdateAll(final boolean b) {
243
        this.getUpdateQ().setAlwaysUpdateAll(b);
244
    }
245
 
246
    // *** change list
247
    // none are synchronized since, they all are called from the EDT
248
 
249
    // liste is sorted
250
    void setList(List<ListSQLLine> liste) {
251
        this.setUpdating(true);
252
        this.liste.clear();
253
        this.liste.addAll(liste);
254
        this.filledOnce = true;
255
        print("liste filled : " + this.liste.size());
256
        this.fireTableDataChanged();
257
        this.setUpdating(false);
258
    }
259
 
260
    void addToList(ListSQLLine modifiedLine) {
261
        this.setUpdating(true);
262
 
263
        this.liste.add(modifiedLine);
264
        Collections.sort(this.liste);
265
        final int index = this.indexFromID(modifiedLine.getID());
266
        this.fireTableRowsInserted(index, index);
267
 
268
        this.setUpdating(false);
269
    }
270
 
271
    // modifiedLine match : it must be displayed
272
    void fullListChanged(ListSQLLine modifiedLine, final Collection<Integer> modifiedFields) {
273
        this.setUpdating(true);
274
 
275
        final int index = this.indexFromID(modifiedLine.getID());
276
        final boolean orderChanged;
277
        if (index >= 0) {
278
            this.liste.set(index, modifiedLine);
279
            final boolean afterPred;
280
            if (index > 0)
281
                afterPred = modifiedLine.compareTo(this.liste.get(index - 1)) > 0;
282
            else
283
                afterPred = true;
284
            final boolean beforeSucc;
285
            if (index < this.liste.size() - 1)
286
                beforeSucc = modifiedLine.compareTo(this.liste.get(index + 1)) < 0;
287
            else
288
                beforeSucc = true;
289
            orderChanged = !(afterPred && beforeSucc);
290
        } else {
291
            this.liste.add(modifiedLine);
292
            orderChanged = true;
293
        }
294
        if (orderChanged) {
295
            Collections.sort(this.liste);
296
            this.fireTableDataChanged();
297
        } else {
298
            if (modifiedFields == null)
299
                this.fireTableRowsUpdated(index, index);
300
            else
301
                for (final Integer i : modifiedFields) {
302
                    // only fire for currently displaying cells
303
                    if (i < this.getColumnCount())
304
                        this.fireTableCellUpdated(index, i);
305
                }
306
        }
307
 
308
        this.setUpdating(false);
309
    }
310
 
311
    void removeFromList(int id) {
312
        this.setUpdating(true);
313
 
314
        final int index = this.indexFromID(id);
315
        // si la ligne n'existe pas, rien à faire
316
        if (index >= 0) {
317
            this.liste.remove(index);
318
            this.fireTableRowsDeleted(index, index);
319
        }
320
 
321
        this.setUpdating(false);
322
    }
323
 
324
    // *** tableModel
325
 
326
    protected void updateColNames() {
61 ilm 327
        this.setUpdating(true);
328
 
17 ilm 329
        // getColumnNames() used to take more than 20% of SearchRunnable.matchFilter(), so cache it.
330
        this.colNames.clear();
331
        for (final SQLTableModelColumn col : getCols())
332
            this.colNames.add(this.isDebug() ? col.getName() + " " + col.getPaths().toString() : col.getName());
333
        this.fireTableStructureChanged();
61 ilm 334
 
335
        this.setUpdating(false);
17 ilm 336
    }
337
 
338
    public List<String> getColumnNames() {
339
        return this.colNames;
340
    }
341
 
342
    private List<? extends SQLTableModelColumn> getCols() {
343
        return this.isDebug() ? this.getReq().getAllColumns() : this.getReq().getColumns();
344
    }
345
 
346
    public int getRowCount() {
347
        return this.liste.size();
348
    }
349
 
350
    /**
351
     * The total number of lines fetched. Equals to {@link #getRowCount()} if there's no search.
352
     *
353
     * @return the total number of lines, or 0 if the first fill hasn't completed.
354
     */
355
    public int getTotalRowCount() {
356
        return this.getSearchQueue().getFullListSize();
357
    }
358
 
359
    // pas besoin de synch les méthode ne se servant que des colonnes, elles ne changent pas
360
 
361
    public int getColumnCount() {
362
        return this.getColumnNames().size();
363
    }
364
 
365
    public String getColumnName(int columnIndex) {
366
        // handle null names (as opposed to .toString())
367
        return String.valueOf(this.getColumnNames().get(columnIndex));
368
    }
369
 
370
    public Class<?> getColumnClass(int columnIndex) {
371
        return this.getReq().getColumn(columnIndex).getValueClass();
372
    }
373
 
374
    public void setEditable(boolean b) {
375
        this.editable = b;
376
    }
377
 
378
    public boolean isCellEditable(int rowIndex, int columnIndex) {
379
        if (!this.editable)
380
            return false;
381
        final SQLTableModelColumn col = this.getReq().getColumn(columnIndex);
382
        // hasRight is expensive so put it last
383
        return col.isEditable() && hasRight(col);
384
    }
385
 
386
    private boolean hasRight(final SQLTableModelColumn col) {
387
        if (!UserRightsManager.getInstance().isValid())
388
            return true;
389
        final UserRights u = UserRightsManager.getCurrentUserRights();
390
        for (final SQLTable t : new SQLFieldsSet(col.getFields()).getTables()) {
391
            if (!TableAllRights.hasRight(u, TableAllRights.MODIFY_ROW_TABLE, t))
392
                return false;
393
        }
394
        return true;
395
    }
396
 
397
    public Object getValueAt(int rowIndex, int columnIndex) {
398
        if (rowIndex >= this.getRowCount())
399
            throw new IllegalArgumentException("!!!+ acces a la ligne :" + rowIndex + " et la taille est de:" + this.getRowCount());
400
        return getRow(rowIndex).getList(columnIndex + 1).get(columnIndex);
401
    }
402
 
403
    public final ListSQLLine getRow(int rowIndex) {
404
        return this.liste.get(rowIndex);
405
    }
406
 
407
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
408
        getRow(rowIndex).setValueAt(aValue, columnIndex);
409
    }
410
 
411
    // *** ids
412
 
413
    /**
414
     * Retourne l'ID de la ligne index.
415
     *
416
     * @param index la ligne dont on veut l'ID.
417
     * @return l'ID de la ligne voulue, ou -1 si index n'est pas valide.
418
     */
419
    public int idFromIndex(int index) {
420
        if (index >= 0 && this.liste.size() > index)
421
            return getRow(index).getID();
422
        else
423
            return -1;
424
    }
425
 
426
    /**
427
     * Retourne l'index de la ligne d'ID voulue.
428
     *
429
     * @param id l'id recherché.
430
     * @return l'index de la ligne correspondante, ou -1 si non trouvé.
431
     */
432
    public int indexFromID(int id) {
433
        return ListSQLLine.indexFromID(this.liste, id);
434
    }
435
 
436
    /**
437
     * The lines affected by the passed row.
438
     *
439
     * @param t the table.
440
     * @param id an ID in <code>t</code>.
441
     * @return all affected lines in the fullList (un-searched).
442
     */
443
    public CollectionMap<ListSQLLine, Path> getAffectedLines(final SQLTable t, final int id) {
444
        return this.getSearchQueue().getAffectedLines(t, id);
445
    }
446
 
447
    // *** search
448
 
449
    final SearchQueue getSearchQueue() {
450
        return this.searchQ;
451
    }
452
 
453
    /**
454
     * Effectue une recherche.
455
     *
456
     * @param list description de la recherche à effectuer.
457
     * @param r sera exécuté dans la queue de recherche une fois <code>list</code> recherchée.
458
     */
459
    public synchronized void search(SearchSpec list, Runnable r) {
460
        this.getSearchQueue().setSearch(list);
461
        if (r != null)
462
            this.getSearchQueue().put(r);
463
    }
464
 
465
    public void search(SearchSpec list) {
466
        this.search(list, null);
467
    }
468
 
469
    // *** move
470
 
471
    public void moveBy(final int rowID, final int inc) {
472
        this.moveQ.move(rowID, inc);
473
    }
474
 
475
    /**
476
     * Compute the id of the row which is <code>inc</code> lines from rowID.
477
     *
478
     * @param rowID an ID of a row of this table.
479
     * @param inc the offset of visible lines.
480
     * @return the destination ID or <code>null</code> if it's the same as <code>rowID</code> or
481
     *         <code>rowID</code> is inexistant.
482
     */
483
    Integer getDestID(int rowID, int inc) {
484
        final int rowIndex = this.indexFromID(rowID);
485
        if (rowIndex < 0)
486
            return null;
487
        int destIndex = rowIndex + inc;
488
        final int min = 0;
489
        final int max = this.getRowCount() - 1;
490
        if (destIndex < min)
491
            destIndex = min;
492
        else if (destIndex > max)
493
            destIndex = max;
494
        if (destIndex != rowIndex) {
495
            return this.idFromIndex(destIndex);
496
        } else
497
            return null;
498
    }
499
 
500
    // *** boolean
501
 
502
    /**
503
     * Whether this model has been filled at least once. Allow to differentiate between request has
504
     * not yet executed and request returned no rows.
505
     *
506
     * @return <code>true</code> if the rows reflect the database.
507
     */
508
    public final boolean filledOnce() {
509
        return this.filledOnce;
510
    }
511
 
512
    public synchronized final boolean isUpdating() {
513
        return this.updating;
514
    }
515
 
61 ilm 516
    // signify that the program is making a change not the user
517
    // i.e. should be called before and after every fireTable*()
17 ilm 518
    private synchronized void setUpdating(boolean searching) {
519
        final boolean old = this.updating;
520
        if (old != searching) {
521
            this.updating = searching;
522
            this.supp.firePropertyChange("updating", old, this.updating);
523
        }
524
    }
525
 
526
    private void setLoading(boolean isLoading) {
527
        // keep the value in an attribute since we are invoked later in EDT and by that time
528
        // the updateQueue might be doing something else and isLoading() could never return true
529
        final boolean old = this.loading;
530
        if (old != isLoading) {
531
            this.loading = isLoading;
532
            this.supp.firePropertyChange("loading", old, this.loading);
533
        }
534
    }
535
 
536
    public final boolean isLoading() {
537
        return this.loading;
538
    }
539
 
540
    private void setSearching(boolean searching) {
541
        final boolean old = this.searching;
542
        if (old != searching) {
543
            this.searching = searching;
544
            this.supp.firePropertyChange("searching", old, this.searching);
545
        }
546
    }
547
 
548
    public final boolean isSearching() {
549
        return this.searching;
550
    }
551
 
552
    // when the model is sleeping, no more updates are performed
553
    void setSleeping(boolean sleeping) {
554
        this.setSleeping(sleeping ? SleepState.SLEEPING : SleepState.AWAKE);
555
    }
556
 
557
    void setSleeping(SleepState state) {
41 ilm 558
        synchronized (this.runSleep) {
559
            this.wantedState = state;
560
            this.sleepUpdated();
17 ilm 561
        }
562
    }
563
 
21 ilm 564
    /**
565
     * Set the number of seconds between reaching the {@link SleepState#SLEEPING} state and setting
566
     * the {@link SleepState#HIBERNATING} state.
567
     *
568
     * @param seconds the number of seconds, less than 0 to disable automatic hibernating.
569
     */
570
    public final void setHibernateDelay(int seconds) {
571
        this.hibernateDelay = seconds;
572
    }
573
 
17 ilm 574
    private void runnableAdded() {
575
        synchronized (this.runSleep) {
576
            this.runSleep.incrementAndGet();
577
            this.sleepUpdated();
578
        }
579
    }
580
 
581
    protected void runnableCompleted() {
582
        synchronized (this.runSleep) {
583
            this.runSleep.decrementAndGet();
584
            this.sleepUpdated();
585
        }
586
    }
587
 
588
    private void sleepUpdated() {
589
        // set to null to do nothing
590
        final SleepState res;
591
        // if there's a user runnable we must wake up
592
        if (this.runSleep.get() > 0)
593
            res = AWAKE;
594
        // else we can go where we want
595
        else if (this.wantedState == this.actualState)
596
            res = null;
597
        else if (this.actualState == AWAKE) {
598
            // no need to test for runSleep
599
            // we cannot go from AWAKE directly to HIBERNATING
600
            res = SleepState.SLEEPING;
601
        } else if (this.actualState == SLEEPING) {
602
            res = this.wantedState;
603
        } else if (this.actualState == HIBERNATING) {
604
            // we cannot go from HIBERNATING to SLEEPING, since we are empty
605
            // besides we are already sleeping
606
            res = this.wantedState == AWAKE ? this.wantedState : null;
607
        } else
608
            throw new IllegalStateException("unknown state: " + this.actualState);
609
 
610
        if (res != null)
611
            this.setActual(res);
612
    }
613
 
614
    private void setActual(SleepState state) {
615
        if (this.actualState != state) {
616
            print("changing state " + this.actualState + " => " + state);
617
            this.actualState = state;
618
 
619
            if (this.autoHibernate != null)
620
                this.autoHibernate.cancel();
621
 
622
            switch (this.actualState) {
623
            case AWAKE:
624
                this.updateQ.setSleeping(false);
625
                break;
626
            case SLEEPING:
627
                this.updateQ.setSleeping(true);
21 ilm 628
                if (this.hibernateDelay >= 0) {
629
                    this.autoHibernate = new TimerTask() {
630
                        @Override
631
                        public void run() {
41 ilm 632
                            try {
633
                                setSleeping(HIBERNATING);
634
                            } catch (Exception e) {
635
                                // never let an exception pass, otherwise the timer thread will die,
636
                                // and the *static* timer will become unusable for everyone
637
                                // OK to ignore setSleeping() since it's merely an optimization
638
                                print("HIBERNATING failed : " + e.getMessage(), Level.WARNING);
639
                                e.printStackTrace();
640
                            }
21 ilm 641
                        }
642
                    };
643
                    getAutoHibernateTimer().schedule(this.autoHibernate, this.hibernateDelay * 1000);
644
                }
17 ilm 645
                break;
646
            case HIBERNATING:
647
                this.updateQ.putRemoveAll();
648
                break;
649
            }
650
 
651
            this.sleepUpdated();
652
        }
653
    }
654
 
655
    public final SQLTableModelLinesSource getLinesSource() {
656
        return this.linesSource;
657
    }
658
 
659
    public final SQLTableModelSource getReq() {
660
        return this.linesSource.getParent();
661
    }
662
 
663
    public final SQLTable getTable() {
664
        return this.getReq().getPrimaryTable();
665
    }
666
 
667
    public void addPropertyChangeListener(PropertyChangeListener l) {
668
        this.supp.addPropertyChangeListener(l);
669
    }
670
 
671
    public void addPropertyChangeListener(final String propName, PropertyChangeListener l) {
672
        this.supp.addPropertyChangeListener(propName, l);
673
    }
674
 
675
    public void rmPropertyChangeListener(PropertyChangeListener l) {
676
        this.supp.removePropertyChangeListener(l);
677
    }
678
 
679
    public void rmPropertyChangeListener(final String propName, PropertyChangeListener l) {
680
        this.supp.removePropertyChangeListener(propName, l);
681
    }
682
 
683
    public final boolean isDebug() {
684
        return this.debug;
685
    }
686
 
687
    /**
688
     * Set the debug mode : add keys to the normal columns and use the field names for the column
689
     * names.
690
     *
691
     * @param debug <code>true</code> to enable the debug mode.
692
     */
693
    public final void setDebug(boolean debug) {
694
        this.debug = debug;
695
        this.updateColNames();
696
    }
697
 
698
    public String toString() {
699
        return this.getClass().getSimpleName() + "@" + this.hashCode() + " for " + this.getTable();
700
    }
701
 
702
    public synchronized void addTableModelListener(TableModelListener l) {
703
        if (this.isDead())
704
            throw new IllegalStateException("dead tableModel: " + this);
705
        super.addTableModelListener(l);
706
    }
707
 
708
    public synchronized void removeTableModelListener(TableModelListener l) {
709
        super.removeTableModelListener(l);
710
        // nobody listens to us so we die
711
        if (this.listenerList.getListenerCount() == 0) {
712
            print("dying");
41 ilm 713
            if (this.autoHibernate != null)
714
                this.autoHibernate.cancel();
17 ilm 715
            this.updateQ.die();
716
            this.getSearchQueue().die();
717
            this.moveQ.die();
718
            this.getLinesSource().die();
719
            this.getReq().rmColumnListener(this.colListener);
720
        }
721
    }
722
 
723
    private synchronized final boolean isDead() {
724
        // in ctor queue can be null, but we're obviously not dead
725
        return this.updateQ != null && this.updateQ.isDead();
726
    }
727
 
728
}