PrometheusDataItem.java
/*
* Prometheus: Application Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.prometheus.data;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.convert.OceanusDataConverter;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
import io.github.tonywasher.joceanus.metis.data.MetisDataState;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.metis.field.MetisFieldState;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionValues;
import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedItem;
import io.github.tonywasher.joceanus.metis.list.MetisListKey;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList.PrometheusListStyle;
import io.github.tonywasher.joceanus.prometheus.exc.PrometheusDataException;
import java.util.Iterator;
/**
* Provides the abstract DataItem class as the basis for data items. The implementation of the
* interface means that this object can only be held in one list at a time and is unique within that
* list
*
* @see PrometheusDataList
*/
public abstract class PrometheusDataItem
extends MetisFieldVersionedItem
implements PrometheusTableItem, Comparable<Object> {
/**
* Report fields.
*/
private static final MetisFieldSet<PrometheusDataItem> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusDataItem.class);
/*
* FieldIds.
*/
static {
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATALIST_NAME, PrometheusDataItem::getList);
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_BASE, PrometheusDataItem::getBase);
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_TOUCH, PrometheusDataItem::getTouchStatus);
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_HEADER, PrometheusDataItem::isHeader);
}
/**
* Validation error.
*/
public static final String ERROR_VALIDATION = PrometheusDataResource.DATAITEM_ERROR_VALIDATION.getValue();
/**
* Resolution error.
*/
public static final String ERROR_RESOLUTION = PrometheusDataResource.DATAITEM_ERROR_RESOLUTION.getValue();
/**
* Duplicate Id error.
*/
public static final String ERROR_DUPLICATE = PrometheusDataResource.DATAITEM_ERROR_DUPLICATE.getValue();
/**
* Unknown Id error.
*/
public static final String ERROR_UNKNOWN = PrometheusDataResource.DATAITEM_ERROR_UNKNOWN.getValue();
/**
* Existing value error.
*/
public static final String ERROR_EXIST = PrometheusDataResource.DATAITEM_ERROR_EXIST.getValue();
/**
* Missing value error.
*/
public static final String ERROR_MISSING = PrometheusDataResource.DATAITEM_ERROR_MISSING.getValue();
/**
* Value too long error.
*/
public static final String ERROR_LENGTH = PrometheusDataResource.DATAITEM_ERROR_LENGTH.getValue();
/**
* Value negative error.
*/
public static final String ERROR_NEGATIVE = PrometheusDataResource.DATAITEM_ERROR_NEGATIVE.getValue();
/**
* Value positive error.
*/
public static final String ERROR_POSITIVE = PrometheusDataResource.DATAITEM_ERROR_POSITIVE.getValue();
/**
* Value zero error.
*/
public static final String ERROR_ZERO = PrometheusDataResource.DATAITEM_ERROR_ZERO.getValue();
/**
* Value outside valid range.
*/
public static final String ERROR_RANGE = PrometheusDataResource.DATAITEM_ERROR_RANGE.getValue();
/**
* Value disabled error.
*/
public static final String ERROR_DISABLED = PrometheusDataResource.DATAITEM_ERROR_DISABLED.getValue();
/**
* Creation failure.
*/
public static final String ERROR_CREATEITEM = PrometheusDataResource.DATAITEM_ERROR_CREATE.getValue();
/**
* Multiple instances Error.
*/
public static final String ERROR_MULT = PrometheusDataResource.DATAITEM_ERROR_MULTIPLE.getValue();
/**
* Reserved name error.
*/
public static final String ERROR_INVALIDCHAR = PrometheusDataResource.DATAITEM_ERROR_INVALIDCHAR.getValue();
/**
* Standard Name length.
*/
public static final int NAMELEN = 30;
/**
* Standard Description length.
*/
public static final int DESCLEN = 50;
/**
* The list to which this item belongs.
*/
private PrometheusDataList<?> theList;
/**
* The item that this DataItem is based upon.
*/
private PrometheusDataItem theBase;
/**
* Is the item a header.
*/
private boolean isHeader;
/**
* Status.
*/
private final PrometheusDataTouch theTouchStatus;
/**
* Construct a new item.
*
* @param pList the list that this item is associated with
* @param uId the Id of the new item (or 0 if not yet known)
*/
protected PrometheusDataItem(final PrometheusDataList<?> pList,
final Integer uId) {
/* Record list and item references */
setIndexedId(uId);
theList = pList;
/* Allocate id */
pList.setNewId(this);
/* Create the touch status */
theTouchStatus = new PrometheusDataTouch();
}
/**
* Construct a new item.
*
* @param pList the list that this item is associated with
* @param pValues the data values
*/
protected PrometheusDataItem(final PrometheusDataList<?> pList,
final PrometheusDataValues pValues) {
/* Record list and item references */
this(pList, pValues.getValue(MetisDataResource.DATA_ID, Integer.class));
}
/**
* Construct a new item based on an old item.
*
* @param pList the list that this item is associated with
* @param pBase the old item
*/
protected PrometheusDataItem(final PrometheusDataList<?> pList,
final PrometheusDataItem pBase) {
/* Initialise using standard constructor */
this(pList, pBase.getIndexedId());
/* Initialise the valueSet */
getValues().copyFrom(pBase.getValues());
/* Access the varying styles and the source state */
final PrometheusListStyle myStyle = pList.getStyle();
final PrometheusListStyle myBaseStyle = pBase.getList().getStyle();
final MetisDataState myState = pBase.getState();
/* Switch on the styles */
switch (myStyle) {
/* We are building an update list (from Core) */
case UPDATE:
switch (myState) {
/* NEW/DELNEW need to be at version 1 */
case DELNEW:
case NEW:
getValues().setVersion(1);
break;
case DELETED:
getValuesHistory().pushHistory(1);
break;
/*
* Changed items need to have new values at version 1 and originals at version 0
*/
case CHANGED:
setHistory(pBase);
break;
/* No change for other states */
default:
break;
}
/* Record the base item */
theBase = pBase;
break;
/* We are building an edit item (from Core/Edit) */
case EDIT:
/* Switch on the base style */
switch (myBaseStyle) {
/* New item from core we need to link back and copy flags */
case CORE:
theBase = pBase;
copyFlags(pBase);
break;
/* Duplication in edit */
case EDIT:
/* set as a new item */
getValues().setVersion(pList.getVersion() + 1);
/* Reset the Id */
setIndexedId(0);
pList.setNewId(this);
break;
default:
break;
}
break;
/* We are building a CORE item */
case CORE:
/* set as a new item */
getValues().setVersion(pList.getVersion() + 1);
/* If we are adding from Edit */
if (myBaseStyle == PrometheusListStyle.EDIT) {
/* Reset the Id */
setIndexedId(0);
pList.setNewId(this);
}
break;
/* Creation of copy element not allowed */
case COPY:
throw new IllegalArgumentException("Illegal creation of COPY element");
/* Nothing special for other styles */
case CLONE:
case DIFFER:
default:
break;
}
}
/**
* Obtain valueSet version.
*
* @return the valueSet version
*/
public int getValueSetVersion() {
return getValues().getVersion();
}
@Override
public String formatObject(final OceanusDataFormatter pFormatter) {
return this.getDataFieldSet().getName();
}
/**
* Obtain the list.
*
* @return the list
*/
public PrometheusDataList<?> getList() {
return theList;
}
/**
* Obtain the dataSet.
*
* @return the dataSet
*/
public PrometheusDataSet getDataSet() {
return getTheDataSet();
}
/**
* Obtain the dataSet.
*
* @return the dataSet
*/
private PrometheusDataSet getTheDataSet() {
return theList.getDataSet();
}
/**
* Get the list style for this item.
*
* @return the list style
*/
public PrometheusListStyle getStyle() {
return theList.getStyle();
}
@Override
public MetisListKey getItemType() {
return theList.getItemType();
}
@Override
public boolean isActive() {
return theTouchStatus.isActive();
}
/**
* Is the item disabled?
*
* @return true/false
*/
public boolean isDisabled() {
return false;
}
/**
* Obtain the touchStatus.
*
* @return the touch status
*/
public PrometheusDataTouch getTouchStatus() {
return theTouchStatus;
}
@Override
public boolean isEditable() {
return !isDeleted();
}
@Override
public boolean isHeader() {
return isHeader;
}
/**
* Set the header indication.
*
* @param pHeader true/false
*/
protected void setHeader(final boolean pHeader) {
isHeader = pHeader;
}
/**
* Determine whether the item is locked (overridden if required).
*
* @return <code>true/false</code>
*/
public boolean isLocked() {
return false;
}
/**
* Determine whether the list is locked (overridden if required).
*
* @return <code>true/false</code>
*/
public boolean isListLocked() {
return false;
}
/**
* DeRegister any infoSet links.
*/
public void deRegister() {
}
/**
* Clear the touch status flag.
*/
public void clearActive() {
theTouchStatus.resetTouches();
}
/**
* Clear the item touches.
*
* @param pItemType the item type
*/
public void clearTouches(final MetisListKey pItemType) {
theTouchStatus.resetTouches(pItemType);
}
/**
* Touch the item.
*
* @param pObject object that references the item
*/
public void touchItem(final PrometheusDataItem pObject) {
theTouchStatus.touchItem(pObject.getItemType());
}
/**
* Touch underlying items that are referenced by this item.
*/
public void touchUnderlyingItems() {
}
/**
* Adjust touches on update.
*/
public void touchOnUpdate() {
}
/**
* Adjust map for this item.
*/
public void adjustMapForItem() {
}
/**
* update Maps.
*/
public void updateMaps() {
/* Clear active flag and touch underlying items */
clearActive();
touchUnderlyingItems();
/* Adjust the map for this item */
adjustMapForItem();
}
/**
* Get the base item for this item.
*
* @return the Base item or <code>null</code>
*/
public PrometheusDataItem getBase() {
return theBase;
}
/**
* Set the base item for this item.
*
* @param pBase the Base item
*/
public void setBase(final PrometheusDataItem pBase) {
theBase = pBase;
}
/**
* Unlink the item from the list.
*/
public void unLink() {
theList.remove(this);
}
/**
* Set new version.
*/
public void setNewVersion() {
getValues().setVersion(getNextVersion());
}
@Override
public int getNextVersion() {
return theList.getVersion() + 1;
}
@Override
public void popHistory() {
rewindToVersion(theList.getVersion());
}
@Override
public void rewindToVersion(final int pVersion) {
/* If the item was newly created */
if (getOriginalValues().getVersion() > pVersion) {
/* Remove from list */
unLink();
deRegister();
/* Return */
return;
}
/* Loop while version is too high */
while (getValues().getVersion() > pVersion) {
/* Pop history */
getValuesHistory().popTheHistory();
}
}
/**
* Set Change history for an update list so that the first and only entry in the change list is
* the original values of the base.
*
* @param pBase the base item
*/
public final void setHistory(final PrometheusDataItem pBase) {
getValuesHistory().setHistory(pBase.getOriginalValues());
}
@Override
public MetisDataDifference fieldChanged(final MetisFieldDef pField) {
return pField instanceof MetisFieldVersionedDef
? getValuesHistory().fieldChanged(pField)
: MetisDataDifference.IDENTICAL;
}
/**
* Note that this item has been validated.
*/
public void setValidEdit() {
final MetisDataState myState = getState();
if (myState == MetisDataState.CLEAN) {
setEditState(MetisDataEditState.CLEAN);
} else if (theList.getStyle() == PrometheusListStyle.CORE) {
setEditState(MetisDataEditState.DIRTY);
} else {
setEditState(MetisDataEditState.VALID);
}
}
@Override
public void addError(final String pError,
final MetisDataFieldId pField) {
/* Set edit state and add the error */
super.addError(pError, pField);
/* Note that the list has errors */
theList.setEditState(MetisDataEditState.ERROR);
}
/**
* Copy flags.
*
* @param pItem the original item
*/
private void copyFlags(final PrometheusDataItem pItem) {
theTouchStatus.copyMap(pItem.theTouchStatus);
}
/**
* Resolve all references to current dataSet.
*
* @throws OceanusException on error
*/
public void resolveDataSetLinks() throws OceanusException {
}
/**
* Resolve a data link into a list.
*
* @param pFieldId the fieldId to resolve
* @param pList the list to resolve against
* @throws OceanusException on error
*/
protected void resolveDataLink(final MetisDataFieldId pFieldId,
final PrometheusDataList<?> pList) throws OceanusException {
/* Access the values */
final MetisFieldVersionValues myValues = getValues();
/* Access value for field */
Object myValue = myValues.getValue(pFieldId);
/* Convert dataItem reference to Id */
if (myValue instanceof PrometheusDataItem myItem) {
myValue = myItem.getIndexedId();
}
/* Lookup Id reference */
if (myValue instanceof Integer i) {
final PrometheusDataItem myItem = pList.findItemById(i);
if (myItem == null) {
addError(ERROR_UNKNOWN, pFieldId);
throw new PrometheusDataException(this, ERROR_RESOLUTION);
}
myValues.setValue(pFieldId, myItem);
/* Lookup Name reference */
} else if (myValue instanceof String s) {
final PrometheusDataItem myItem = pList.findItemByName(s);
if (myItem == null) {
addError(ERROR_UNKNOWN, pFieldId);
throw new PrometheusDataException(this, ERROR_RESOLUTION);
}
myValues.setValue(pFieldId, myItem);
}
}
/**
* Is the item to be included in output XML?
*
* @param pField the field to check
* @return true/false
*/
public boolean includeXmlField(final MetisDataFieldId pField) {
return false;
}
@Override
public boolean equals(final Object pThat) {
/* Handle the trivial cases */
if (this == pThat) {
return true;
}
if (pThat == null) {
return false;
}
/* Make sure that the object is the same class */
if (pThat.getClass() != getClass()) {
return false;
}
/* Access the object as a DataItem */
final PrometheusDataItem myItem = (PrometheusDataItem) pThat;
/* Check the id */
if (compareId(myItem) != 0) {
return false;
}
/* Loop through the fields */
final Iterator<MetisFieldDef> myIterator = getDataFieldSet().fieldIterator();
while (myIterator.hasNext()) {
/* Access Field */
final MetisFieldDef myField = myIterator.next();
/* Skip if not used in equality */
if (!(myField instanceof MetisFieldVersionedDef myVersioned)
|| !myVersioned.isEquality()) {
continue;
}
/* Access the values */
final Object myValue = myField.getFieldValue(this);
final Object myNew = myField.getFieldValue(myItem);
/* Check the field */
if (!MetisDataDifference.isEqual(myValue, myNew)) {
return false;
}
}
/* Return identical */
return true;
}
@Override
public int hashCode() {
/* hash code is Id for simplicity */
return getIndexedId();
}
@Override
public int compareTo(final Object pThat) {
/* Handle the trivial cases */
if (this.equals(pThat)) {
return 0;
}
if (pThat == null) {
return -1;
}
/* Non-DataItems are last */
if (!(pThat instanceof PrometheusDataItem)) {
return -1;
}
/* Check data type */
final PrometheusDataItem myThat = (PrometheusDataItem) pThat;
int iDiff = getItemType().getItemKey() - myThat.getItemType().getItemKey();
if (iDiff != 0) {
return iDiff;
}
/* Check values and finally id */
iDiff = compareValues(myThat);
return iDiff != 0 ? iDiff : compareId(myThat);
}
/**
* compareTo another dataItem.
*
* @param pThat the DataItem to compare
* @return the order
*/
protected abstract int compareValues(PrometheusDataItem pThat);
/**
* compareTo another dataItem.
*
* @param pThat the DataItem to compare
* @return the order
*/
protected int compareId(final PrometheusDataItem pThat) {
return getIndexedId() - pThat.getIndexedId();
}
/**
* Get the state of the underlying record.
*
* @return the underlying state
*/
protected MetisDataState getBaseState() {
final PrometheusDataItem myBase = getBase();
return (myBase == null)
? MetisDataState.NOSTATE
: myBase.getState();
}
/**
* Determine index of element within the list.
*
* @return The index
*/
public int indexOf() {
/* Return index */
return theList.indexOf(this);
}
/**
* Apply changes to the item from a changed version. Overwritten by objects that have changes
*
* @param pElement the changed element.
* @return were changes made?
*/
public boolean applyChanges(final PrometheusDataItem pElement) {
return false;
}
/**
* Validate the element
* <p>
* Dirty items become valid.
*/
public void validate() {
getList().getValidator().validate(this);
}
/**
* Does the string contain only valid characters (no control chars)?
*
* @param pString the string
* @param pDisallowed the set of additional disallowed characters
* @return true/false
*/
public static boolean validString(final String pString,
final String pDisallowed) {
/* Loop through the string */
for (int i = 0; i < pString.length(); i++) {
final int myChar = pString.codePointAt(i);
/* Check for ISO control */
if (Character.isISOControl(myChar)) {
return false;
}
/* Check for disallowed value */
if (pDisallowed != null
&& pDisallowed.indexOf(myChar) != -1) {
return false;
}
}
return true;
}
/**
* Obtain the byte length of a string.
*
* @param pString the string
* @return the length
*/
public static int byteLength(final String pString) {
return OceanusDataConverter.stringToByteArray(pString).length;
}
/**
* Remove the item (and any subitems from the lists).
*/
public void removeItem() {
theList.remove(this);
}
/**
* Obtain the field state.
*
* @param pField the field
* @return the state
*/
public MetisFieldState getFieldState(final MetisDataFieldId pField) {
/* Determine DELETED state */
if (isDeleted()) {
return MetisFieldState.DELETED;
/* Determine Error state */
} else if (hasErrors() && hasErrors(pField)) {
return MetisFieldState.ERROR;
/* Determine Changed state */
} else if (fieldChanged(pField).isDifferent()) {
return MetisFieldState.CHANGED;
/* Determine standard states */
} else {
switch (getState()) {
case NEW:
return MetisFieldState.NEW;
case RECOVERED:
return MetisFieldState.RESTORED;
default:
return MetisFieldState.NORMAL;
}
}
}
/**
* Obtain the item State.
*
* @return the state
*/
public MetisFieldState getItemState() {
/* Determine DELETED state */
if (isDeleted()) {
return MetisFieldState.DELETED;
/* Determine Error state */
} else if (hasErrors()) {
return MetisFieldState.ERROR;
/* Determine Changed state */
} else if (hasHistory()) {
return MetisFieldState.CHANGED;
/* Determine standard states */
} else {
switch (getState()) {
case NEW:
return MetisFieldState.NEW;
case RECOVERED:
return MetisFieldState.RESTORED;
default:
return MetisFieldState.NORMAL;
}
}
}
}