1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package info.rolandkrueger.roklib.util.tables.filtertable;
26
27 import info.rolandkrueger.roklib.util.helper.CheckForNull;
28
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
33
34 import javax.swing.SortOrder;
35 import javax.swing.event.TableModelEvent;
36 import javax.swing.event.TableModelListener;
37 import javax.swing.table.AbstractTableModel;
38 import javax.swing.table.TableModel;
39
40 public class SortableFilterableTableDataModel<T, H extends ITableDataColumnHeader> extends
41 AbstractTableModel implements TableModelListener
42 {
43 private static final long serialVersionUID = 2016102798175233234L;
44
45 public enum SearchMode
46 {
47 NONE, PREFIX_SIMILARITY, INFIX, BOTH
48 };
49
50 private List<TableDataRow<T>> mTableRows;
51 private List<TableDataColumn<T, H>> mTableColumns;
52 private List<TableDataRow<T>> mVisibleRows;
53 private int mColumnCount;
54 private List<Comparator<T>> mColumnComparators;
55 private List<Class<?>> mColumnClasses;
56 private int mCurrentAddColumnIndex = 0;
57 private TableDataRow<T> mCurrentRow;
58 private int mSortColumnIndex = - 1;
59 private SortOrder mSortOrder;
60 private final StringComparator mStringComparator = new StringComparator ();
61 private final NumberComparator mnNumberComparator = new NumberComparator ();
62 private TableModel mDataFromTableModel;
63 private List<ISortableFilterableTableListener> mListeners;
64
65 public SortableFilterableTableDataModel (TableModel tableModel, SearchMode searchCapability)
66 {
67 this (tableModel.getColumnCount (), searchCapability);
68 mDataFromTableModel = tableModel;
69 mDataFromTableModel.addTableModelListener (this);
70 buildFromTableModel ();
71 }
72
73 public SortableFilterableTableDataModel (int columnCount, SearchMode searchCapability)
74 {
75 if (columnCount < 0)
76 throw new IllegalArgumentException ("Column count must be a positive integer.");
77 mTableColumns = new ArrayList<TableDataColumn<T, H>> (columnCount);
78 mColumnClasses = new ArrayList<Class<?>> (mColumnCount);
79 for (int i = 0; i < columnCount; ++i)
80 {
81 mTableColumns.add (new TableDataColumn<T, H> (searchCapability));
82 mColumnClasses.add (null);
83 }
84 mTableRows = new ArrayList<TableDataRow<T>> ();
85 mVisibleRows = new ArrayList<TableDataRow<T>> ();
86 mListeners = new ArrayList<ISortableFilterableTableListener> ();
87 mColumnCount = columnCount;
88 }
89
90 public void addSortableFilterableTableListener (ISortableFilterableTableListener listener)
91 {
92 mListeners.add (listener);
93 }
94
95 public void removeSortableFilterableTableListener (ISortableFilterableTableListener listener)
96 {
97 mListeners.remove (listener);
98 }
99
100 public synchronized SortableFilterableTableDataModel<T, H> addStringData (String data)
101 {
102 updateInsertData (addCellLabel (data));
103 return this;
104 }
105
106 public synchronized SortableFilterableTableDataModel<T, H> addData (String data, T additional)
107 {
108 updateInsertData (addCellLabel (data).setAdditionalData (additional));
109 return this;
110 }
111
112 public synchronized SortableFilterableTableDataModel<T, H> addData (T data)
113 {
114 if (data != null)
115 addCellLabel (data.toString ());
116 else
117 addCellLabel ("");
118 updateInsertData (mCurrentRow.getRowData (mCurrentAddColumnIndex).setAdditionalData (data));
119 return this;
120 }
121
122 public synchronized SortableFilterableTableDataModel<T, H> addData (int data)
123 {
124 addData (data, null);
125 return this;
126 }
127
128 public synchronized SortableFilterableTableDataModel<T, H> addData (long data)
129 {
130 addData (data, null);
131 return this;
132 }
133
134 public synchronized SortableFilterableTableDataModel<T, H> addData (float data)
135 {
136 addData (data, null);
137 return this;
138 }
139
140 public synchronized SortableFilterableTableDataModel<T, H> addData (double data)
141 {
142 addData (data, null);
143 return this;
144 }
145
146 public synchronized SortableFilterableTableDataModel<T, H> addData (byte data)
147 {
148 addData (data, null);
149 return this;
150 }
151
152 public synchronized SortableFilterableTableDataModel<T, H> addData (short data)
153 {
154 addData (data, null);
155 return this;
156 }
157
158 public synchronized SortableFilterableTableDataModel<T, H> addData (char data)
159 {
160 addData (data, null);
161 return this;
162 }
163
164 public synchronized SortableFilterableTableDataModel<T, H> addData (int data, T additional)
165 {
166 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
167 .setAdditionalData (additional));
168 return this;
169 }
170
171 public synchronized SortableFilterableTableDataModel<T, H> addData (long data, T additional)
172 {
173 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
174 .setAdditionalData (additional));
175 return this;
176 }
177
178 public synchronized SortableFilterableTableDataModel<T, H> addData (double data, T additional)
179 {
180 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
181 .setAdditionalData (additional));
182 return this;
183 }
184
185 public synchronized SortableFilterableTableDataModel<T, H> addData (float data, T additional)
186 {
187 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
188 .setAdditionalData (additional));
189 return this;
190 }
191
192 public synchronized SortableFilterableTableDataModel<T, H> addData (byte data, T additional)
193 {
194 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
195 .setAdditionalData (additional));
196 return this;
197 }
198
199 public synchronized SortableFilterableTableDataModel<T, H> addData (char data, T additional)
200 {
201 updateInsertData (getCurrentAddRow ().addCellData (mCurrentAddColumnIndex, data)
202 .setAdditionalData (additional));
203 return this;
204 }
205
206 private TableCellData<T> addCellLabel (String label)
207 {
208 TableDataRow<T> row = getCurrentAddRow ();
209 TableDataColumn<T, H> col = mTableColumns.get (mCurrentAddColumnIndex);
210 TableCellData<T> cellData = row.addCellData (mCurrentAddColumnIndex, label, col.isNumeric ());
211 if (col.isNumeric () && ! cellData.isNumeric ()) col.setNumeric (false);
212 return cellData;
213 }
214
215 private TableDataRow<T> getCurrentAddRow ()
216 {
217 if (mDataFromTableModel != null)
218 throw new IllegalStateException (
219 "Cannot add data. Data is already defined through a TableModel.");
220 if (mCurrentRow == null)
221 {
222 mCurrentRow = new TableDataRow<T> (mColumnCount, mTableRows.size ());
223 mTableRows.add (mCurrentRow);
224 mVisibleRows.add (mCurrentRow);
225
226 updateVisibleRows ();
227 }
228 return mCurrentRow;
229 }
230
231 private void updateInsertData (TableCellData<T> cellData)
232 {
233 mTableColumns.get (mCurrentAddColumnIndex).addToSearchTree (cellData.getStringValue (),
234 mCurrentRow);
235 if (mCurrentAddColumnIndex == mColumnCount - 1)
236 {
237 mCurrentAddColumnIndex = 0;
238 mCurrentRow = null;
239 } else
240 ++mCurrentAddColumnIndex;
241 }
242
243 public synchronized void refreshSearchIndices ()
244 {
245 int index = 0;
246 for (TableDataColumn<T, H> column : mTableColumns)
247 {
248 column.clearSearchIndices ();
249 for (TableDataRow<T> row : mTableRows)
250 {
251 column.addToSearchTree (row.getRowData (index).getStringValue (), row);
252 }
253 }
254 ++index;
255 }
256
257 public synchronized void applyFilters ()
258 {
259 for (TableDataRow<T> row : mTableRows)
260 {
261 row.setVisible (true);
262 }
263 for (TableDataColumn<T, H> column : mTableColumns)
264 {
265 column.filterWithPrefix ();
266 column.filterWithInfix ();
267 column.filterWithSimilarContent ();
268 }
269 updateVisibleRows ();
270 }
271
272 private void updateVisibleRows ()
273 {
274 mVisibleRows.clear ();
275 for (TableDataRow<T> row : mTableRows)
276 {
277 if (row.isVisible ())
278 {
279 mVisibleRows.add (row);
280 }
281 }
282
283
284 }
285
286 public String getInfixFilter (int columnIndex)
287 {
288 checkColumnIndex (columnIndex);
289 return mTableColumns.get (columnIndex).getCurrentInfixFilter ();
290 }
291
292 public String getPrefixFilter (int columnIndex)
293 {
294 checkColumnIndex (columnIndex);
295 return mTableColumns.get (columnIndex).getCurrentPrefixFilter ();
296 }
297
298 public String getMatchSimilarFilter (int columnIndex)
299 {
300 checkColumnIndex (columnIndex);
301 return mTableColumns.get (columnIndex).getCurrentMatchSimilarFilter ();
302 }
303
304 public synchronized void setInfixFilter (String infix, int columnIndex, boolean applyFilter)
305 {
306 checkColumnIndex (columnIndex);
307 mTableColumns.get (columnIndex).setInfixFilter (infix);
308 if (applyFilter)
309 {
310 mTableColumns.get (columnIndex).filterWithInfix ();
311 updateVisibleRows ();
312 }
313 }
314
315 public synchronized void setPrefixFilter (String prefix, int columnIndex, boolean applyFilter)
316 {
317 checkColumnIndex (columnIndex);
318 mTableColumns.get (columnIndex).setPrefixFilter (prefix);
319 if (applyFilter)
320 {
321 mTableColumns.get (columnIndex).filterWithPrefix ();
322 updateVisibleRows ();
323 }
324 }
325
326 public synchronized void setMatchSimilarFilter (String filter, int distance, int lengthTolerance,
327 int columnIndex, boolean applyFilter)
328 {
329 checkColumnIndex (columnIndex);
330 mTableColumns.get (columnIndex).setMatchSimilarFilter (filter, distance, lengthTolerance);
331 if (applyFilter)
332 {
333 mTableColumns.get (columnIndex).filterWithSimilarContent ();
334 updateVisibleRows ();
335 }
336 }
337
338 public synchronized void clear ()
339 {
340 mTableColumns.clear ();
341 mTableRows.clear ();
342 mVisibleRows.clear ();
343 mCurrentAddColumnIndex = 0;
344 mCurrentRow = null;
345 fireTableDataChanged ();
346 }
347
348 public synchronized void setColumnHeader (H header, int columnIndex)
349 {
350 checkColumnIndex (columnIndex);
351 mTableColumns.get (columnIndex).setHeader (header);
352 }
353
354 public H getColumnHeader (int columnIndex)
355 {
356 checkColumnIndex (columnIndex);
357 return mTableColumns.get (columnIndex).getHeader ();
358 }
359
360 public synchronized Comparator<T> setColumnSortingComparator (Comparator<T> comparator,
361 int columnIndex)
362 {
363 checkColumnIndex (columnIndex);
364 if (mColumnComparators == null)
365 {
366 mColumnComparators = new ArrayList<Comparator<T>> (mColumnCount);
367 for (int i = 0; i < mColumnCount; ++i)
368 mColumnComparators.add (null);
369 }
370 Comparator<T> old = mColumnComparators.get (columnIndex);
371 mColumnComparators.set (columnIndex, comparator);
372 return old;
373 }
374
375 public synchronized void setColumnClass (Class<?> clazz, int columnIndex)
376 {
377 checkColumnIndex (columnIndex);
378 mColumnClasses.set (columnIndex, clazz);
379 }
380
381 public synchronized void setCaseSensitiveFiltering (boolean caseSensitive, int columnIndex)
382 {
383 checkColumnIndex (columnIndex);
384 mTableColumns.get (columnIndex).setCaseSensitiveFiltering (caseSensitive);
385 refreshSearchIndices ();
386 }
387
388 public synchronized void resetFilters ()
389 {
390 for (TableDataColumn<T, H> column : mTableColumns)
391 {
392 column.resetFilters ();
393 }
394 for (TableDataRow<T> row : mTableRows)
395 {
396 row.setVisible (true);
397 }
398 mVisibleRows.clear ();
399 mVisibleRows.addAll (mTableRows);
400 fireResetAllFilters ();
401 }
402
403 public Class<?> getColumnClass (int columnIndex)
404 {
405 Class<?> result = mColumnClasses.get (columnIndex);
406 if (result == null) return String.class;
407 return result;
408 }
409
410 public int getColumnCount ()
411 {
412 return mColumnCount;
413 }
414
415 public String getColumnName (int columnIndex)
416 {
417 checkColumnIndex (columnIndex);
418 H header = mTableColumns.get (columnIndex).getHeader ();
419 if (header == null) return "";
420 return header.getHeadline ();
421 }
422
423 public int getRowCount ()
424 {
425 return mVisibleRows.size ();
426 }
427
428 public Object getValueAt (int rowIndex, int columnIndex)
429 {
430 return mVisibleRows.get (rowIndex).getRowData (columnIndex).getStringValue ();
431 }
432
433 public TableCellData<T> getCellDataAt (int rowIndex, int columnIndex)
434 {
435 return mVisibleRows.get (rowIndex).getRowData (columnIndex);
436 }
437
438 public boolean isCellEditable (int rowIndex, int columnIndex)
439 {
440 return false;
441 }
442
443 public synchronized void setValueAt (Object aValue, int rowIndex, int columnIndex)
444 {
445 TableDataColumn<T, H> column = mTableColumns.get (columnIndex);
446 TableDataRow<T> row = mVisibleRows.get (rowIndex);
447 TableCellData<T> cellData = row.getRowData (columnIndex);
448 String oldValue = cellData.getStringValue ();
449 String newValue = aValue.toString ();
450 cellData.setStringValue (newValue, column.isNumeric ());
451 if (column.isNumeric () && ! cellData.isNumeric ()) column.setNumeric (false);
452 column.updateSearchTree (oldValue, newValue, row);
453 fireTableCellUpdated (rowIndex, columnIndex);
454 }
455
456 public int getMaximumCellValueLength (int forColumnIndex)
457 {
458 checkColumnIndex (forColumnIndex);
459 return mTableColumns.get (forColumnIndex).getMaximumCellValueLength ();
460 }
461
462 private void checkColumnIndex (int index)
463 {
464 if (index < 0)
465 throw new ArrayIndexOutOfBoundsException (String.format ("index %d < 0", index));
466 if (index >= mColumnCount)
467 throw new ArrayIndexOutOfBoundsException (String.format ("index %d >= %d", index,
468 mColumnCount));
469 }
470
471 public synchronized void restoreOriginalOrdering ()
472 {
473 mSortOrder = SortOrder.UNSORTED;
474 mSortColumnIndex = - 1;
475 Collections.sort (mVisibleRows, mStringComparator);
476
477
478
479 }
480
481 public synchronized void sortColumn (SortOrder order, int columnIndex)
482 {
483 checkColumnIndex (columnIndex);
484 mSortOrder = order;
485 TableDataColumn<T, H> column = mTableColumns.get (columnIndex);
486 Comparator<TableDataRow<T>> comparator = mStringComparator;
487 if (column.isNumeric ()) comparator = mnNumberComparator;
488 Comparator<T> tComparator = mColumnComparators.get (columnIndex);
489 if (tComparator != null) comparator = new TComparator (tComparator);
490
491 mSortColumnIndex = columnIndex;
492 Collections.sort (mVisibleRows, comparator);
493 Collections.sort (mTableRows, comparator);
494 fireTableDataChanged ();
495 }
496
497 private class TComparator implements Comparator<TableDataRow<T>>
498 {
499 private Comparator<T> mComparator;
500
501 public TComparator (Comparator<T> comparator)
502 {
503 assert comparator != null;
504 mComparator = comparator;
505 }
506
507 public int compare (TableDataRow<T> o1, TableDataRow<T> o2)
508 {
509 if (mSortOrder == SortOrder.UNSORTED)
510 return o1.getOriginalIndexInTable ().compareTo (o2.getOriginalIndexInTable ());
511 if (mSortOrder == SortOrder.ASCENDING)
512 return mComparator.compare (o1.getRowData (mSortColumnIndex).getAdditionalData (), o2
513 .getRowData (mSortColumnIndex).getAdditionalData ());
514 else if (mSortOrder == SortOrder.DESCENDING)
515 return mComparator.compare (o2.getRowData (mSortColumnIndex).getAdditionalData (), o1
516 .getRowData (mSortColumnIndex).getAdditionalData ());
517 return 0;
518 }
519 }
520
521 private class StringComparator implements Comparator<TableDataRow<T>>
522 {
523 public int compare (TableDataRow<T> o1, TableDataRow<T> o2)
524 {
525 if (mSortOrder == SortOrder.UNSORTED)
526 return o1.getOriginalIndexInTable ().compareTo (o2.getOriginalIndexInTable ());
527 if (mSortOrder == SortOrder.ASCENDING)
528 return o1.getRowData (mSortColumnIndex).getStringValue ()
529 .compareTo (o2.getRowData (mSortColumnIndex).getStringValue ());
530 else if (mSortOrder == SortOrder.DESCENDING)
531 return o2.getRowData (mSortColumnIndex).getStringValue ()
532 .compareTo (o1.getRowData (mSortColumnIndex).getStringValue ());
533 return 0;
534 }
535 }
536
537 private class NumberComparator implements Comparator<TableDataRow<T>>
538 {
539 public int compare (TableDataRow<T> o1, TableDataRow<T> o2)
540 {
541 if (mSortOrder == SortOrder.UNSORTED)
542 return o1.getOriginalIndexInTable ().compareTo (o2.getOriginalIndexInTable ());
543 if (mSortOrder == SortOrder.ASCENDING)
544 return o1.getRowData (mSortColumnIndex).getNumericValue ()
545 .compareTo (o2.getRowData (mSortColumnIndex).getNumericValue ());
546 else if (mSortOrder == SortOrder.DESCENDING)
547 return o2.getRowData (mSortColumnIndex).getNumericValue ()
548 .compareTo (o1.getRowData (mSortColumnIndex).getNumericValue ());
549 return 0;
550 }
551 }
552
553 public void setTableModel (TableModel model)
554 {
555 CheckForNull.check (model);
556 mDataFromTableModel = model;
557 clear ();
558 buildFromTableModel ();
559 }
560
561 private void buildFromTableModel ()
562 {
563 TableModel model = mDataFromTableModel;
564 mDataFromTableModel = null;
565 for (int r = 0; r < model.getRowCount (); ++r)
566 {
567 for (int c = 0; c < model.getColumnCount (); ++c)
568 {
569 addStringData (model.getValueAt (r, c).toString ());
570 }
571 }
572 mDataFromTableModel = model;
573 }
574
575 public void tableChanged (TableModelEvent e)
576 {
577 if (mDataFromTableModel == null) return;
578
579 clear ();
580 buildFromTableModel ();
581 }
582
583 private void fireResetAllFilters ()
584 {
585 for (ISortableFilterableTableListener l : new ArrayList<ISortableFilterableTableListener> (
586 mListeners))
587 {
588 l.resetAllFilters ();
589 }
590 }
591 }