View Javadoc
1   /*
2    * Prometheus: Application Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.prometheus.data;
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.MetisFieldSet;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldState;
26  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionValues;
27  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedItem;
28  import io.github.tonywasher.joceanus.metis.list.MetisListKey;
29  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
30  import io.github.tonywasher.joceanus.oceanus.convert.OceanusDataConverter;
31  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
32  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataItemCtl;
33  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataListCtl;
34  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataSetCtl;
35  import io.github.tonywasher.joceanus.prometheus.data.PrometheusData.PrometheusDataValuesCtl;
36  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
37  
38  import java.util.Iterator;
39  
40  /**
41   * Provides the abstract DataItem class as the basis for data items. The implementation of the
42   * interface means that this object can only be held in one list at a time and is unique within that
43   * list
44   *
45   * @see PrometheusDataList
46   */
47  public abstract class PrometheusDataItem
48          extends MetisFieldVersionedItem
49          implements PrometheusDataItemCtl, Comparable<Object> {
50      /**
51       * Report fields.
52       */
53      private static final MetisFieldSet<PrometheusDataItem> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusDataItem.class);
54  
55      /*
56       * FieldIds.
57       */
58      static {
59          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATALIST_NAME, PrometheusDataItem::getList);
60          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_BASE, PrometheusDataItem::getBase);
61          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_TOUCH, PrometheusDataItem::getTouchStatus);
62          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_HEADER, PrometheusDataItem::isHeader);
63      }
64  
65      /**
66       * Validation error.
67       */
68      public static final String ERROR_VALIDATION = PrometheusDataResource.DATAITEM_ERROR_VALIDATION.getValue();
69  
70      /**
71       * Resolution error.
72       */
73      public static final String ERROR_RESOLUTION = PrometheusDataResource.DATAITEM_ERROR_RESOLUTION.getValue();
74  
75      /**
76       * Duplicate Id error.
77       */
78      public static final String ERROR_DUPLICATE = PrometheusDataResource.DATAITEM_ERROR_DUPLICATE.getValue();
79  
80      /**
81       * Unknown Id error.
82       */
83      public static final String ERROR_UNKNOWN = PrometheusDataResource.DATAITEM_ERROR_UNKNOWN.getValue();
84  
85      /**
86       * Existing value error.
87       */
88      public static final String ERROR_EXIST = PrometheusDataResource.DATAITEM_ERROR_EXIST.getValue();
89  
90      /**
91       * Missing value error.
92       */
93      public static final String ERROR_MISSING = PrometheusDataResource.DATAITEM_ERROR_MISSING.getValue();
94  
95      /**
96       * Value too long error.
97       */
98      public static final String ERROR_LENGTH = PrometheusDataResource.DATAITEM_ERROR_LENGTH.getValue();
99  
100     /**
101      * Value negative error.
102      */
103     public static final String ERROR_NEGATIVE = PrometheusDataResource.DATAITEM_ERROR_NEGATIVE.getValue();
104 
105     /**
106      * Value positive error.
107      */
108     public static final String ERROR_POSITIVE = PrometheusDataResource.DATAITEM_ERROR_POSITIVE.getValue();
109 
110     /**
111      * Value zero error.
112      */
113     public static final String ERROR_ZERO = PrometheusDataResource.DATAITEM_ERROR_ZERO.getValue();
114 
115     /**
116      * Value outside valid range.
117      */
118     public static final String ERROR_RANGE = PrometheusDataResource.DATAITEM_ERROR_RANGE.getValue();
119 
120     /**
121      * Value disabled error.
122      */
123     public static final String ERROR_DISABLED = PrometheusDataResource.DATAITEM_ERROR_DISABLED.getValue();
124 
125     /**
126      * Creation failure.
127      */
128     public static final String ERROR_CREATEITEM = PrometheusDataResource.DATAITEM_ERROR_CREATE.getValue();
129 
130     /**
131      * Multiple instances Error.
132      */
133     public static final String ERROR_MULT = PrometheusDataResource.DATAITEM_ERROR_MULTIPLE.getValue();
134 
135     /**
136      * Reserved name error.
137      */
138     public static final String ERROR_INVALIDCHAR = PrometheusDataResource.DATAITEM_ERROR_INVALIDCHAR.getValue();
139 
140     /**
141      * Standard Name length.
142      */
143     public static final int NAMELEN = 30;
144 
145     /**
146      * Standard Description length.
147      */
148     public static final int DESCLEN = 50;
149 
150     /**
151      * The list to which this item belongs.
152      */
153     private final PrometheusDataListCtl<?> theList;
154 
155     /**
156      * The item that this DataItem is based upon.
157      */
158     private PrometheusDataItem theBase;
159 
160     /**
161      * Is the item a header.
162      */
163     private boolean isHeader;
164 
165     /**
166      * Status.
167      */
168     private final PrometheusDataTouch theTouchStatus;
169 
170     /**
171      * Construct a new item.
172      *
173      * @param pList the list that this item is associated with
174      * @param uId   the Id of the new item (or 0 if not yet known)
175      */
176     protected PrometheusDataItem(final PrometheusDataListCtl<?> pList,
177                                  final Integer uId) {
178         /* Record list and item references */
179         setIndexedId(uId);
180         theList = pList;
181 
182         /* Allocate id */
183         pList.setNewId(this);
184 
185         /* Create the touch status */
186         theTouchStatus = new PrometheusDataTouch();
187     }
188 
189     /**
190      * Construct a new item.
191      *
192      * @param pList   the list that this item is associated with
193      * @param pValues the data values
194      */
195     protected PrometheusDataItem(final PrometheusDataListCtl<?> pList,
196                                  final PrometheusDataValuesCtl pValues) {
197         /* Record list and item references */
198         this(pList, pValues.getValue(MetisDataResource.DATA_ID, Integer.class));
199     }
200 
201     /**
202      * Construct a new item based on an old item.
203      *
204      * @param pList the list that this item is associated with
205      * @param pBase the old item
206      */
207     protected PrometheusDataItem(final PrometheusDataListCtl<?> pList,
208                                  final PrometheusDataItem pBase) {
209         /* Initialise using standard constructor */
210         this(pList, pBase.getIndexedId());
211 
212         /* Initialise the valueSet */
213         getValues().copyFrom(pBase.getValues());
214 
215         /* Access the varying styles and the source state */
216         final PrometheusListStyle myStyle = pList.getStyle();
217         final PrometheusListStyle myBaseStyle = pBase.getList().getStyle();
218         final MetisDataState myState = pBase.getState();
219 
220         /* Switch on the styles */
221         switch (myStyle) {
222             /* We are building an update list (from Core) */
223             case UPDATE:
224                 switch (myState) {
225                     /* NEW/DELNEW need to be at version 1 */
226                     case DELNEW, NEW:
227                         getValues().setVersion(1);
228                         break;
229 
230                     case DELETED:
231                         getValuesHistory().pushHistory(1);
232                         break;
233 
234                     /*
235                      * Changed items need to have new values at version 1 and originals at version 0
236                      */
237                     case CHANGED:
238                         setHistory(pBase);
239                         break;
240 
241                     /* No change for other states */
242                     default:
243                         break;
244                 }
245 
246                 /* Record the base item */
247                 theBase = pBase;
248                 break;
249 
250             /* We are building an edit item (from Core/Edit) */
251             case EDIT:
252                 /* Switch on the base style */
253                 switch (myBaseStyle) {
254                     /* New item from core we need to link back and copy flags */
255                     case CORE:
256                         theBase = pBase;
257                         copyFlags(pBase);
258                         break;
259                     /* Duplication in edit */
260                     case EDIT:
261                         /* set as a new item */
262                         getValues().setVersion(pList.getVersion() + 1);
263 
264                         /* Reset the Id */
265                         setIndexedId(0);
266                         pList.setNewId(this);
267                         break;
268                     default:
269                         break;
270                 }
271                 break;
272 
273             /* We are building a CORE item */
274             case CORE:
275                 /* set as a new item */
276                 getValues().setVersion(pList.getVersion() + 1);
277 
278                 /* If we are adding from Edit */
279                 if (myBaseStyle == PrometheusListStyle.EDIT) {
280                     /* Reset the Id */
281                     setIndexedId(0);
282                     pList.setNewId(this);
283                 }
284                 break;
285 
286             /* Creation of copy element not allowed */
287             case COPY:
288                 throw new IllegalArgumentException("Illegal creation of COPY element");
289 
290                 /* Nothing special for other styles */
291             default:
292                 break;
293         }
294     }
295 
296     /**
297      * Obtain valueSet version.
298      *
299      * @return the valueSet version
300      */
301     public int getValueSetVersion() {
302         return getValues().getVersion();
303     }
304 
305     @Override
306     public String formatObject(final OceanusDataFormatter pFormatter) {
307         return this.getDataFieldSet().getName();
308     }
309 
310     /**
311      * Obtain the list.
312      *
313      * @return the list
314      */
315     public PrometheusDataListCtl<?> getList() {
316         return theList;
317     }
318 
319     @Override
320     public PrometheusDataSetCtl getDataSet() {
321         return getTheDataSet();
322     }
323 
324     /**
325      * Obtain the dataSet.
326      *
327      * @return the dataSet
328      */
329     private PrometheusDataSetCtl getTheDataSet() {
330         return theList.getDataSet();
331     }
332 
333     /**
334      * Get the list style for this item.
335      *
336      * @return the list style
337      */
338     public PrometheusListStyle getStyle() {
339         return theList.getStyle();
340     }
341 
342     @Override
343     public MetisListKey getItemType() {
344         return theList.getItemType();
345     }
346 
347     @Override
348     public boolean isActive() {
349         return theTouchStatus.isActive();
350     }
351 
352     /**
353      * Is the item disabled?
354      *
355      * @return true/false
356      */
357     public boolean isDisabled() {
358         return false;
359     }
360 
361     /**
362      * Obtain the touchStatus.
363      *
364      * @return the touch status
365      */
366     public PrometheusDataTouch getTouchStatus() {
367         return theTouchStatus;
368     }
369 
370     @Override
371     public boolean isEditable() {
372         return !isDeleted();
373     }
374 
375     @Override
376     public boolean isHeader() {
377         return isHeader;
378     }
379 
380     /**
381      * Set the header indication.
382      *
383      * @param pHeader true/false
384      */
385     protected void setHeader(final boolean pHeader) {
386         isHeader = pHeader;
387     }
388 
389     /**
390      * Determine whether the item is locked (overridden if required).
391      *
392      * @return <code>true/false</code>
393      */
394     public boolean isLocked() {
395         return false;
396     }
397 
398     /**
399      * Determine whether the list is locked (overridden if required).
400      *
401      * @return <code>true/false</code>
402      */
403     public boolean isListLocked() {
404         return false;
405     }
406 
407     /**
408      * DeRegister any infoSet links.
409      */
410     public void deRegister() {
411     }
412 
413     /**
414      * Clear the touch status flag.
415      */
416     public void clearActive() {
417         theTouchStatus.resetTouches();
418     }
419 
420     /**
421      * Clear the item touches.
422      *
423      * @param pItemType the item type
424      */
425     public void clearTouches(final MetisListKey pItemType) {
426         theTouchStatus.resetTouches(pItemType);
427     }
428 
429     /**
430      * Touch the item.
431      *
432      * @param pObject object that references the item
433      */
434     public void touchItem(final PrometheusDataItem pObject) {
435         theTouchStatus.touchItem(pObject.getItemType());
436     }
437 
438     /**
439      * Touch underlying items that are referenced by this item.
440      */
441     public void touchUnderlyingItems() {
442     }
443 
444     /**
445      * Adjust touches on update.
446      */
447     public void touchOnUpdate() {
448     }
449 
450     /**
451      * Adjust map for this item.
452      */
453     public void adjustMapForItem() {
454     }
455 
456     /**
457      * update Maps.
458      */
459     public void updateMaps() {
460         /* Clear active flag and touch underlying items */
461         clearActive();
462         touchUnderlyingItems();
463 
464         /* Adjust the map for this item */
465         adjustMapForItem();
466     }
467 
468     /**
469      * Get the base item for this item.
470      *
471      * @return the Base item or <code>null</code>
472      */
473     public PrometheusDataItem getBase() {
474         return theBase;
475     }
476 
477     /**
478      * Set the base item for this item.
479      *
480      * @param pBase the Base item
481      */
482     public void setBase(final PrometheusDataItem pBase) {
483         theBase = pBase;
484     }
485 
486     /**
487      * Unlink the item from the list.
488      */
489     public void unLink() {
490         theList.remove(this);
491     }
492 
493     /**
494      * Set new version.
495      */
496     public void setNewVersion() {
497         getValues().setVersion(getNextVersion());
498     }
499 
500     @Override
501     public int getNextVersion() {
502         return theList.getVersion() + 1;
503     }
504 
505     @Override
506     public void popHistory() {
507         rewindToVersion(theList.getVersion());
508     }
509 
510     @Override
511     public void rewindToVersion(final int pVersion) {
512         /* If the item was newly created */
513         if (getOriginalValues().getVersion() > pVersion) {
514             /* Remove from list */
515             unLink();
516             deRegister();
517 
518             /* Return */
519             return;
520         }
521 
522         /* Loop while version is too high */
523         while (getValues().getVersion() > pVersion) {
524             /* Pop history */
525             getValuesHistory().popTheHistory();
526         }
527     }
528 
529     /**
530      * Set Change history for an update list so that the first and only entry in the change list is
531      * the original values of the base.
532      *
533      * @param pBase the base item
534      */
535     public final void setHistory(final PrometheusDataItem pBase) {
536         getValuesHistory().setHistory(pBase.getOriginalValues());
537     }
538 
539     @Override
540     public MetisDataDifference fieldChanged(final MetisFieldDef pField) {
541         return pField instanceof MetisFieldVersionedDef
542                 ? getValuesHistory().fieldChanged(pField)
543                 : MetisDataDifference.IDENTICAL;
544     }
545 
546     /**
547      * Note that this item has been validated.
548      */
549     public void setValidEdit() {
550         final MetisDataState myState = getState();
551         if (myState == MetisDataState.CLEAN) {
552             setEditState(MetisDataEditState.CLEAN);
553         } else if (theList.getStyle() == PrometheusListStyle.CORE) {
554             setEditState(MetisDataEditState.DIRTY);
555         } else {
556             setEditState(MetisDataEditState.VALID);
557         }
558     }
559 
560     @Override
561     public void addError(final String pError,
562                          final MetisDataFieldId pField) {
563         /* Set edit state and add the error */
564         super.addError(pError, pField);
565 
566         /* Note that the list has errors */
567         theList.setEditState(MetisDataEditState.ERROR);
568     }
569 
570     /**
571      * Copy flags.
572      *
573      * @param pItem the original item
574      */
575     private void copyFlags(final PrometheusDataItem pItem) {
576         theTouchStatus.copyMap(pItem.theTouchStatus);
577     }
578 
579     /**
580      * Resolve all references to current dataSet.
581      *
582      * @throws OceanusException on error
583      */
584     public void resolveDataSetLinks() throws OceanusException {
585     }
586 
587     /**
588      * Resolve a data link into a list.
589      *
590      * @param pFieldId  the fieldId to resolve
591      * @param pDataType the dataType to resolve against
592      * @throws OceanusException on error
593      */
594     protected void resolveDataLink(final MetisDataFieldId pFieldId,
595                                    final MetisListKey pDataType) throws OceanusException {
596         final PrometheusDataListCtl<?> myList = getDataSet().getDataList(pDataType, PrometheusDataListCtl.class);
597         resolveDataLink(pFieldId, myList);
598     }
599 
600     /**
601      * Resolve a data link into a list.
602      *
603      * @param pFieldId the fieldId to resolve
604      * @param pList    the list to resolve against
605      * @throws OceanusException on error
606      */
607     protected void resolveDataLink(final MetisDataFieldId pFieldId,
608                                    final PrometheusDataListCtl<?> pList) throws OceanusException {
609         /* Access the values */
610         final MetisFieldVersionValues myValues = getValues();
611 
612         /* Access value for field */
613         Object myValue = myValues.getValue(pFieldId);
614 
615         /* Convert dataItem reference to Id */
616         if (myValue instanceof PrometheusDataItem myItem) {
617             myValue = myItem.getIndexedId();
618         }
619 
620         /* Lookup Id reference */
621         if (myValue instanceof Integer i) {
622             final PrometheusDataItem myItem = (PrometheusDataItem) pList.findItemById(i);
623             if (myItem == null) {
624                 addError(ERROR_UNKNOWN, pFieldId);
625                 throw new PrometheusDataException(this, ERROR_RESOLUTION);
626             }
627             myValues.setValue(pFieldId, myItem);
628 
629             /* Lookup Name reference */
630         } else if (myValue instanceof String s) {
631             final PrometheusDataItem myItem = (PrometheusDataItem) pList.findItemByName(s);
632             if (myItem == null) {
633                 addError(ERROR_UNKNOWN, pFieldId);
634                 throw new PrometheusDataException(this, ERROR_RESOLUTION);
635             }
636             myValues.setValue(pFieldId, myItem);
637         }
638     }
639 
640     /**
641      * Is the item to be included in output XML?
642      *
643      * @param pField the field to check
644      * @return true/false
645      */
646     public boolean includeXmlField(final MetisDataFieldId pField) {
647         return false;
648     }
649 
650     @Override
651     public boolean equals(final Object pThat) {
652         /* Handle the trivial cases */
653         if (this == pThat) {
654             return true;
655         }
656         if (pThat == null) {
657             return false;
658         }
659 
660         /* Make sure that the object is the same class */
661         if (pThat.getClass() != getClass()) {
662             return false;
663         }
664 
665         /* Access the object as a DataItem */
666         final PrometheusDataItem myItem = (PrometheusDataItem) pThat;
667 
668         /* Check the id */
669         if (compareId(myItem) != 0) {
670             return false;
671         }
672 
673         /* Loop through the fields */
674         final Iterator<MetisFieldDef> myIterator = getDataFieldSet().fieldIterator();
675         while (myIterator.hasNext()) {
676             /* Access Field */
677             final MetisFieldDef myField = myIterator.next();
678 
679             /* Skip if not used in equality */
680             if (!(myField instanceof MetisFieldVersionedDef myVersioned)
681                     || !myVersioned.isEquality()) {
682                 continue;
683             }
684 
685             /* Access the values */
686             final Object myValue = myField.getFieldValue(this);
687             final Object myNew = myField.getFieldValue(myItem);
688 
689             /* Check the field */
690             if (!MetisDataDifference.isEqual(myValue, myNew)) {
691                 return false;
692             }
693         }
694 
695         /* Return identical */
696         return true;
697     }
698 
699     @Override
700     public int hashCode() {
701         /* hash code is Id for simplicity */
702         return getIndexedId();
703     }
704 
705     @Override
706     public int compareTo(final Object pThat) {
707         /* Handle the trivial cases */
708         if (this.equals(pThat)) {
709             return 0;
710         }
711         if (pThat == null) {
712             return -1;
713         }
714 
715         /* Non-DataItems are last */
716         if (!(pThat instanceof PrometheusDataItem myThat)) {
717             return -1;
718         }
719 
720         /* Check data type */
721         int iDiff = getItemType().getItemKey() - myThat.getItemType().getItemKey();
722         if (iDiff != 0) {
723             return iDiff;
724         }
725 
726         /* Check values and finally id */
727         iDiff = compareValues(myThat);
728         return iDiff != 0 ? iDiff : compareId(myThat);
729     }
730 
731     /**
732      * compareTo another dataItem.
733      *
734      * @param pThat the DataItem to compare
735      * @return the order
736      */
737     protected abstract int compareValues(PrometheusDataItem pThat);
738 
739     /**
740      * compareTo another dataItem.
741      *
742      * @param pThat the DataItem to compare
743      * @return the order
744      */
745     protected int compareId(final PrometheusDataItem pThat) {
746         return getIndexedId() - pThat.getIndexedId();
747     }
748 
749     /**
750      * Get the state of the underlying record.
751      *
752      * @return the underlying state
753      */
754     protected MetisDataState getBaseState() {
755         final PrometheusDataItem myBase = getBase();
756         return (myBase == null)
757                 ? MetisDataState.NOSTATE
758                 : myBase.getState();
759     }
760 
761     /**
762      * Determine index of element within the list.
763      *
764      * @return The index
765      */
766     public int indexOf() {
767         /* Return index */
768         return theList.indexOf(this);
769     }
770 
771     /**
772      * Apply changes to the item from a changed version. Overwritten by objects that have changes
773      *
774      * @param pElement the changed element.
775      * @return were changes made?
776      */
777     public boolean applyChanges(final PrometheusDataItem pElement) {
778         return false;
779     }
780 
781     /**
782      * Validate the element
783      * <p>
784      * Dirty items become valid.
785      */
786     public void validate() {
787         getList().getValidator().validate(this);
788     }
789 
790     /**
791      * Does the string contain only valid characters (no control chars)?
792      *
793      * @param pString     the string
794      * @param pDisallowed the set of additional disallowed characters
795      * @return true/false
796      */
797     public static boolean validString(final String pString,
798                                       final String pDisallowed) {
799         /* Loop through the string */
800         for (int i = 0; i < pString.length(); i++) {
801             final int myChar = pString.codePointAt(i);
802             /* Check for ISO control */
803             if (Character.isISOControl(myChar)) {
804                 return false;
805             }
806 
807             /* Check for disallowed value */
808             if (pDisallowed != null
809                     && pDisallowed.indexOf(myChar) != -1) {
810                 return false;
811             }
812         }
813         return true;
814     }
815 
816     /**
817      * Obtain the byte length of a string.
818      *
819      * @param pString the string
820      * @return the length
821      */
822     public static int byteLength(final String pString) {
823         return OceanusDataConverter.stringToByteArray(pString).length;
824     }
825 
826     /**
827      * Remove the item (and any subitems from the lists).
828      */
829     public void removeItem() {
830         theList.remove(this);
831     }
832 
833     /**
834      * Obtain the field state.
835      *
836      * @param pField the field
837      * @return the state
838      */
839     public MetisFieldState getFieldState(final MetisDataFieldId pField) {
840         /* Determine DELETED state */
841         if (isDeleted()) {
842             return MetisFieldState.DELETED;
843 
844             /* Determine Error state */
845         } else if (hasErrors() && hasErrors(pField)) {
846             return MetisFieldState.ERROR;
847 
848             /* Determine Changed state */
849         } else if (fieldChanged(pField).isDifferent()) {
850             return MetisFieldState.CHANGED;
851 
852             /* Determine standard states */
853         } else {
854             return switch (getState()) {
855                 case NEW -> MetisFieldState.NEW;
856                 case RECOVERED -> MetisFieldState.RESTORED;
857                 default -> MetisFieldState.NORMAL;
858             };
859         }
860     }
861 
862     /**
863      * Obtain the item State.
864      *
865      * @return the state
866      */
867     public MetisFieldState getItemState() {
868         /* Determine DELETED state */
869         if (isDeleted()) {
870             return MetisFieldState.DELETED;
871 
872             /* Determine Error state */
873         } else if (hasErrors()) {
874             return MetisFieldState.ERROR;
875 
876             /* Determine Changed state */
877         } else if (hasHistory()) {
878             return MetisFieldState.CHANGED;
879 
880             /* Determine standard states */
881         } else {
882             return switch (getState()) {
883                 case NEW -> MetisFieldState.NEW;
884                 case RECOVERED -> MetisFieldState.RESTORED;
885                 default -> MetisFieldState.NORMAL;
886             };
887         }
888     }
889 }