1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.moneywise.data.basic;
18
19 import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20 import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
21 import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22 import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
23 import io.github.tonywasher.joceanus.metis.data.MetisDataState;
24 import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
25 import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
26 import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
27 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashCategory.MoneyWiseCashCategoryList;
28 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCashInfo.MoneyWiseCashInfoList;
29 import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
30 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
31 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoType.MoneyWiseAccountInfoTypeList;
32 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCashCategoryClass;
33 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
34 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
35 import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
36 import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
37 import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
38 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
39 import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
40 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
41 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
42 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataMapItem;
43 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
44 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
45 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
46 import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues.PrometheusInfoItem;
47 import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncrypted.PrometheusDataInfoSetItemCtl;
48 import io.github.tonywasher.joceanus.prometheus.data.PrometheusListStyle;
49 import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
50
51 import java.util.Iterator;
52
53
54
55
56 public class MoneyWiseCash
57 extends MoneyWiseAssetBase
58 implements PrometheusDataInfoSetItemCtl<MoneyWiseCashInfo> {
59
60
61
62 public static final String OBJECT_NAME = MoneyWiseBasicDataType.CASH.getItemName();
63
64
65
66
67 public static final String LIST_NAME = MoneyWiseBasicDataType.CASH.getListName();
68
69
70
71
72 private static final MetisFieldVersionedSet<MoneyWiseCash> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseCash.class);
73
74
75
76
77 static {
78 FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFOSET_NAME, MoneyWiseCash::getInfoSet);
79 FIELD_DEFS.buildFieldMap(MoneyWiseAccountInfoClass.class, MoneyWiseCash::getFieldValue);
80 }
81
82
83
84
85 private final boolean hasInfoSet;
86
87
88
89
90 private final boolean useInfoSet;
91
92
93
94
95 private final MoneyWiseCashInfoSet theInfoSet;
96
97
98
99
100
101
102
103 protected MoneyWiseCash(final MoneyWiseCashList pList,
104 final MoneyWiseCash pCash) {
105
106 super(pList, pCash);
107
108
109 switch (getList().getStyle()) {
110 case EDIT:
111 theInfoSet = new MoneyWiseCashInfoSet(this, pList.getActInfoTypes(), pList.getCashInfo());
112 theInfoSet.cloneDataInfoSet(pCash.getInfoSet());
113 hasInfoSet = true;
114 useInfoSet = true;
115 break;
116 case CLONE, CORE:
117 theInfoSet = new MoneyWiseCashInfoSet(this, pList.getActInfoTypes(), pList.getCashInfo());
118 hasInfoSet = true;
119 useInfoSet = false;
120 break;
121 default:
122 theInfoSet = null;
123 hasInfoSet = false;
124 useInfoSet = false;
125 break;
126 }
127 }
128
129
130
131
132
133
134
135
136 private MoneyWiseCash(final MoneyWiseCashList pList,
137 final PrometheusDataValues pValues) throws OceanusException {
138
139 super(pList, pValues);
140
141
142 theInfoSet = new MoneyWiseCashInfoSet(this, pList.getActInfoTypes(), pList.getCashInfo());
143 hasInfoSet = true;
144 useInfoSet = false;
145 }
146
147
148
149
150
151
152 public MoneyWiseCash(final MoneyWiseCashList pList) {
153 super(pList);
154
155
156 theInfoSet = new MoneyWiseCashInfoSet(this, pList.getActInfoTypes(), pList.getCashInfo());
157 hasInfoSet = true;
158 useInfoSet = true;
159 }
160
161 @Override
162 public MetisFieldSetDef getDataFieldSet() {
163 return FIELD_DEFS;
164 }
165
166 @Override
167 public boolean includeXmlField(final MetisDataFieldId pField) {
168
169 if (MoneyWiseBasicResource.CATEGORY_NAME.equals(pField)) {
170 return true;
171 }
172 if (MoneyWiseStaticDataType.CURRENCY.equals(pField)) {
173 return true;
174 }
175
176
177 return super.includeXmlField(pField);
178 }
179
180 @Override
181 public Long getExternalId() {
182 return MoneyWiseAssetType.createExternalId(isAutoExpense() ? MoneyWiseAssetType.AUTOEXPENSE : MoneyWiseAssetType.CASH, getIndexedId());
183 }
184
185 @Override
186 public MoneyWiseCashInfoSet getInfoSet() {
187 return theInfoSet;
188 }
189
190
191
192
193
194
195
196 private Object getFieldValue(final MetisDataFieldId pFieldId) {
197 return theInfoSet != null ? theInfoSet.getFieldValue(pFieldId) : null;
198 }
199
200
201
202
203
204
205 public char[] getNotes() {
206 return hasInfoSet
207 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.NOTES, char[].class)
208 : null;
209 }
210
211
212
213
214
215
216 public MoneyWiseTransCategory getAutoExpense() {
217 return hasInfoSet
218 ? theInfoSet.getEventCategory(MoneyWiseAccountInfoClass.AUTOEXPENSE)
219 : null;
220 }
221
222
223
224
225
226
227 public MoneyWisePayee getAutoPayee() {
228 return hasInfoSet
229 ? theInfoSet.getPayee(MoneyWiseAccountInfoClass.AUTOPAYEE)
230 : null;
231 }
232
233 @Override
234 public OceanusMoney getOpeningBalance() {
235 return hasInfoSet
236 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.OPENINGBALANCE, OceanusMoney.class)
237 : null;
238 }
239
240 @Override
241 public MoneyWiseCashCategory getCategory() {
242 return getValues().getValue(MoneyWiseBasicResource.CATEGORY_NAME, MoneyWiseCashCategory.class);
243 }
244
245
246
247
248
249
250 public Integer getCategoryId() {
251 final MoneyWiseCashCategory myCategory = getCategory();
252 return myCategory == null
253 ? null
254 : myCategory.getIndexedId();
255 }
256
257
258
259
260
261
262 public String getCategoryName() {
263 final MoneyWiseCashCategory myCategory = getCategory();
264 return myCategory == null
265 ? null
266 : myCategory.getName();
267 }
268
269
270
271
272
273
274 public MoneyWiseCashCategoryClass getCategoryClass() {
275 final MoneyWiseCashCategory myCategory = getCategory();
276 return myCategory == null
277 ? null
278 : myCategory.getCategoryTypeClass();
279 }
280
281 @Override
282 public boolean isForeign() {
283 final MoneyWiseCurrency myDefault = getDefaultCurrency();
284 return !myDefault.equals(getAssetCurrency());
285 }
286
287 @Override
288 public boolean isAutoExpense() {
289 return MoneyWiseCashCategoryClass.AUTOEXPENSE.equals(getCategoryClass());
290 }
291
292 @Override
293 public MoneyWiseAssetType getAssetType() {
294 return isAutoExpense()
295 ? MoneyWiseAssetType.AUTOEXPENSE
296 : MoneyWiseAssetType.CASH;
297 }
298
299 @Override
300 public MoneyWiseCash getBase() {
301 return (MoneyWiseCash) super.getBase();
302 }
303
304 @Override
305 public MoneyWiseCashList getList() {
306 return (MoneyWiseCashList) super.getList();
307 }
308
309 @Override
310 public MetisDataState getState() {
311
312 MetisDataState myState = super.getState();
313
314
315 if ((myState == MetisDataState.CLEAN) && useInfoSet) {
316
317 myState = theInfoSet.getState();
318 }
319
320
321 return myState;
322 }
323
324 @Override
325 public MetisDataEditState getEditState() {
326
327 MetisDataEditState myState = super.getEditState();
328
329
330 if (myState == MetisDataEditState.CLEAN
331 && useInfoSet) {
332
333 myState = theInfoSet.getEditState();
334 }
335
336
337 return myState;
338 }
339
340 @Override
341 public boolean hasHistory() {
342
343 boolean hasHistory = super.hasHistory();
344
345
346 if (!hasHistory && useInfoSet) {
347
348 hasHistory = theInfoSet.hasHistory();
349 }
350
351
352 return hasHistory;
353 }
354
355 @Override
356 public void pushHistory() {
357
358 super.pushHistory();
359
360
361 if (useInfoSet) {
362
363 theInfoSet.pushHistory();
364 }
365 }
366
367 @Override
368 public void popHistory() {
369
370 super.popHistory();
371
372
373 if (useInfoSet) {
374
375 theInfoSet.popHistory();
376 }
377 }
378
379 @Override
380 public boolean checkForHistory() {
381
382 boolean bChanges = super.checkForHistory();
383
384
385 if (useInfoSet) {
386
387 bChanges |= theInfoSet.checkForHistory();
388 }
389
390
391 return bChanges;
392 }
393
394 @Override
395 public MetisDataDifference fieldChanged(final MetisDataFieldId pField) {
396
397 final MoneyWiseAccountInfoClass myClass = MoneyWiseCashInfoSet.getClassForField(pField);
398 if (myClass != null) {
399 return useInfoSet
400 ? theInfoSet.fieldChanged(myClass)
401 : MetisDataDifference.IDENTICAL;
402 }
403
404
405 return super.fieldChanged(pField);
406 }
407
408 @Override
409 public void setDeleted(final boolean bDeleted) {
410
411 if (useInfoSet) {
412 theInfoSet.setDeleted(bDeleted);
413 }
414
415
416 super.setDeleted(bDeleted);
417 }
418
419
420
421
422
423
424 public void setDefaults() throws OceanusException {
425 getList().getValidator().setDefaults(this);
426 }
427
428
429
430
431
432
433 public void autoCorrect() throws OceanusException {
434 getList().getValidator().autoCorrect(this);
435 }
436
437 @Override
438 public int compareValues(final PrometheusDataItem pThat) {
439
440 final MoneyWiseCash myThat = (MoneyWiseCash) pThat;
441 int iDiff = MetisDataDifference.compareObject(getCategory(), myThat.getCategory());
442 if (iDiff == 0) {
443 iDiff = MetisDataDifference.compareObject(getName(), myThat.getName());
444 }
445 return iDiff;
446 }
447
448 @Override
449 public void resolveDataSetLinks() throws OceanusException {
450
451 super.resolveDataSetLinks();
452
453
454 resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, MoneyWiseBasicDataType.CASHCATEGORY);
455 resolveDataLink(MoneyWiseStaticDataType.CURRENCY, MoneyWiseStaticDataType.CURRENCY);
456 }
457
458 @Override
459 protected void resolveEditSetLinks() throws OceanusException {
460
461 final PrometheusEditSet myEditSet = getList().getEditSet();
462
463
464 if (myEditSet.hasDataType(MoneyWiseBasicDataType.CASHCATEGORY)) {
465 resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, myEditSet.getDataList(MoneyWiseBasicDataType.CASHCATEGORY, MoneyWiseCashCategoryList.class));
466 }
467 if (myEditSet.hasDataType(MoneyWiseStaticDataType.CURRENCY)) {
468 resolveDataLink(MoneyWiseStaticDataType.CURRENCY, myEditSet.getDataList(MoneyWiseStaticDataType.CURRENCY, MoneyWiseCurrencyList.class));
469 }
470
471
472 theInfoSet.resolveEditSetLinks(myEditSet);
473 }
474
475
476
477
478
479
480
481 public void setNotes(final char[] pNotes) throws OceanusException {
482 setInfoSetValue(MoneyWiseAccountInfoClass.NOTES, pNotes);
483 }
484
485
486
487
488
489
490
491 public void setAutoExpense(final MoneyWiseTransCategory pCategory) throws OceanusException {
492 setInfoSetValue(MoneyWiseAccountInfoClass.AUTOEXPENSE, pCategory);
493 }
494
495
496
497
498
499
500
501 public void setAutoPayee(final MoneyWisePayee pPayee) throws OceanusException {
502 setInfoSetValue(MoneyWiseAccountInfoClass.AUTOPAYEE, pPayee);
503 }
504
505
506
507
508
509
510
511 public void setOpeningBalance(final OceanusMoney pBalance) throws OceanusException {
512 setInfoSetValue(MoneyWiseAccountInfoClass.OPENINGBALANCE, pBalance);
513 }
514
515
516
517
518
519
520
521
522 private void setInfoSetValue(final MoneyWiseAccountInfoClass pInfoClass,
523 final Object pValue) throws OceanusException {
524
525 if (!hasInfoSet) {
526 throw new MoneyWiseLogicException(ERROR_BADINFOSET);
527 }
528
529
530 theInfoSet.setValue(pInfoClass, pValue);
531 }
532
533 @Override
534 public void touchUnderlyingItems() {
535
536 getCategory().touchItem(this);
537 getAssetCurrency().touchItem(this);
538
539
540 theInfoSet.touchUnderlyingItems();
541 }
542
543 @Override
544 public void touchOnUpdate() {
545
546 theInfoSet.touchOnUpdate();
547 }
548
549
550
551
552
553
554
555 @Override
556 public boolean applyChanges(final PrometheusDataItem pCash) {
557
558 if (!(pCash instanceof MoneyWiseCash myCash)) {
559 return false;
560 }
561
562
563 pushHistory();
564
565
566 applyBasicChanges(myCash);
567
568
569 return checkForHistory();
570 }
571
572 @Override
573 public void adjustMapForItem() {
574 final MoneyWiseCashList myList = getList();
575 final MoneyWiseCashDataMap myMap = myList.getDataMap();
576 myMap.adjustForItem(this);
577 }
578
579 @Override
580 public void removeItem() {
581 theInfoSet.removeItems();
582 super.removeItem();
583 }
584
585
586
587
588 public static class MoneyWiseCashList
589 extends MoneyWiseAssetBaseList<MoneyWiseCash> {
590
591
592
593 private static final MetisFieldSet<MoneyWiseCashList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseCashList.class);
594
595
596
597
598 private MoneyWiseCashInfoList theInfoList;
599
600
601
602
603 private MoneyWiseAccountInfoTypeList theInfoTypeList;
604
605
606
607
608
609
610 public MoneyWiseCashList(final PrometheusDataSet pData) {
611 super(pData, MoneyWiseCash.class, MoneyWiseBasicDataType.CASH);
612 }
613
614
615
616
617
618
619 protected MoneyWiseCashList(final MoneyWiseCashList pSource) {
620 super(pSource);
621 }
622
623 @Override
624 public MetisFieldSet<MoneyWiseCashList> getDataFieldSet() {
625 return FIELD_DEFS;
626 }
627
628 @Override
629 public String listName() {
630 return LIST_NAME;
631 }
632
633 @Override
634 public MetisFieldSetDef getItemFields() {
635 return MoneyWiseCash.FIELD_DEFS;
636 }
637
638 @Override
639 protected MoneyWiseCashDataMap getDataMap() {
640 return (MoneyWiseCashDataMap) super.getDataMap();
641 }
642
643
644
645
646
647
648 public MoneyWiseCashInfoList getCashInfo() {
649 if (theInfoList == null) {
650 theInfoList = getDataSet().getDataList(MoneyWiseBasicDataType.CASHINFO, MoneyWiseCashInfoList.class);
651 }
652 return theInfoList;
653 }
654
655
656
657
658
659
660 public MoneyWiseAccountInfoTypeList getActInfoTypes() {
661 if (theInfoTypeList == null) {
662 theInfoTypeList = getEditSet() == null
663 ? getDataSet().getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class)
664 : getEditSet().getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
665 }
666 return theInfoTypeList;
667 }
668
669 @Override
670 protected MoneyWiseCashList getEmptyList(final PrometheusListStyle pStyle) {
671 final MoneyWiseCashList myList = new MoneyWiseCashList(this);
672 myList.setStyle(pStyle);
673 return myList;
674 }
675
676
677
678
679
680
681
682
683 public MoneyWiseCashList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
684
685 final MoneyWiseCashList myList = getEmptyList(PrometheusListStyle.EDIT);
686 final MoneyWisePayeeList myPayees = pEditSet.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class);
687 myList.ensureMap(myPayees);
688 pEditSet.setEditEntryList(MoneyWiseBasicDataType.CASH, myList);
689 myList.getValidator().setEditSet(pEditSet);
690
691
692 myList.theInfoTypeList = pEditSet.getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
693
694
695 final MoneyWiseCashInfoList myCashInfo = getCashInfo();
696 myList.theInfoList = myCashInfo.getEmptyList(PrometheusListStyle.EDIT);
697 pEditSet.setEditEntryList(MoneyWiseBasicDataType.CASHINFO, myList.theInfoList);
698
699
700 myList.setEditSet(pEditSet);
701
702
703 final Iterator<MoneyWiseCash> myIterator = iterator();
704 while (myIterator.hasNext()) {
705 final MoneyWiseCash myCurr = myIterator.next();
706
707
708 if (myCurr.isDeleted()) {
709 continue;
710 }
711
712
713 final MoneyWiseCash myCash = new MoneyWiseCash(myList, myCurr);
714 myList.add(myCash);
715 myCash.resolveEditSetLinks();
716
717
718 myCash.adjustMapForItem();
719 }
720
721
722 return myList;
723 }
724
725 @Override
726 public MoneyWiseCash findItemByName(final String pName) {
727
728 return getDataMap().findItemByName(pName);
729 }
730
731 @Override
732 public boolean checkAvailableName(final String pName) {
733
734 return getDataMap().availableName(pName);
735 }
736
737 @Override
738 public boolean validNameCount(final String pName) {
739
740 return getDataMap().validNameCount(pName);
741 }
742
743 @Override
744 public MoneyWiseCash addCopyItem(final PrometheusDataItem pCash) {
745
746 if (!(pCash instanceof MoneyWiseCash)) {
747 throw new UnsupportedOperationException();
748 }
749
750 final MoneyWiseCash myCash = new MoneyWiseCash(this, (MoneyWiseCash) pCash);
751 add(myCash);
752 return myCash;
753 }
754
755 @Override
756 public MoneyWiseCash addNewItem() {
757 final MoneyWiseCash myCash = new MoneyWiseCash(this);
758 add(myCash);
759 return myCash;
760 }
761
762 @Override
763 public MoneyWiseCash addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
764
765 final MoneyWiseCash myCash = new MoneyWiseCash(this, pValues);
766
767
768 if (!isIdUnique(myCash.getIndexedId())) {
769 myCash.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
770 throw new MoneyWiseDataException(myCash, ERROR_VALIDATION);
771 }
772
773
774 add(myCash);
775
776
777 if (pValues.hasInfoItems()) {
778
779 final Iterator<PrometheusInfoItem> myIterator = pValues.infoIterator();
780 while (myIterator.hasNext()) {
781 final PrometheusInfoItem myItem = myIterator.next();
782
783
784 final PrometheusDataValues myValues = myItem.getValues(myCash);
785 theInfoList.addValuesItem(myValues);
786 }
787 }
788
789
790 return myCash;
791 }
792
793
794
795
796
797
798 private void ensureMap(final MoneyWisePayeeList pPayees) {
799 setDataMap(new MoneyWiseCashDataMap(pPayees));
800 }
801
802 @Override
803 protected MoneyWiseCashDataMap allocateDataMap() {
804 return new MoneyWiseCashDataMap(getDataSet().getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class));
805 }
806
807 @Override
808 public void postProcessOnLoad() throws OceanusException {
809
810 super.resolveDataSetLinks();
811 reSort();
812 }
813 }
814
815
816
817
818 protected static class MoneyWiseCashDataMap
819 implements PrometheusDataMapItem, MetisFieldItem {
820
821
822
823 private static final MetisFieldSet<MoneyWiseCashDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseCashDataMap.class);
824
825
826
827
828 static {
829 FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_UNDERLYING, MoneyWiseCashDataMap::getUnderlyingMap);
830 }
831
832
833
834
835 private final MoneyWiseAssetDataMap theUnderlyingMap;
836
837
838
839
840
841
842 protected MoneyWiseCashDataMap(final MoneyWisePayeeList pPayees) {
843 theUnderlyingMap = pPayees.getDataMap().getUnderlyingMap();
844 }
845
846 @Override
847 public MetisFieldSet<MoneyWiseCashDataMap> getDataFieldSet() {
848 return FIELD_DEFS;
849 }
850
851 @Override
852 public String formatObject(final OceanusDataFormatter pFormatter) {
853 return FIELD_DEFS.getName();
854 }
855
856
857
858
859
860
861 private MoneyWiseAssetDataMap getUnderlyingMap() {
862 return theUnderlyingMap;
863 }
864
865 @Override
866 public void resetMap() {
867
868 }
869
870 @Override
871 public void adjustForItem(final PrometheusDataItem pItem) {
872
873 theUnderlyingMap.adjustForItem(pItem);
874 }
875
876
877
878
879
880
881
882 public MoneyWiseCash findItemByName(final String pName) {
883 final MoneyWiseAssetBase myAsset = theUnderlyingMap.findAssetByName(pName);
884 return myAsset instanceof MoneyWiseCash myCash
885 ? myCash
886 : null;
887 }
888
889
890
891
892
893
894
895 public boolean validNameCount(final String pName) {
896 return theUnderlyingMap.validNameCount(pName);
897 }
898
899
900
901
902
903
904
905 public boolean availableName(final String pName) {
906 return theUnderlyingMap.availableKey(pName);
907 }
908 }
909 }