1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package fulmine.ui;
17
18 import static fulmine.util.Utils.EMPTY_STRING;
19
20 import java.awt.Color;
21 import java.awt.Component;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Enumeration;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.Timer;
30 import java.util.TimerTask;
31 import java.util.Map.Entry;
32
33 import javax.swing.AbstractCellEditor;
34 import javax.swing.JComponent;
35 import javax.swing.JLabel;
36 import javax.swing.JTable;
37 import javax.swing.SwingUtilities;
38 import javax.swing.table.AbstractTableModel;
39 import javax.swing.table.TableCellEditor;
40 import javax.swing.table.TableCellRenderer;
41 import javax.swing.table.TableColumn;
42
43 import org.apache.commons.logging.Log;
44
45 import fulmine.IType;
46 import fulmine.Type;
47 import fulmine.context.IFulmineContext;
48 import fulmine.event.EventFrameExecution;
49 import fulmine.event.IEvent;
50 import fulmine.event.IEventSource;
51 import fulmine.event.listener.EventListenerUtils;
52 import fulmine.event.listener.IEventListener;
53 import fulmine.model.container.IContainer;
54 import fulmine.model.container.IContainer.DataState;
55 import fulmine.model.container.events.ContainerFieldAddedEvent;
56 import fulmine.model.container.events.ContainerFieldRemovedEvent;
57 import fulmine.model.container.events.ContainerStateChangeEvent;
58 import fulmine.model.container.impl.Record;
59 import fulmine.model.field.IField;
60 import fulmine.model.field.StringField;
61 import fulmine.model.field.containerdefinition.ContainerDefinitionField;
62 import fulmine.ui.field.AbstractFieldUI;
63 import fulmine.ui.field.BooleanFieldUI;
64 import fulmine.ui.field.DoubleFieldUI;
65 import fulmine.ui.field.FloatFieldUI;
66 import fulmine.ui.field.IFieldUI;
67 import fulmine.ui.field.IntegerFieldUI;
68 import fulmine.ui.field.LongFieldUI;
69 import fulmine.ui.field.StringFieldUI;
70 import fulmine.util.Utils;
71 import fulmine.util.collection.CollectionFactory;
72 import fulmine.util.log.AsyncLog;
73 import fulmine.util.reference.AutoCreatingStore;
74 import fulmine.util.reference.IAutoCreatingStore;
75 import fulmine.util.reference.IObjectBuilder;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 @SuppressWarnings("unchecked")
102 public class RecordTable extends JTable implements IEventListener
103 {
104 private final static Log LOG = new AsyncLog(RecordTable.class);
105
106
107 private static final long serialVersionUID = 1L;
108
109
110
111
112
113
114 private enum Address
115 {
116 name, type, domain, context
117 }
118
119
120
121
122 private final static IAutoCreatingStore<IType, IFieldUI> UIStore =
123 new AutoCreatingStore<IType, IFieldUI>(
124 new IObjectBuilder<IType, IFieldUI>()
125 {
126 public IFieldUI create(IType key)
127 {
128 try
129 {
130 return (IFieldUI) fieldUIClassPerType.get(key).newInstance();
131 }
132 catch (Exception e)
133 {
134 throw new IllegalStateException(
135 "Could not create the AbstractFieldUI for type "
136 + key, e);
137 }
138 }
139 });
140
141
142 static final Map<IType, Class> fieldUIClassPerType =
143 CollectionFactory.newMap();
144 static
145 {
146 fieldUIClassPerType.put(Type.BOOLEAN_FIELD, BooleanFieldUI.class);
147 fieldUIClassPerType.put(Type.INTEGER_FIELD, IntegerFieldUI.class);
148 fieldUIClassPerType.put(Type.LONG_FIELD, LongFieldUI.class);
149 fieldUIClassPerType.put(Type.FLOAT_FIELD, FloatFieldUI.class);
150 fieldUIClassPerType.put(Type.DOUBLE_FIELD, DoubleFieldUI.class);
151 fieldUIClassPerType.put(Type.STRING_FIELD, StringFieldUI.class);
152 }
153
154
155
156
157
158
159
160
161
162
163 class RecordTableModel extends AbstractTableModel implements IEventListener
164 {
165
166
167 private static final long serialVersionUID = 1L;
168
169
170 private final List<Record> records;
171
172
173 private final List<String> fieldIndexes;
174
175
176 private final Set<String> fieldNames;
177
178
179
180
181 public RecordTableModel()
182 {
183 super();
184 this.records = CollectionFactory.newList(1);
185 this.fieldIndexes = CollectionFactory.newList(3);
186 this.fieldNames = CollectionFactory.newSet(3);
187
188
189 final Address[] values = Address.values();
190 for (Address address : values)
191 {
192 addColumn(new StringField(address.toString()));
193 }
194 }
195
196 public int getColumnCount()
197 {
198 return getFieldIndexes().size();
199 }
200
201 public int getRowCount()
202 {
203 return getRecords().size();
204 }
205
206 public Object getValueAt(int rowIndex, int columnIndex)
207 {
208 if (columnIndex >= getFieldIndexes().size())
209 {
210 return "";
211 }
212 final Record record = getRecords().get(rowIndex);
213
214 if (columnIndex == Address.name.ordinal())
215 {
216 return record.getIdentity();
217 }
218 if (columnIndex == Address.domain.ordinal())
219 {
220 return record.getDomain().getName();
221 }
222 if (columnIndex == Address.type.ordinal())
223 {
224 return record.getType().getName();
225 }
226 if (columnIndex == Address.context.ordinal())
227 {
228 return record.getNativeContextIdentity();
229 }
230 IField field = record.get(getFieldIndexes().get(columnIndex));
231 if (field == null)
232 {
233 return "";
234 }
235
236
237
238
239 return field.getValueAsString();
240 }
241
242 @Override
243 public String getColumnName(int column)
244 {
245 return getFieldIndexes().get(column);
246 }
247
248
249
250
251
252 Map<Integer, List<Format>> formats = CollectionFactory.newMap();
253
254
255
256
257
258 Map<Integer, List<Format>> getFormats()
259 {
260 return this.formats;
261 }
262
263 void addColumn(IField field)
264 {
265 if (field instanceof ContainerDefinitionField)
266 {
267
268 return;
269 }
270 final String identity = field.getIdentity();
271 getFieldIndexes().add(identity);
272 getFieldNames().add(identity);
273 final Set<Entry<Integer, List<Format>>> entrySet =
274 formats.entrySet();
275 for (Entry<Integer, List<Format>> entry : entrySet)
276 {
277 entry.getValue().add(new Format());
278 }
279 fireTableStructureChanged();
280 }
281
282 void deleteColumn(IField field)
283 {
284 final String identity = field.getIdentity();
285 final int index = getFieldIndexes().indexOf(identity);
286 getFieldIndexes().remove(index);
287 getFieldNames().remove(identity);
288 final Set<Entry<Integer, List<Format>>> entrySet =
289 formats.entrySet();
290 for (Entry<Integer, List<Format>> entry : entrySet)
291 {
292 entry.getValue().remove(index);
293 }
294 fireTableStructureChanged();
295 }
296
297 @Override
298 public final int hashCode()
299 {
300 return super.hashCode();
301 }
302
303 @Override
304 public final boolean equals(Object obj)
305 {
306 return super.equals(obj);
307 }
308
309 @Override
310 public String toString()
311 {
312 final SwingWorker<String> worker = new SwingWorker<String>()
313 {
314 @Override
315 public String doWork() throws Exception
316 {
317 return getRecords().toString();
318 }
319 };
320 return worker.invokeAndWait().getResult();
321 }
322
323 public void addedAsListenerFor(IEventSource source)
324 {
325
326 }
327
328 public Class<? extends IEvent>[] getEventTypeFilter()
329 {
330 return eventFilter;
331 }
332
333 public void removedAsListenerFrom(IEventSource source)
334 {
335
336 }
337
338 public void update(final IEvent event)
339 {
340
341
342
343
344
345 SwingUtilities.invokeLater(new Runnable()
346 {
347 public void run()
348 {
349 if (event instanceof Record)
350 {
351 handle((Record) event);
352 return;
353 }
354 if (event instanceof ContainerStateChangeEvent)
355 {
356 handle((ContainerStateChangeEvent) event);
357 return;
358 }
359 if (event instanceof ContainerFieldAddedEvent)
360 {
361 handle((ContainerFieldAddedEvent) event);
362 return;
363 }
364 if (event instanceof ContainerFieldRemovedEvent)
365 {
366 handle((ContainerFieldRemovedEvent) event);
367 return;
368 }
369 if (LOG.isDebugEnabled())
370 {
371 LOG.debug("Unhandled event: "
372 + Utils.safeToString(event));
373 }
374 }
375 });
376 }
377
378
379
380
381
382
383
384 void handle(ContainerFieldRemovedEvent event)
385 {
386
387 boolean found = false;
388 for (Record other : getRecords())
389 {
390 if (!other.getAddress().equals(event.getSource().getAddress())
391 && other.contains(event.getField().getIdentity()))
392 {
393
394 found = true;
395 break;
396 }
397 }
398 if (!found)
399 {
400
401 deleteColumn(event.getField());
402 }
403 }
404
405
406
407
408
409
410
411 void handle(ContainerFieldAddedEvent event)
412 {
413 if (!getFieldNames().contains(event.getField().getIdentity()))
414 {
415
416 addColumn(event.getField());
417 }
418 }
419
420
421
422
423
424
425
426 void handle(ContainerStateChangeEvent event)
427 {
428 if (event.getNewState() == DataState.STALE)
429 {
430
431 final int index = getRecords().indexOf(event.getSource());
432 getRecords().remove(index);
433 fireTableRowsDeleted(index, index);
434 }
435 }
436
437
438
439
440
441
442
443 @SuppressWarnings("boxing")
444 void handle(Record record)
445 {
446
447 if (!record.isActive())
448 {
449 return;
450 }
451 final Collection<IField> changedFields = record.getChangedFields();
452
453
454
455
456 for (IField field : changedFields)
457 {
458 if (!getFieldNames().contains(field.getIdentity()))
459 {
460
461 addColumn(field);
462 }
463 }
464
465 int index = getRecords().indexOf(record);
466 if (index > -1)
467 {
468 getRecords().set(index, record);
469 if (changedFields.size() == 0)
470 {
471 fireTableRowsUpdated(index, index);
472 }
473 else
474 {
475 for (IField field : changedFields)
476 {
477 final int col =
478 getFieldIndexes().indexOf(field.getIdentity());
479 cellUpdated(index, col);
480 fireTableCellUpdated(index, col);
481 }
482 }
483 }
484 else
485 {
486
487 getRecords().add(record);
488 index = getRecords().indexOf(record);
489 final ArrayList<Format> columnFormats =
490 new ArrayList<Format>(1);
491 for (int i = 0; i < fieldIndexes.size(); i++)
492 {
493 columnFormats.add(new Format());
494 }
495 formats.put(index, columnFormats);
496 fireTableRowsInserted(index, index);
497 }
498 }
499
500 @SuppressWarnings("boxing")
501 private void cellUpdated(int row, int col)
502 {
503 RecordTable.this.updateHandler.getUpdated().get(row).add(col);
504 }
505
506
507
508
509
510
511 Set<String> getFieldNames()
512 {
513 return this.fieldNames;
514 }
515
516
517
518
519
520
521
522
523 List<String> getFieldIndexes()
524 {
525 return this.fieldIndexes;
526 }
527
528
529
530
531
532
533 List<Record> getRecords()
534 {
535 return this.records;
536 }
537 }
538
539 private static final Color SELECTED_COLOUR = new Color(200, 200, 120);
540
541
542
543
544
545
546 class Format
547 {
548
549 private boolean update;
550
551 private boolean opaque = true;
552
553 private Color background;
554
555 private Color selectedBackground = SELECTED_COLOUR;
556
557 public Color getUpdateBackground()
558 {
559 return Color.CYAN;
560 }
561
562 public Color getBackground(boolean isSelected)
563 {
564 if (this.isUpdate())
565 {
566 return getUpdateBackground();
567 }
568 if (isSelected)
569 {
570 return getSelectedBackground();
571 }
572 return this.background;
573 }
574
575 public boolean isOpaque()
576 {
577 return this.opaque;
578 }
579
580 public void setBackground(Color background)
581 {
582 this.background = background;
583 }
584
585 public void setOpaque(boolean opaque)
586 {
587 this.opaque = opaque;
588 }
589
590 boolean isUpdate()
591 {
592 return this.update;
593 }
594
595 void setUpdate(boolean update)
596 {
597 this.update = update;
598 }
599
600 public Color getSelectedBackground()
601 {
602 return this.selectedBackground;
603 }
604 }
605
606
607
608
609
610
611
612
613 class RecordTableCellRendererAndEditor extends AbstractCellEditor implements
614 TableCellRenderer, TableCellEditor
615 {
616
617 private static final long serialVersionUID = 1L;
618
619
620
621
622
623 private IFieldUI activeUI;
624
625 public RecordTableCellRendererAndEditor()
626 {
627 super();
628 }
629
630 @SuppressWarnings("boxing")
631 public Component getTableCellRendererComponent(JTable table,
632 Object value, boolean isSelected, boolean hasFocus, final int row,
633 final int column)
634 {
635 final int convertedColumn = convertColumnIndexToModel(column);
636 final Component renderer =
637 doGetComponent(table, value, isSelected, hasFocus, row,
638 convertedColumn, true);
639
640 final Format format =
641 getModel().getFormats().get(row).get(convertedColumn);
642 if (renderer instanceof JComponent)
643 {
644 ((JComponent) renderer).setOpaque(format.isOpaque());
645 }
646
647
648 final Iterator<Integer> iterator =
649 RecordTable.this.updateHandler.getUpdated().get(row).iterator();
650 while (iterator.hasNext())
651 {
652 final Integer next = iterator.next();
653 if (next.equals(convertedColumn))
654 {
655 iterator.remove();
656 format.setUpdate(true);
657 TableUpdateHandler.updateTimer.schedule(new TimerTask()
658 {
659 @Override
660 public void run()
661 {
662 SwingUtilities.invokeLater(new Runnable()
663 {
664 public void run()
665 {
666 format.setUpdate(false);
667
668 getModel().fireTableCellUpdated(row,
669 convertedColumn);
670 }
671 });
672 }
673 }, 500);
674 }
675 }
676
677 renderer.setBackground(format.getBackground(isSelected));
678 return renderer;
679 }
680
681 public Component getTableCellEditorComponent(JTable table,
682 Object value, boolean isSelected, int row, int column)
683 {
684 return doGetComponent(table, value, isSelected, true, row,
685 convertColumnIndexToModel(column), false);
686 }
687
688 public Object getCellEditorValue()
689 {
690 return this.activeUI == null ? EMPTY_STRING
691 : this.activeUI.getCellEditorValue();
692 }
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714 @SuppressWarnings("boxing")
715 private Component doGetComponent(JTable table, Object value,
716 boolean isSelected, boolean hasFocus, int row, int column,
717 boolean renderer)
718 {
719 IType type = Type.STRING_FIELD;
720 final Record record = getModel().getRecords().get(row);
721 final Format format = getModel().getFormats().get(row).get(column);
722 if (column >= Address.values().length)
723 {
724 final String fieldName =
725 getModel().getFieldIndexes().get(column);
726 final IField field = record.get(fieldName);
727
728 if (field == null)
729 {
730 JLabel component = new JLabel();
731 component.setText("" + value);
732 format.setBackground(Color.gray);
733 format.setOpaque(true);
734 component.setEnabled(false);
735 return component;
736 }
737 type = field.getType();
738 }
739
740 format.setBackground(Color.white);
741
742 IFieldUI ui = getFieldUIForType(type);
743 Component component;
744 if (renderer)
745 {
746 component =
747 ui.getTableCellRendererComponent(table, value, isSelected,
748 hasFocus, row, column);
749 }
750 else
751 {
752 component =
753 ui.getTableCellEditorComponent(table, value, isSelected,
754 row, column);
755 }
756 this.activeUI = ui;
757 if (DataState.STALE.equals(record.getDataState()))
758 {
759 component.setEnabled(false);
760 }
761 else
762 {
763 component.setEnabled(true);
764 }
765 return component;
766 }
767
768
769
770
771
772
773
774
775
776 private IFieldUI getFieldUIForType(final IType fieldType)
777 {
778 return UIStore.get(fieldType);
779 }
780 }
781
782
783
784
785
786 static final Class<? extends IEvent>[] eventFilter =
787 EventListenerUtils.createFilter(IContainer.class,
788 ContainerStateChangeEvent.class, ContainerFieldRemovedEvent.class,
789 ContainerFieldAddedEvent.class);
790
791
792 private final RecordTableModel model;
793
794
795 private final TableUpdateHandler updateHandler = new TableUpdateHandler();
796
797 public RecordTable()
798 {
799 super();
800 this.model = new RecordTableModel();
801 setModel(this.model);
802 setAutoCreateColumnsFromModel(true);
803 }
804
805 @Override
806 public RecordTableModel getModel()
807 {
808 return this.model;
809 }
810
811
812
813
814 @Override
815 public void createDefaultColumnsFromModel()
816 {
817 super.createDefaultColumnsFromModel();
818 final Enumeration<TableColumn> columns = getColumnModel().getColumns();
819 while (columns.hasMoreElements())
820 {
821 final TableColumn column = columns.nextElement();
822 final RecordTableCellRendererAndEditor cellRenderer =
823 new RecordTableCellRendererAndEditor();
824 column.setCellRenderer(cellRenderer);
825 column.setCellEditor(cellRenderer);
826 }
827 }
828
829
830
831
832
833 public boolean isCellEditable(int row, int col)
834 {
835 final Record record = getModel().getRecords().get(row);
836 col = convertColumnIndexToModel(col);
837 final String fieldName = getModel().getFieldIndexes().get(col);
838 return record.contains(fieldName);
839 }
840
841 public void setValueAt(final Object value, int row, int col)
842 {
843 if (LOG.isTraceEnabled())
844 {
845 LOG.trace("value=" + value + ", row=" + row + ", col=" + col);
846 }
847 Record record = getModel().getRecords().get(row);
848 col = convertColumnIndexToModel(col);
849 final String fieldName = getModel().getFieldIndexes().get(col);
850 if (record.isLocal())
851 {
852
853 record =
854 (Record) record.getContext().getLocalContainer(
855 record.getIdentity(), record.getType(), record.getDomain());
856 record.beginFrame(new EventFrameExecution());
857 try
858 {
859 final IField field = record.get(fieldName);
860 if (field == null)
861 {
862 if (LOG.isTraceEnabled())
863 {
864 LOG.trace("No field");
865 }
866 return;
867 }
868 try
869 {
870 field.setValueFromString(EMPTY_STRING + value);
871 }
872 catch (Exception e)
873 {
874 Utils.logException(LOG, "Could not set " + value + " on "
875 + field + " for record " + record.getAddress(), e);
876 }
877 }
878 finally
879 {
880 record.endFrame();
881 }
882 }
883 else
884 {
885 final IContainer remoteRecord = record;
886
887 record.getContext().execute(new Runnable()
888 {
889 public void run()
890 {
891 remoteRecord.getContext().updateRemoteContainer(
892 remoteRecord.getNativeContextIdentity(),
893 remoteRecord.getIdentity(), remoteRecord.getType(),
894 remoteRecord.getDomain(), fieldName,
895 EMPTY_STRING + value);
896 }
897 });
898 }
899 }
900
901 public void addedAsListenerFor(IEventSource source)
902 {
903 getModel().addedAsListenerFor(source);
904 }
905
906 public Class<? extends IEvent>[] getEventTypeFilter()
907 {
908 return getModel().getEventTypeFilter();
909 }
910
911 public void removedAsListenerFrom(IEventSource source)
912 {
913 getModel().removedAsListenerFrom(source);
914 }
915
916 public void update(IEvent event)
917 {
918 getModel().update(event);
919 }
920
921
922
923
924
925 public Record getSelectedRecord()
926 {
927 return getModel().getRecords().get(getSelectedRow());
928 }
929
930
931
932
933 public IField getSelectedField()
934 {
935 final int column = convertColumnIndexToModel(getSelectedColumn());
936 final String name = getModel().getColumnName(column);
937 final Record record = getSelectedRecord();
938 return record.get(name);
939 }
940 }
941
942
943
944
945
946
947 final class TableUpdateHandler
948 {
949
950
951 final static Timer updateTimer;
952 static
953 {
954 updateTimer =
955 new Timer(TableUpdateHandler.class.getSimpleName() + "UpdateTimer",
956 true);
957 }
958
959
960
961
962 private final IAutoCreatingStore<Integer, Set<Integer>> updated =
963 new AutoCreatingStore<Integer, Set<Integer>>(
964 new IObjectBuilder<Integer, Set<Integer>>()
965 {
966 public Set<Integer> create(Integer key)
967 {
968 return CollectionFactory.newSet(1);
969 }
970 });
971
972
973
974
975
976 IAutoCreatingStore<Integer, Set<Integer>> getUpdated()
977 {
978 return this.updated;
979 }
980
981 }