1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.prometheus.data;
18
19 import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianFactory.GordianFactoryLock;
20 import io.github.tonywasher.joceanus.gordianknot.api.keyset.GordianKeySetSpec;
21 import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
22 import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
23 import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
24 import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
25 import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedItem;
26 import io.github.tonywasher.joceanus.metis.list.MetisListKey;
27 import io.github.tonywasher.joceanus.metis.toolkit.MetisToolkit;
28 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
29 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
30 import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
31 import io.github.tonywasher.joceanus.prometheus.data.PrometheusControlData.PrometheusControlDataList;
32 import io.github.tonywasher.joceanus.prometheus.data.PrometheusControlKey.PrometheusControlKeyList;
33 import io.github.tonywasher.joceanus.prometheus.data.PrometheusControlKeySet.PrometheusControlKeySetList;
34 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataKeySet.PrometheusDataKeySetList;
35 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList.PrometheusDataListSet;
36 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList.PrometheusListStyle;
37 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValidator.PrometheusDataValidatorFactory;
38 import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncryptedDataItem.PrometheusEncryptedList;
39 import io.github.tonywasher.joceanus.prometheus.preference.PrometheusPreferenceManager;
40 import io.github.tonywasher.joceanus.prometheus.preference.PrometheusPreferenceSecurity.PrometheusSecurityPreferenceKey;
41 import io.github.tonywasher.joceanus.prometheus.preference.PrometheusPreferenceSecurity.PrometheusSecurityPreferences;
42 import io.github.tonywasher.joceanus.prometheus.security.PrometheusSecurityPasswordManager;
43 import io.github.tonywasher.joceanus.prometheus.toolkit.PrometheusToolkit;
44 import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;
45
46 import java.util.Iterator;
47 import java.util.LinkedHashMap;
48 import java.util.Map;
49 import java.util.Map.Entry;
50
51
52
53
54 public abstract class PrometheusDataSet
55 implements MetisFieldItem, PrometheusDataListSet {
56
57
58
59 protected static final int HASH_PRIME = 19;
60
61
62
63
64 @SuppressWarnings("rawtypes")
65 private static final MetisFieldSet<PrometheusDataSet> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusDataSet.class);
66
67
68
69
70 static {
71 FIELD_DEFS.declareLocalField(PrometheusDataResource.DATASET_VERSION, PrometheusDataSet::getVersion);
72 FIELD_DEFS.declareLocalFieldsForEnum(PrometheusCryptographyDataType.class, PrometheusDataSet::getFieldListValue);
73 }
74
75
76
77
78 private static final String TASK_SECINIT = PrometheusDataResource.TASK_SECURITY_INIT.getValue();
79
80
81
82
83 private static final String TASK_SECCHECK = PrometheusDataResource.TASK_SECURITY_CHECK.getValue();
84
85
86
87
88 private static final String TASK_SECUPDATE = PrometheusDataResource.TASK_SECURITY_UPDATE.getValue();
89
90
91
92
93 private static final String TASK_SECRENEW = PrometheusDataResource.TASK_SECURITY_RENEW.getValue();
94
95
96
97
98 private static final String TASK_DATAREBASE = PrometheusDataResource.TASK_DATA_REBASE.getValue();
99
100
101
102
103 private static final String TASK_DATADIFF = PrometheusDataResource.TASK_DATA_DIFF.getValue();
104
105
106
107
108 private final PrometheusSecurityPasswordManager thePasswordMgr;
109
110
111
112
113 private final GordianKeySetSpec theKeySetSpec;
114
115
116
117
118 private final int theNumActiveKeySets;
119
120
121
122
123 private int theNumEncrypted;
124
125
126
127
128 private int theVersion;
129
130
131
132
133 private final Map<MetisListKey, PrometheusDataList<?>> theListMap;
134
135
136
137
138 private final OceanusDataFormatter theFormatter;
139
140
141
142
143 private PrometheusDataValidatorFactory theValidatorFactory;
144
145
146
147
148
149
150 protected PrometheusDataSet(final PrometheusToolkit pToolkit) {
151
152 thePasswordMgr = pToolkit.getPasswordManager();
153 theKeySetSpec = thePasswordMgr.getLockSpec().getKeySetSpec();
154
155
156 final PrometheusPreferenceManager myPrefMgr = pToolkit.getPreferenceManager();
157 final PrometheusSecurityPreferences mySecPreferences = myPrefMgr.getPreferenceSet(PrometheusSecurityPreferences.class);
158 theNumActiveKeySets = mySecPreferences.getIntegerValue(PrometheusSecurityPreferenceKey.ACTIVEKEYSETS);
159
160
161 theListMap = new LinkedHashMap<>();
162
163
164 for (PrometheusCryptographyDataType myType : PrometheusCryptographyDataType.values()) {
165
166 addList(myType, newList(myType));
167 }
168
169
170 final MetisToolkit myToolkit = pToolkit.getToolkit();
171 theFormatter = myToolkit.getFormatter();
172 }
173
174
175
176
177
178
179 protected PrometheusDataSet(final PrometheusDataSet pSource) {
180
181
182 theNumActiveKeySets = pSource.getNumActiveKeySets();
183
184
185 thePasswordMgr = pSource.getPasswordMgr();
186
187
188 theKeySetSpec = pSource.getKeySetSpec();
189
190
191 theListMap = new LinkedHashMap<>();
192
193
194 theFormatter = pSource.getDataFormatter();
195
196
197 theValidatorFactory = pSource.theValidatorFactory;
198 }
199
200 @Override
201 public String formatObject(final OceanusDataFormatter pFormatter) {
202 return PrometheusDataSet.class.getSimpleName();
203 }
204
205
206
207
208
209
210 public OceanusDataFormatter getDataFormatter() {
211 return theFormatter;
212 }
213
214
215
216
217
218
219 public void setValidatorFactory(final PrometheusDataValidatorFactory pFactory) {
220 theValidatorFactory = pFactory;
221 }
222
223
224
225
226
227
228
229 PrometheusDataValidator getValidator(final MetisListKey pItemType) {
230 if (theValidatorFactory == null) {
231 throw new IllegalStateException("Validator factory not set");
232 }
233 return theValidatorFactory.newValidator(pItemType);
234 }
235
236
237
238
239
240
241 public PrometheusSecurityPasswordManager getPasswordMgr() {
242 return thePasswordMgr;
243 }
244
245
246
247
248
249
250 public PrometheusControlKeyList getControlKeys() {
251 return getDataList(PrometheusCryptographyDataType.CONTROLKEY, PrometheusControlKeyList.class);
252 }
253
254
255
256
257
258
259 public PrometheusControlKeySetList getControlKeySets() {
260 return getDataList(PrometheusCryptographyDataType.CONTROLKEYSET, PrometheusControlKeySetList.class);
261 }
262
263
264
265
266
267
268 public PrometheusDataKeySetList getDataKeySets() {
269 return getDataList(PrometheusCryptographyDataType.DATAKEYSET, PrometheusDataKeySetList.class);
270 }
271
272
273
274
275
276
277 public PrometheusControlDataList getControlData() {
278 return getDataList(PrometheusCryptographyDataType.CONTROLDATA, PrometheusControlDataList.class);
279 }
280
281
282
283
284
285
286 public int getVersion() {
287 return theVersion;
288 }
289
290
291
292
293
294
295 public int getNumActiveKeySets() {
296 return theNumActiveKeySets;
297 }
298
299
300
301
302
303
304 public GordianKeySetSpec getKeySetSpec() {
305 return theKeySetSpec;
306 }
307
308
309
310
311
312
313 protected Map<MetisListKey, PrometheusDataList<?>> getListMap() {
314 return theListMap;
315 }
316
317 @Override
318 public boolean hasDataType(final MetisListKey pDataType) {
319 return theListMap.containsKey(pDataType);
320 }
321
322
323
324
325
326
327
328 public abstract PrometheusDataSet deriveCloneSet() throws OceanusException;
329
330
331
332
333
334
335
336 private PrometheusDataList<?> newList(final PrometheusCryptographyDataType pListType) {
337
338 switch (pListType) {
339 case CONTROLDATA:
340 return new PrometheusControlDataList(this);
341 case CONTROLKEY:
342 return new PrometheusControlKeyList(this);
343 case CONTROLKEYSET:
344 return new PrometheusControlKeySetList(this);
345 case DATAKEYSET:
346 return new PrometheusDataKeySetList(this);
347 default:
348 throw new IllegalArgumentException(pListType.toString());
349 }
350 }
351
352
353
354
355
356
357
358 protected void buildEmptyCloneSet(final PrometheusDataSet pSource) throws OceanusException {
359
360 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = pSource.entryIterator();
361 while (myIterator.hasNext()) {
362 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
363
364
365 final MetisListKey myType = myEntry.getKey();
366 final PrometheusDataList<?> myList = myEntry.getValue();
367
368
369 addList(myType, myList.getEmptyList(PrometheusListStyle.CLONE));
370 }
371 }
372
373
374
375
376
377
378
379 protected void deriveCloneSet(final PrometheusDataSet pSource) throws OceanusException {
380
381 final Map<MetisListKey, PrometheusDataList<?>> myOldMap = pSource.getListMap();
382
383
384 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = entryIterator();
385 while (myIterator.hasNext()) {
386 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
387
388
389 final MetisListKey myType = myEntry.getKey();
390 final PrometheusDataList<?> myNew = myEntry.getValue();
391 final PrometheusDataList<?> myOld = myOldMap.get(myType);
392
393
394 myNew.cloneList(this, myOld);
395 }
396 }
397
398
399
400
401
402
403
404 public abstract PrometheusDataSet deriveUpdateSet() throws OceanusException;
405
406
407
408
409
410
411
412 protected void deriveUpdateSet(final PrometheusDataSet pSource) throws OceanusException {
413
414 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = pSource.entryIterator();
415 while (myIterator.hasNext()) {
416 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
417
418
419 final MetisListKey myType = myEntry.getKey();
420 final PrometheusDataList<?> myList = myEntry.getValue();
421
422
423 addList(myType, myList.deriveList(PrometheusListStyle.UPDATE));
424 }
425
426
427 if (!isEmpty()) {
428
429 setVersion(1);
430 }
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444 public abstract PrometheusDataSet getDifferenceSet(TethysUIThreadStatusReport pReport,
445 PrometheusDataSet pOld) throws OceanusException;
446
447
448
449
450
451
452
453
454
455
456
457
458 protected void deriveDifferences(final TethysUIThreadStatusReport pReport,
459 final PrometheusDataSet pNew,
460 final PrometheusDataSet pOld) throws OceanusException {
461
462 final OceanusProfile myTask = pReport.getActiveTask();
463 final OceanusProfile myStage = myTask.startTask(TASK_DATADIFF);
464
465
466 final Map<MetisListKey, PrometheusDataList<?>> myOldMap = pOld.getListMap();
467
468
469 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = pNew.entryIterator();
470 while (myIterator.hasNext()) {
471 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
472
473
474 final MetisListKey myType = myEntry.getKey();
475 final PrometheusDataList<?> myNew = myEntry.getValue();
476 final PrometheusDataList<?> myOld = myOldMap.get(myType);
477
478
479 myStage.startTask(myNew.listName());
480 addList(myType, myNew.deriveDifferences(this, myOld));
481 }
482
483
484 myStage.end();
485 }
486
487
488
489
490
491
492
493
494 public void reBase(final TethysUIThreadStatusReport pReport,
495 final PrometheusDataSet pOld) throws OceanusException {
496
497 final OceanusProfile myTask = pReport.getActiveTask();
498 final OceanusProfile myStage = myTask.startTask(TASK_DATAREBASE);
499
500
501 final Map<MetisListKey, PrometheusDataList<?>> myMap = pOld.getListMap();
502
503
504 boolean bUpdates = false;
505 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = entryIterator();
506 while (myIterator.hasNext()) {
507 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
508
509
510 final MetisListKey myType = myEntry.getKey();
511 final PrometheusDataList<?> myList = myEntry.getValue();
512
513
514 myStage.startTask(myList.listName());
515 bUpdates |= myList.reBase(myMap.get(myType));
516 }
517
518
519 if (bUpdates) {
520
521 setVersion(getVersion() + 1);
522 }
523
524
525 myStage.end();
526 }
527
528
529
530
531
532
533
534 protected void addList(final MetisListKey pListType,
535 final PrometheusDataList<?> pList) {
536
537 theListMap.put(pListType, pList);
538
539
540 if (pList instanceof PrometheusEncryptedList) {
541 theNumEncrypted++;
542 }
543 }
544
545 @Override
546 public <L extends PrometheusDataList<?>> L getDataList(final MetisListKey pListType,
547 final Class<L> pListClass) {
548
549 final PrometheusDataList<?> myList = theListMap.get(pListType);
550
551
552 return myList == null
553 ? null
554 : pListClass.cast(myList);
555 }
556
557
558
559
560
561
562
563 protected Object getFieldListValue(final MetisListKey pListType) {
564
565 final PrometheusDataList<?> myList = theListMap.get(pListType);
566
567
568 return myList == null
569 || myList.isEmpty()
570 ? MetisDataFieldValue.SKIP
571 : myList;
572 }
573
574
575
576
577
578
579
580
581 public <L extends PrometheusDataList<?>> L getDataList(final Class<L> pListClass) {
582
583 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = entryIterator();
584 while (myIterator.hasNext()) {
585 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
586
587
588 final PrometheusDataList<?> myList = myEntry.getValue();
589 if (pListClass.equals(myList.getClass())) {
590 return pListClass.cast(myList);
591 }
592 }
593
594
595 return null;
596 }
597
598
599
600
601
602
603 public void setVersion(final int pVersion) {
604
605 theVersion = pVersion;
606
607
608 final Iterator<PrometheusDataList<?>> myIterator = iterator();
609 while (myIterator.hasNext()) {
610 final PrometheusDataList<?> myList = myIterator.next();
611
612
613 myList.setVersion(pVersion);
614 }
615 }
616
617
618
619
620
621
622 public void rewindToVersion(final int pVersion) {
623
624 theVersion = pVersion;
625
626
627 final Iterator<PrometheusDataList<?>> myIterator = iterator();
628 while (myIterator.hasNext()) {
629 final PrometheusDataList<?> myList = myIterator.next();
630
631
632 myList.rewindToVersion(pVersion);
633 }
634 }
635
636 @Override
637 public boolean equals(final Object pThat) {
638
639 if (this == pThat) {
640 return true;
641 }
642 if (pThat == null) {
643 return false;
644 }
645
646
647 if (pThat.getClass() != this.getClass()) {
648 return false;
649 }
650
651
652 final PrometheusDataSet myThat = (PrometheusDataSet) pThat;
653
654
655 if (myThat.getVersion() != theVersion) {
656 return false;
657 }
658
659
660 return theListMap.equals(myThat.getListMap());
661 }
662
663 @Override
664 public int hashCode() {
665
666 int myHashCode = 0;
667
668
669 final Iterator<PrometheusDataList<?>> myIterator = iterator();
670 while (myIterator.hasNext()) {
671 final PrometheusDataList<?> myList = myIterator.next();
672
673
674 myHashCode *= HASH_PRIME;
675 myHashCode += myList.hashCode();
676 }
677
678
679 return myHashCode;
680 }
681
682
683
684
685
686
687 public boolean isEmpty() {
688
689 final Iterator<PrometheusDataList<?>> myIterator = iterator();
690 while (myIterator.hasNext()) {
691 final PrometheusDataList<?> myList = myIterator.next();
692
693
694 if (!myList.isEmpty()) {
695 return false;
696 }
697 }
698
699
700 return true;
701 }
702
703
704
705
706
707
708 public boolean hasUpdates() {
709
710 final Iterator<PrometheusDataList<?>> myIterator = iterator();
711 while (myIterator.hasNext()) {
712 final PrometheusDataList<?> myList = myIterator.next();
713
714
715 if (myList.hasUpdates()) {
716 return true;
717 }
718 }
719
720
721 return false;
722 }
723
724
725
726
727
728
729 public PrometheusControlData getControl() {
730
731 return getControlData().getControl();
732 }
733
734
735
736
737
738
739 public PrometheusControlKey getControlKey() {
740
741 final PrometheusControlData myControl = getControl();
742
743
744 return myControl == null ? null : myControl.getControlKey();
745 }
746
747
748
749
750
751
752
753
754 public void initialiseSecurity(final TethysUIThreadStatusReport pReport,
755 final PrometheusDataSet pBase) throws OceanusException {
756
757 final OceanusProfile myTask = pReport.getActiveTask();
758 final OceanusProfile myStage = myTask.startTask(TASK_SECINIT);
759
760
761 pReport.initTask(TASK_SECINIT);
762 pReport.setNumStages(theNumEncrypted);
763
764
765 getControlKeys().initialiseSecurity(pBase);
766
767
768 final Map<MetisListKey, PrometheusDataList<?>> myMap = pBase.getListMap();
769
770
771 final Iterator<Entry<MetisListKey, PrometheusDataList<?>>> myIterator = entryIterator();
772 while (myIterator.hasNext()) {
773 final Entry<MetisListKey, PrometheusDataList<?>> myEntry = myIterator.next();
774
775
776 final PrometheusDataList<?> myList = myEntry.getValue();
777 final PrometheusDataList<?> myBase = myMap.get(myEntry.getKey());
778
779
780 if (myList instanceof PrometheusEncryptedList) {
781
782 myStage.startTask(myList.listName());
783 final PrometheusEncryptedList<?> myEncrypted = (PrometheusEncryptedList<?>) myList;
784 myEncrypted.adoptSecurity(pReport, (PrometheusEncryptedList<?>) myBase);
785 }
786 }
787
788
789 myStage.end();
790 }
791
792
793
794
795
796
797
798 public void renewSecurity(final TethysUIThreadStatusReport pReport) throws OceanusException {
799
800 final OceanusProfile myTask = pReport.getActiveTask();
801 final OceanusProfile myStage = myTask.startTask(TASK_SECRENEW);
802
803
804 final PrometheusControlData myControl = getControl();
805
806
807 final PrometheusControlKey myKey = getControlKeys().cloneItem(myControl.getControlKey());
808
809
810 myControl.setControlKey(myKey);
811
812
813 updateSecurity(pReport);
814
815
816 myStage.end();
817 }
818
819
820
821
822
823
824
825 public void checkSecurity(final TethysUIThreadStatusReport pReport) throws OceanusException {
826
827 final OceanusProfile myTask = pReport.getActiveTask();
828 final OceanusProfile myStage = myTask.startTask(TASK_SECCHECK);
829
830
831 if (getControlKeys().size() > 1) {
832
833 updateSecurity(pReport);
834 }
835
836
837 myStage.end();
838 }
839
840
841
842
843
844
845
846 public void updateSecurity(final TethysUIThreadStatusReport pReport) throws OceanusException {
847
848 final PrometheusControlKey myControl = getControlKey();
849
850
851 pReport.initTask(TASK_SECUPDATE);
852 pReport.setNumStages(theNumEncrypted);
853
854
855 final Iterator<PrometheusDataList<?>> myIterator = iterator();
856 while (myIterator.hasNext()) {
857 final PrometheusDataList<?> myList = myIterator.next();
858
859
860 if (myList instanceof PrometheusEncryptedList) {
861
862 final PrometheusEncryptedList<?> myEncrypted = (PrometheusEncryptedList<?>) myList;
863 myEncrypted.updateSecurity(pReport, myControl);
864 }
865 }
866
867
868 getControlKeys().purgeOldControlKeys();
869 setVersion(1);
870 }
871
872
873
874
875
876
877
878 public GordianFactoryLock getFactoryLock() throws OceanusException {
879
880 final PrometheusControlKey myKey = getControlKey();
881
882
883 return myKey == null
884 ? null
885 : myKey.getFactoryLock();
886 }
887
888
889
890
891
892
893
894
895 public void updateFactoryLock(final TethysUIThreadStatusReport pReport,
896 final String pSource) throws OceanusException {
897
898 getControlKey().updateFactoryLock(pSource);
899 }
900
901
902
903
904 public void undoLastChange() {
905
906 if (theVersion == 0) {
907 return;
908 }
909
910
911 theVersion--;
912
913
914 rewindToVersion(theVersion);
915 }
916
917
918
919
920 public void resetChanges() {
921
922 if (theVersion == 0) {
923 return;
924 }
925
926
927 theVersion = 0;
928
929
930 rewindToVersion(theVersion);
931 }
932
933
934
935
936
937
938 public Iterator<PrometheusDataList<?>> iterator() {
939 return theListMap.values().iterator();
940 }
941
942
943
944
945
946
947 public Iterator<Entry<MetisListKey, PrometheusDataList<?>>> entryIterator() {
948 return theListMap.entrySet().iterator();
949 }
950
951
952
953
954
955
956 public Iterator<MetisListKey> keyIterator() {
957 return theListMap.keySet().iterator();
958 }
959
960
961
962
963 public enum PrometheusCryptographyDataType
964 implements MetisListKey, MetisDataFieldId {
965
966
967
968 CONTROLKEY(1),
969
970
971
972
973 CONTROLKEYSET(2),
974
975
976
977
978 DATAKEYSET(3),
979
980
981
982
983 CONTROLDATA(4);
984
985
986
987
988 public static final Integer MAXKEYID = CONTROLDATA.getItemKey();
989
990
991
992
993 private final Integer theKey;
994
995
996
997
998 private String theName;
999
1000
1001
1002
1003 private String theListName;
1004
1005
1006
1007
1008
1009
1010 PrometheusCryptographyDataType(final Integer pKey) {
1011 theKey = pKey;
1012 }
1013
1014 @Override
1015 public String toString() {
1016
1017 if (theName == null) {
1018
1019 theName = PrometheusDataResource.getKeyForCryptoItem(this).getValue();
1020 }
1021
1022
1023 return theName;
1024 }
1025
1026 @Override
1027 public String getItemName() {
1028 return toString();
1029 }
1030
1031 @Override
1032 public Class<? extends MetisFieldVersionedItem> getClazz() {
1033 switch (this) {
1034 case CONTROLDATA:
1035 return PrometheusControlData.class;
1036 case CONTROLKEY:
1037 return PrometheusControlKey.class;
1038 case CONTROLKEYSET:
1039 return PrometheusControlKeySet.class;
1040 case DATAKEYSET:
1041 return PrometheusDataKeySet.class;
1042 default:
1043 return null;
1044 }
1045 }
1046
1047 @Override
1048 public String getListName() {
1049
1050 if (theListName == null) {
1051
1052 theListName = PrometheusDataResource.getKeyForCryptoList(this).getValue();
1053 }
1054
1055
1056 return theListName;
1057 }
1058
1059 @Override
1060 public Integer getItemKey() {
1061 return theKey;
1062 }
1063
1064 @Override
1065 public String getId() {
1066 return toString();
1067 }
1068 }
1069 }