PrometheusDataInfoSet.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.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.MetisDataState;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInfoItem.PrometheusDataInfoList;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusStaticDataItem.PrometheusStaticList;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Spliterator;
import java.util.function.Consumer;

/**
 * Representation of an information set extension of a DataItem.
 *
 * @param <T> the data type
 * @author Tony Washer
 */
public abstract class PrometheusDataInfoSet<T extends PrometheusDataInfoItem>
        implements MetisFieldItem, Iterable<T> {
    /**
     * Report fields.
     */
    @SuppressWarnings("rawtypes")
    private static final MetisFieldSet<PrometheusDataInfoSet> FIELD_DEFS = MetisFieldSet.newFieldSet(PrometheusDataInfoSet.class);

    /*
     * FieldIds.
     */
    static {
        FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFO_OWNER, PrometheusDataInfoSet::getOwner);
        FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFOSET_VALUES, PrometheusDataInfoSet::getValues);
    }

    /**
     * The Owner to which this set belongs.
     */
    private final PrometheusDataItem theOwner;

    /**
     * The InfoTypes for the InfoSet.
     */
    private final PrometheusStaticList<?> theTypeList;

    /**
     * The DataInfoList for the InfoSet.
     */
    private final PrometheusDataInfoList<T> theInfoList;

    /**
     * The Map of the DataInfo.
     */
    private final Map<PrometheusDataInfoClass, PrometheusDataInfoItem> theMap;

    /**
     * The class of the entries.
     */
    private final Class<T> theClass;

    /**
     * Constructor.
     *
     * @param pOwner    the Owner to which this Set belongs
     * @param pTypeList the infoTypeList for the set
     * @param pInfoList the infoList for the set
     */
    protected PrometheusDataInfoSet(final PrometheusDataItem pOwner,
                                    final PrometheusStaticList<?> pTypeList,
                                    final PrometheusDataInfoList<T> pInfoList) {
        /* Store the Owner and InfoType List */
        theOwner = pOwner;
        theTypeList = pTypeList;
        theInfoList = pInfoList;
        theClass = theInfoList.getBaseClass();

        /* Create the Map */
        theMap = new HashMap<>();
    }

    @Override
    public String formatObject(final OceanusDataFormatter pFormatter) {
        return this.getDataFieldSet().getName();
    }

    /**
     * Obtain owner.
     *
     * @return the owner
     */
    public PrometheusDataItem getOwner() {
        return theOwner;
    }

    /**
     * Obtain values.
     *
     * @return the values
     */
    private Map<PrometheusDataInfoClass, PrometheusDataInfoItem> getValues() {
        return theMap;
    }

    /**
     * Obtain class iterator for the underlying infoClasses.
     *
     * @return the Iterator
     */
    public abstract Iterator<PrometheusDataInfoClass> classIterator();

    /**
     * Clone the dataInfoSet.
     *
     * @param pSource the InfoSet to clone
     */
    @SuppressWarnings("unchecked")
    protected void cloneTheDataInfoSet(final PrometheusDataInfoSet<T> pSource) {
        /* Clone the InfoSet for each Event in the underlying Map */
        for (Entry<PrometheusDataInfoClass, PrometheusDataInfoItem> myEntry : pSource.theMap.entrySet()) {
            /* Create the new value */
            final PrometheusDataInfoItem myValue = myEntry.getValue();

            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Clone the infoLinkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                final PrometheusDataInfoItem myNew = new PrometheusDataInfoLinkSet<>(theInfoList, mySet);
                theMap.put(myEntry.getKey(), myNew);

                /* else its a standard entry */
            } else {
                /* Add to the map */
                final PrometheusDataInfoItem myNew = theInfoList.addCopyItem(myValue);
                theMap.put(myEntry.getKey(), myNew);
            }
        }
    }

    /**
     * Obtain the value for the infoClass.
     *
     * @param <X>        the infoClass
     * @param pInfoClass the Info Class
     * @param pClass     the Value Class
     * @return the value
     */
    public <X> X getValue(final PrometheusDataInfoClass pInfoClass,
                          final Class<X> pClass) {
        /* Reject if called for LinkSet */
        if (pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Access existing entry */
        final PrometheusDataInfoItem myValue = theMap.get(pInfoClass);

        /* If we have no entry, return null */
        if (myValue == null) {
            return null;
        }

        /* Return the value */
        return myValue.getValue(pClass);
    }

    /**
     * Obtain the list iterator for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @return the iterator
     */
    public List<?> getListValue(final PrometheusDataInfoClass pInfoClass) {
        /* Reject if not called for LinkSet */
        if (!pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Access existing entry */
        final PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(pInfoClass);

        /* Return list if it is available */
        return mySet == null
                ? null
                : mySet.getActive();
    }

    /**
     * Is there active values for the infoClass?
     *
     * @param pInfoClass the info class
     * @return true/false
     */
    public boolean isExisting(final PrometheusDataInfoClass pInfoClass) {
        /* Access the value */
        final PrometheusDataInfoItem myInfo = theMap.get(pInfoClass);
        if (myInfo instanceof PrometheusDataInfoLinkSet) {
            /* Access the info */
            final PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(pInfoClass);
            return mySet.isExisting();
        }

        /* Handle standard info */
        return (myInfo != null) && !myInfo.isDeleted();
    }

    /**
     * Obtain the field value for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @return the value
     */
    public PrometheusEncryptedPair getField(final PrometheusDataInfoClass pInfoClass) {
        /* Reject if called for LinkSet */
        if (pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Access existing entry */
        final PrometheusDataInfoItem myValue = theMap.get(pInfoClass);

        /* If we have no entry, return null */
        if (myValue == null) {
            return null;
        }

        /* Return the field */
        return myValue.getField();
    }

    /**
     * Obtain the field value for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @return the value
     */
    public T getInfo(final PrometheusDataInfoClass pInfoClass) {
        /* Reject if called for LinkSet */
        if (pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Return the info */
        final PrometheusDataInfoItem myInfo = theMap.get(pInfoClass);
        return theClass.cast(myInfo);
    }

    /**
     * link the value for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @param pLink      the link value
     * @throws OceanusException on error
     */
    public void linkValue(final PrometheusDataInfoClass pInfoClass,
                          final PrometheusDataItem pLink) throws OceanusException {
        /* Reject if not called for LinkSet */
        if (!pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Access existing set */
        PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(pInfoClass);

        /* If we have no set, create one */
        if (mySet == null) {
            /* Obtain infoType */
            final PrometheusStaticDataItem myInfoType = theTypeList.findItemByClass(pInfoClass);

            /* Allocate the new set */
            mySet = new PrometheusDataInfoLinkSet<>(theInfoList, theOwner, myInfoType);

            /* Add to the map */
            theMap.put(pInfoClass, mySet);
        }

        /* Locate any existing link */
        T myItem = mySet.getItemForValue(pLink);

        /* If this is a new link */
        if (myItem == null) {
            /* Obtain infoType */
            final PrometheusStaticDataItem myInfoType = theTypeList.findItemByClass(pInfoClass);

            /* Create the entry and add to list */
            myItem = theInfoList.addNewItem(theOwner, myInfoType);
            myItem.setValue(pLink);
            myItem.setNewVersion();

            /* link the item */
            mySet.linkItem(myItem);
            mySet.sortLinks();

            /* else if this is a deleted link */
        } else if (myItem.isDeleted()) {
            /* Restore the value */
            myItem.setDeleted(false);
        }
    }

    /**
     * set the list value for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @param pLinks     the links value
     * @throws OceanusException on error
     */
    public void setListValue(final PrometheusDataInfoClass pInfoClass,
                             final List<? extends PrometheusDataItem> pLinks) throws OceanusException {
        /* Reject if not called for LinkSet */
        if (!pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Access existing set */
        PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(pInfoClass);

        /* If we now have no selected values */
        if (pLinks == null || pLinks.isEmpty()) {
            /* If we have a set */
            if (mySet != null) {
                /* Clear all values in linkSet */
                mySet.clearAllLinks();
                theMap.remove(pInfoClass);
            }

            /* Else we have selected values */
        } else {
            /* If we do not currently have a set */
            if (mySet == null) {
                /* Obtain infoType */
                final PrometheusStaticDataItem myInfoType = theTypeList.findItemByClass(pInfoClass);

                /* Allocate the new set */
                mySet = new PrometheusDataInfoLinkSet<>(theInfoList, theOwner, myInfoType);

                /* Add to the map */
                theMap.put(pInfoClass, mySet);
            }

            /* Clear out unnecessary links */
            mySet.clearUnnecessaryLinks(pLinks);

            /* Add new links */
            mySet.addNewLinks(pLinks);
            mySet.sortLinks();
        }
    }

    /**
     * Obtain the infoLinkSet for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @return the value
     */
    @SuppressWarnings("unchecked")
    protected PrometheusDataInfoLinkSet<T> getInfoLinkSet(final PrometheusDataInfoClass pInfoClass) {
        /* Reject if not called for LinkSet */
        if (!pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Return the info */
        final PrometheusDataInfoItem myInfo = theMap.get(pInfoClass);
        return (PrometheusDataInfoLinkSet<T>) myInfo;
    }

    /**
     * Determine whether a particular field has changed in this edit view.
     *
     * @param pInfoClass the class to test
     * @return <code>true/false</code>
     */
    public MetisDataDifference fieldChanged(final PrometheusDataInfoClass pInfoClass) {
        /* If this is called for LinkSet */
        if (pInfoClass.isLinkSet()) {
            /* Access the info */
            final PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(pInfoClass);
            return mySet == null
                    ? MetisDataDifference.IDENTICAL
                    : mySet.fieldChanged();
        }

        /* Access the info */
        final T myInfo = getInfo(pInfoClass);

        /* Return change details */
        if (myInfo == null) {
            return MetisDataDifference.DIFFERENT;
        }
        return myInfo.hasHistory()
                || MetisDataState.NEW.equals(myInfo.getState())
                ? MetisDataDifference.DIFFERENT
                : MetisDataDifference.IDENTICAL;
    }

    /**
     * Set the value for the infoClass.
     *
     * @param pInfoClass the Info Class
     * @param pValue     the Value
     * @throws OceanusException on error
     */
    public void setValue(final PrometheusDataInfoClass pInfoClass,
                         final Object pValue) throws OceanusException {
        /* Reject if called for LinkSet */
        if (pInfoClass.isLinkSet()) {
            throw new UnsupportedOperationException();
        }

        /* Determine whether this is a deletion */
        final boolean bDelete = pValue == null;

        /* Obtain the Map value */
        PrometheusDataInfoItem myValue = theMap.get(pInfoClass);

        /* If we are deleting */
        if (bDelete) {
            /* Mark existing value as deleted */
            if (myValue != null) {
                myValue.markDeleted();
            }

            /* Return to caller */
            return;
        }

        /* If we have no entry */
        if (myValue == null) {
            /* Obtain infoType */
            final PrometheusStaticDataItem myInfoType = theTypeList.findItemByClass(pInfoClass);

            /* Create the entry and add to list */
            myValue = theInfoList.addNewItem(theOwner, myInfoType);
            myValue.setNewVersion();

            /* Insert the value into the map */
            theMap.put(pInfoClass, myValue);
        }

        /* Update the value */
        myValue.setValue(pValue);
    }

    /**
     * Sort linkSets.
     */
    @SuppressWarnings("unchecked")
    public void sortLinkSets() {
        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                mySet.sortLinks();
            }
        }
    }

    /**
     * Register Info.
     *
     * @param pInfo the info to register
     * @throws OceanusException on error
     */
    public void registerInfo(final T pInfo) throws OceanusException {
        /* Obtain the existing Map value */
        final PrometheusDataInfoClass myClass = pInfo.getInfoClass();

        /* If this is an instance of a LinkSet */
        if (myClass.isLinkSet()) {
            /* Access existing entry */
            PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(myClass);
            if (mySet == null) {
                /* Allocate the new set */
                mySet = new PrometheusDataInfoLinkSet<>(theInfoList, theOwner, pInfo.getInfoType());

                /* Add to the map */
                theMap.put(myClass, mySet);
            }

            /* link the item */
            mySet.linkItem(pInfo);

            /* else it is a standard item */
        } else {
            /* Access existing entry */
            final PrometheusDataInfoItem myValue = theMap.get(myClass);

            /* Reject if duplicate and not re-registration */
            if ((myValue != null) && !myValue.getIndexedId().equals(pInfo.getIndexedId())) {
                throw new IllegalArgumentException("Duplicate information type " + pInfo.getInfoClass());
            }

            /* Add to the map */
            theMap.put(myClass, pInfo);
        }
    }

    /**
     * deRegister Info.
     *
     * @param pInfo the info to deRegister
     */
    public void deRegisterInfo(final T pInfo) {
        /* Obtain the existing Map value */
        final PrometheusDataInfoClass myClass = pInfo.getInfoClass();

        /* If this is an instance of a LinkSet */
        if (myClass.isLinkSet()) {
            /* Access existing entry */
            final PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(myClass);
            if (mySet != null) {
                /* Unlink the item */
                mySet.unlinkItem(pInfo);

                /* If the set is now empty */
                if (mySet.isEmpty()) {
                    /* Remove the set from the map */
                    theMap.remove(myClass);
                }
            }

            /* else it is a standard info */
        } else {
            /* Remove from the map */
            theMap.remove(myClass);
        }
    }

    /**
     * deRegister Info.
     *
     * @param pInfo the info to deRegister
     */
    public void rewindInfoLinkSet(final T pInfo) {
        /* Obtain the existing Map value */
        final PrometheusDataInfoClass myClass = pInfo.getInfoClass();

        /* If this is an instance of a LinkSet */
        if (myClass.isLinkSet()) {
            /* Access existing entry */
            final PrometheusDataInfoLinkSet<T> mySet = getInfoLinkSet(myClass);
            if (mySet != null) {
                /* reSort the links */
                mySet.sortLinks();
            } else {
                /* Remove the set from the map */
                theMap.remove(myClass);
            }

            /* illegal operation */
        } else {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * wipe information regarding the infoClass.
     *
     * @param pInfoClass the Info Class
     */
    public void wipeInfo(final PrometheusDataInfoClass pInfoClass) {
        /* If we have an item for the class */
        final PrometheusDataInfoItem myInfo = theMap.get(pInfoClass);
        if (myInfo != null) {
            /* Remove and unlink it */
            theMap.remove(pInfoClass);
            theInfoList.remove(myInfo);
        }
    }

    /**
     * touch underlying items.
     */
    @SuppressWarnings("unchecked")
    public void touchUnderlyingItems() {
        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                mySet.touchUnderlyingItems();

                /* else this is a standard DataInfo */
            } else {
                /* Touch the underlying items */
                myValue.touchUnderlyingItems();
            }
        }
    }

    /**
     * touch underlying items after update.
     */
    @SuppressWarnings("unchecked")
    public void touchOnUpdate() {
        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                mySet.touchOnUpdate();

                /* else this is a standard DataInfo */
            } else {
                /* Touch the underlying items */
                myValue.touchOnUpdate();
            }
        }
    }

    /**
     * Determine whether the set has changes.
     *
     * @return <code>true/false</code>
     */
    @SuppressWarnings("unchecked")
    public boolean hasHistory() {
        boolean bChanges = false;

        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                bChanges |= mySet.hasHistory();

            } else {
                /* Check for history */
                bChanges |= myValue.hasHistory();
            }
        }

        /* return result */
        return bChanges;
    }

    /**
     * Push history.
     */
    @SuppressWarnings("unchecked")
    public void pushHistory() {
        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                mySet.pushHistory();

            } else {
                /* Push history for the value */
                myValue.pushHistory();
            }
        }
    }

    /**
     * Pop history.
     */
    @SuppressWarnings("unchecked")
    public void popHistory() {
        /* Iterate through table values */
        final Iterator<PrometheusDataInfoItem> myIterator = theMap.values().iterator();
        while (myIterator.hasNext()) {
            final PrometheusDataInfoItem myValue = myIterator.next();

            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                mySet.popHistory();

                /* If the set is now empty */
                if (mySet.isEmpty()) {
                    /* Remove the value */
                    myIterator.remove();
                }

                /* else this is a standard element */
            } else {
                /* If the entry should be removed */
                if (myValue.getOriginalValues().getVersion() > theInfoList.getVersion()) {
                    /* Remove the value */
                    myIterator.remove();
                    myValue.unLink();
                    continue;
                }

                /* Pop the value */
                myValue.popHistory();
            }
        }
    }

    /**
     * Check for history.
     *
     * @return <code>true</code> if changes were made, <code>false</code> otherwise
     */
    @SuppressWarnings("unchecked")
    public boolean checkForHistory() {
        /* Loop through each existing value */
        boolean bChanges = false;
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myValue;
                bChanges |= mySet.checkForHistory();

            } else {
                /* If this is a newly created item */
                if (!myValue.hasHistory()) {
                    bChanges = true;

                    /* else existing entry */
                } else {
                    /* Check for history */
                    bChanges |= myValue.checkForHistory();
                }
            }
        }

        /* return result */
        return bChanges;
    }

    /**
     * Set values as deleted/restored.
     *
     * @param bDeleted <code>true/false</code>
     */
    public void setDeleted(final boolean bDeleted) {
        /* If we are restoring */
        if (!bDeleted) {
            /* Handle separately */
            setRestored();
            return;
        }

        /* For each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If the value is active */
            if (!myValue.isDeleted()) {
                /* Set the value as deleted */
                myValue.setDeleted(true);
            }
        }
    }

    /**
     * Restore values that we deleted at the same time as the owner.
     */
    private void setRestored() {
        /* Access the version of the owner */
        int myVersion = theOwner.getValueSetVersion();

        /* We are restoring an edit version if delete was in this session */
        final boolean bEditRestore = myVersion > 0;
        if (!bEditRestore) {
            /* Access underlying version if not editRestore */
            myVersion = theOwner.getBase().getValueSetVersion();
        }

        /* For each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If this is an instance of a LinkSet */
            if (myValue instanceof PrometheusDataInfoLinkSet) {
                /* Pass call to linkSet */
                myValue.setDeleted(false);

            } else {
                /* Access version of value */
                final int myValueVersion = bEditRestore
                        ? myValue.getValueSetVersion()
                        : myValue.getBase().getValueSetVersion();

                /* If the value was deleted at same time as owner */
                if (myValueVersion == myVersion) {
                    /* Set the value as restored */
                    myValue.setDeleted(false);
                }
            }
        }
    }

    /**
     * Get the EditState for this item.
     *
     * @return the EditState
     */
    public MetisDataEditState getEditState() {
        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If we have changes */
            if (myValue.hasHistory()) {
                /* Note that new state is changed */
                return MetisDataEditState.VALID;
            }
        }

        /* Default to clean */
        return MetisDataEditState.CLEAN;
    }

    /**
     * Get the State for this infoSet.
     *
     * @return the State
     */
    public MetisDataState getState() {
        /* Default to clean */
        final MetisDataState myState = MetisDataState.CLEAN;

        /* Loop through each existing value */
        for (PrometheusDataInfoItem myValue : theMap.values()) {
            /* If we have changes */
            if (myValue.getState() != MetisDataState.CLEAN) {
                /* Note that new state is changed */
                return MetisDataState.CHANGED;
            }
        }

        /* return result */
        return myState;
    }

    @Override
    public Iterator<T> iterator() {
        return new InfoIterator();
    }

    @Override
    public void forEach(final Consumer<? super T> pAction) {
        final Iterator<T> myIterator = iterator();
        while (myIterator.hasNext()) {
            pAction.accept(myIterator.next());
        }
    }

    @Override
    public Spliterator<T> spliterator() {
        throw new UnsupportedOperationException();
    }

    /**
     * Is the infoSet empty?
     *
     * @return true/false.
     */
    public boolean isEmpty() {
        return theMap.isEmpty();
    }

    /**
     * Remove items.
     */
    public void removeItems() {
        final InfoIterator myIterator = new InfoIterator();
        while (myIterator.hasNext()) {
            final T myItem = myIterator.next();
            myItem.removeItem();
        }
    }

    /**
     * Iterator class.
     */
    private final class InfoIterator
            implements Iterator<T> {
        /**
         * Overall iterator.
         */
        private final Iterator<PrometheusDataInfoItem> theIterator;

        /**
         * Local iterator.
         */
        private Iterator<T> theSetIterator;

        /**
         * Constructor.
         */
        private InfoIterator() {
            /* Allocate iterator */
            theIterator = theMap.values().iterator();
        }

        @Override
        public boolean hasNext() {
            /* If we have a set iterator with more to go */
            if (theSetIterator != null
                    && theSetIterator.hasNext()) {
                /* We have a next item */
                return true;
            }

            /* No longer have a valid setIterator */
            theSetIterator = null;

            /* Check for next entry */
            return theIterator.hasNext();
        }

        @Override
        @SuppressWarnings("unchecked")
        public T next() {
            /* If we have a set iterator with more to go */
            if (theSetIterator != null
                    && theSetIterator.hasNext()) {
                /* Return the item */
                return theSetIterator.next();
            }

            /* No longer have a valid setIterator */
            theSetIterator = null;

            /* Obtain the next item */
            final PrometheusDataInfoItem myItem = theIterator.next();

            /* If this is an infoLinkSet */
            if (myItem instanceof PrometheusDataInfoLinkSet) {
                /* Access set iterator and return first element */
                final PrometheusDataInfoLinkSet<T> mySet = (PrometheusDataInfoLinkSet<T>) myItem;
                theSetIterator = mySet.iterator();
                return theSetIterator.next();
            }

            /* Return the item */
            return theClass.cast(myItem);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(final Consumer<? super T> pAction) {
            while (hasNext()) {
                pAction.accept(next());
            }
        }
    }

    /**
     * Obtain the field for the infoSet class.
     *
     * @param pClass the class
     * @return the field
     */
    public abstract MetisDataFieldId getFieldForClass(PrometheusDataInfoClass pClass);
}