ThemisAnalysisDataMap.java

/*
 * Themis: Java Project 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.themis.lethe.analysis;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisFile.ThemisAnalysisObject;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisGeneric.ThemisAnalysisGenericVar;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisImports.ThemisAnalysisImport;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * The data map manager.
 */
public class ThemisAnalysisDataMap {
    /**
     * Marker interface for dataType.
     */
    interface ThemisAnalysisDataType {
    }

    /**
     * Marker interface for intermediate.
     */
    interface ThemisAnalysisIntermediate extends ThemisAnalysisDataType {
    }

    /**
     * The logger.
     */
    private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(ThemisAnalysisDataMap.class);

    /**
     * The base dataTypes.
     */
    private static final Map<String, ThemisAnalysisDataType> BASETYPES = createDataTypeMap();

    /**
     * The hidden dataTypes.
     */
    private static final Map<String, String> HIDDENTYPES = createHiddenTypeMap();

    /**
     * The local dataTypes.
     */
    private final Map<String, ThemisAnalysisDataType> theLocalTypes;

    /**
     * The map of all classes.
     */
    private final Map<String, ThemisAnalysisObject> theClassMap;

    /**
     * The map of all classes.
     */
    private final Map<String, ThemisAnalysisObject> theShortClassMap;

    /**
     * The map of local/anonymous classes.
     */
    private final Map<String, Integer> theLocalIdMap;

    /**
     * The file dataTypes.
     */
    private Map<String, ThemisAnalysisDataType> theFileTypes;

    /**
     * The list of file classes.
     */
    private List<ThemisAnalysisObject> theFileClasses;

    /**
     * The list of all references.
     */
    private List<ThemisAnalysisReference> theReferences;

    /**
     * The parent.
     */
    private ThemisAnalysisDataMap theParent;

    /**
     * Base constructor.
     */
    ThemisAnalysisDataMap() {
        theParent = null;
        theClassMap = new LinkedHashMap<>();
        theShortClassMap = new LinkedHashMap<>();
        theLocalTypes = new HashMap<>();
        theLocalIdMap = new HashMap<>();
    }

    /**
     * Nested constructor.
     *
     * @param pParent the parent dataMap
     */
    ThemisAnalysisDataMap(final ThemisAnalysisDataMap pParent) {
        theParent = pParent;
        theClassMap = pParent.theClassMap;
        theShortClassMap = pParent.theShortClassMap;
        theLocalTypes = new HashMap<>();
        theFileTypes = pParent.theFileTypes;
        theFileClasses = pParent.theFileClasses;
        theReferences = pParent.theReferences;
        theLocalIdMap = new HashMap<>();
    }

    /**
     * lookUp thedataType.
     *
     * @param pToken  the token.
     * @param pMethod is this a method call/def?
     * @return the dataType (or null)
     */
    ThemisAnalysisDataType lookUpDataType(final String pToken,
                                          final boolean pMethod) {
        /* Look up standard data type */
        ThemisAnalysisDataType myDataType = lookUpTheDataType(pToken);

        /* If it is not found and this is not a method call */
        if (myDataType == null && !pMethod) {
            /* Look for a root dataType */
            final int myIndex = pToken.indexOf(ThemisAnalysisChar.PERIOD);
            if (myIndex != -1) {
                /* Check for child of imported dataType */
                final String myStart = pToken.substring(0, myIndex);
                final String myEnd = pToken.substring(myIndex + 1);
                final ThemisAnalysisDataType myParent = lookUpTheDataType(myStart);

                /* If we have found it */
                if (myParent != null) {
                    myDataType = new ThemisAnalysisDataTypeChild(myParent, myEnd);
                    theFileTypes.put(pToken, myDataType);
                }
            }
        }

        /* Return the dataType */
        return myDataType;
    }

    /**
     * lookUp dataType.
     *
     * @param pToken the token.
     * @return the dataType (or null)
     */
    ThemisAnalysisDataType lookUpTheDataType(final String pToken) {
        /* Look up in local types */
        final ThemisAnalysisDataType myDataType = theLocalTypes.get(pToken);
        if (myDataType != null) {
            return myDataType;
        }

        /* Pass call on */
        return theParent == null
                ? BASETYPES.get(pToken)
                : theParent.lookUpTheDataType(pToken);
    }

    /**
     * Record Object.
     *
     * @param pObject the object.
     * @throws OceanusException on error
     */
    void declareObject(final ThemisAnalysisObject pObject) throws OceanusException {
        /* Access properties */
        final ThemisAnalysisProperties myProps = pObject.getProperties();
        final String myShortName = pObject.getShortName();

        /* Only register class globally if it is non-private */
        if (!myProps.hasModifier(ThemisAnalysisModifier.PRIVATE)) {
            /* Check that the shortName is unique */
            if (theShortClassMap.get(myShortName) != null) {
                throw new ThemisDataException("Duplicate class shortName: " + myShortName);
            }

            /* Register the object */
            theShortClassMap.put(myShortName, pObject);
            theClassMap.put(pObject.getFullName(), pObject);
        }

        /* Store locally */
        theFileTypes.put(myShortName, pObject);
        theFileClasses.add(pObject);
    }

    /**
     * Set up file resources.
     */
    void setUpFileResources() {
        /* Local types are file wide */
        theFileTypes = theLocalTypes;

        /* Allocate the fileClasses */
        theFileClasses = new ArrayList<>();

        /* Allocate the references list */
        theReferences = new ArrayList<>();
    }

    /**
     * Set parent.
     *
     * @param pParent the parent dataMap.
     */
    void setParent(final ThemisAnalysisDataMap pParent) {
        theParent = pParent;
    }

    /**
     * declare file.
     *
     * @param pFile the file
     */
    void declareFile(final ThemisAnalysisFile pFile) {
        theLocalTypes.put(pFile.getName(), pFile);
    }

    /**
     * declare import.
     *
     * @param pImport the import
     */
    void declareImport(final ThemisAnalysisImport pImport) {
        theLocalTypes.put(pImport.getSimpleName(), pImport);
    }

    /**
     * declare generic variable.
     *
     * @param pVar the generic variable
     */
    void declareGenericVar(final ThemisAnalysisGenericVar pVar) {
        theLocalTypes.put(pVar.getName(), pVar);
    }

    /**
     * declare unknown type.
     *
     * @param pName the name
     * @return the new type
     */
    ThemisAnalysisDataType declareUnknown(final String pName) {
        final ThemisAnalysisDataType myType = new ThemisAnalysisDataTypeUnknown(pName);
        theFileTypes.put(pName, myType);
        return myType;
    }

    /**
     * declare reference.
     *
     * @param pRef the reference
     */
    void declareReference(final ThemisAnalysisReference pRef) {
        theReferences.add(pRef);
    }

    /**
     * Consolidate the class map.
     *
     * @throws OceanusException on error
     */
    void consolidateMap() throws OceanusException {
        /* Update intermediate references */
        updateIntermediates();

        /* resolve the immediate ancestors */
        resolveImmediateAncestors();
    }

    /**
     * Update intermediate references from classMap.
     */
    private void updateIntermediates() {
        /* Loop through the localDataTypes */
        for (Entry<String, ThemisAnalysisDataType> myEntry : theLocalTypes.entrySet()) {
            /* Access the value */
            final ThemisAnalysisDataType myType = myEntry.getValue();

            /* If this is an intermediate */
            if (myType instanceof ThemisAnalysisIntermediate myIntermediate) {
                /* Look up actual value */
                final ThemisAnalysisDataType myActual = lookUpActualDataType(myIntermediate);
                if (myActual != null) {
                    myEntry.setValue(myActual);
                }
            }
        }

        /* Loop through the References */
        for (ThemisAnalysisReference myRef : theReferences) {
            /* Access the dataType */
            final ThemisAnalysisDataType myType = myRef.getDataType();

            /* If this is an intermediate */
            if (myType instanceof ThemisAnalysisIntermediate myIntermediate) {
                /* Look up actual value */
                final ThemisAnalysisDataType myActual = lookUpActualDataType(myIntermediate);
                if (myActual != null) {
                    myRef.updateDataType(myActual);
                }
            }
        }
    }

    /**
     * Look up actual dataType.
     *
     * @param pIntermediate the intermediate dataType.
     * @return the actual dataType (or null)
     */
    ThemisAnalysisObject lookUpActualDataType(final ThemisAnalysisIntermediate pIntermediate) {
        /* If this is an import */
        if (pIntermediate instanceof ThemisAnalysisImport myImport) {
            /* Replace it with actual object if known */
            return theClassMap.get(myImport.getFullName());
        }

        /* If this is a file */
        if (pIntermediate instanceof ThemisAnalysisFile myFile) {
            /* Replace it with actual object if known */
            final String myFullName = myFile.getPackageName() + ThemisAnalysisChar.PERIOD + myFile.getName();
            return theClassMap.get(myFullName);
        }

        /* No change */
        return null;
    }

    /**
     * Resolve immediate ancestors.
     *
     * @throws OceanusException on error
     */
    private void resolveImmediateAncestors() throws OceanusException {
        /* Loop through the fileClasses */
        for (ThemisAnalysisObject myClass : theFileClasses) {
            /* Loop through the ancestors of each class */
            for (ThemisAnalysisReference myRef : myClass.getAncestors()) {
                /* Resolve the ancestor */
                resolveAncestor(myRef);
            }
        }
    }

    /**
     * Resolve ancestors.
     *
     * @param pAncestor the ancestor
     * @throws OceanusException on error
     */
    private void resolveAncestor(final ThemisAnalysisReference pAncestor) throws OceanusException {
        /* Only worry about unknown ancestors */
        final ThemisAnalysisDataType myDataType = pAncestor.getDataType();
        if (!(myDataType instanceof ThemisAnalysisDataTypeUnknown)) {
            return;
        }

        /* Look up the shortName */
        final String myName = ((ThemisAnalysisDataTypeUnknown) myDataType).theName;
        final ThemisAnalysisObject myActual = theShortClassMap.get(myName);
        if (myActual == null) {
            throw new ThemisDataException("Unknown ancestor: " + myName);
        }

        /* Update the reference */
        pAncestor.updateDataType(myActual);
    }

    /**
     * Resolve references.
     */
    void resolveReferences() {
        /* Process implicit imports */
        processImplicit();

        /* Loop through the References */
        for (ThemisAnalysisReference myRef : theReferences) {
            /* Access the dataType */
            final ThemisAnalysisDataType myType = myRef.getDataType();

            /* If this is an unknown */
            if (myType instanceof ThemisAnalysisDataTypeUnknown) {
                /* Check for implicit import */
                final ThemisAnalysisDataType myActual = theLocalTypes.get(myType.toString());
                if (!(myActual instanceof ThemisAnalysisDataTypeUnknown)) {
                    myRef.updateDataType(myActual);
                }
            }
        }
    }

    /**
     * Process implicit imports.
     */
    private void processImplicit() {
        /* Loop through the fileClasses */
        for (ThemisAnalysisObject myClass : theFileClasses) {
            /* Process the ancestors */
            processAncestors(myClass);
        }
    }

    /**
     * Process implicit imports.
     *
     * @param pClass the class
     */
    private void processAncestors(final ThemisAnalysisObject pClass) {
        /* Loop through the ancestors */
        for (ThemisAnalysisReference myRef : pClass.getAncestors()) {
            /* Process the ancestor */
            final ThemisAnalysisDataType myDataType = myRef.getDataType();
            if (myDataType instanceof ThemisAnalysisObject myObject) {
                processAncestor(myObject);
                processAncestors(myObject);
            }
        }
    }

    /**
     * Process ancestor.
     *
     * @param pAncestor the ancestor
     */
    private void processAncestor(final ThemisAnalysisObject pAncestor) {
        /* Loop through the fileClasses */
        for (ThemisAnalysisObject myClass : theClassMap.values()) {
            /* If this is a direct child of the ancestor */
            final String myName = pAncestor.getFullName() + ThemisAnalysisChar.PERIOD + myClass.getShortName();
            if (myName.equals(myClass.getFullName())) {
                /* Add it to the local types */
                theLocalTypes.put(myClass.getShortName(), myClass);
            }
        }
    }

    /**
     * Report unknown references.
     */
    void reportUnknown() {
        /* Loop through the localDataTypes */
        for (Entry<String, ThemisAnalysisDataType> myEntry : theLocalTypes.entrySet()) {
            /* Access the value */
            final ThemisAnalysisDataType myType = myEntry.getValue();

            /* If this is an unknown value */
            if (myType instanceof ThemisAnalysisDataTypeUnknown myUnknown) {
                /* Process the unknown reference */
                processUnknown(myEntry, myUnknown);
            }
        }
    }

    /**
     * Process unknown reference.
     *
     * @param pEntry   the entry
     * @param pUnknown the unknown reference
     */
    private void processUnknown(final Entry<String, ThemisAnalysisDataType> pEntry,
                                final ThemisAnalysisDataTypeUnknown pUnknown) {
        /* If this is a hidden child */
        final String myName = pEntry.getKey();
        if (HIDDENTYPES.containsKey(myName)) {
            /* Look up the parent */
            final ThemisAnalysisDataType myParent = theLocalTypes.get(HIDDENTYPES.get(myName));
            if (myParent instanceof ThemisAnalysisImport myImport) {
                /* Register a fake import and return */
                final String myFullName = myImport.getFullName() + ThemisAnalysisChar.PERIOD + myName;
                final ThemisAnalysisImport myChild = new ThemisAnalysisImport(myFullName);
                pEntry.setValue(myChild);
                return;
            }
        }

        /* Report it if we have not rectified the problem */
        LOGGER.info("Unknown: " + pUnknown.toString());
    }

    /**
     * Create the dataTypeMap.
     *
     * @return the new map
     */
    private static Map<String, ThemisAnalysisDataType> createDataTypeMap() {
        /* create the map */
        final Map<String, ThemisAnalysisDataType> myMap = new HashMap<>();

        /* Add the primitives */
        for (ThemisAnalysisPrimitive myPrimitive : ThemisAnalysisPrimitive.values()) {
            myMap.put(myPrimitive.toString(), myPrimitive);
            if (myPrimitive.getBoxed() != null) {
                myMap.put(myPrimitive.getBoxed(), myPrimitive);
            }
        }

        /* Add the java.lang classes */
        for (ThemisAnalysisJavaLang myClass : ThemisAnalysisJavaLang.values()) {
            myMap.put(myClass.toString(), myClass);
        }

        /* return the map */
        return myMap;
    }

    /**
     * Create hidden dataType map.
     *
     * @return the map
     */
    private static Map<String, String> createHiddenTypeMap() {
        final Map<String, String> myMap = new HashMap<>();
        myMap.put("StateChangeNotification", "Preloader");
        myMap.put("SortKey", "RowSorter");
        myMap.put("Entry", "RowFilter");
        return myMap;
    }

    /**
     * Obtain local id.
     *
     * @param pName the name
     * @return the localId
     */
    public int getLocalId(final String pName) {
        final int myId = 1 + theLocalIdMap.computeIfAbsent(pName, s -> 0);
        theLocalIdMap.put(pName, myId);
        return myId;
    }

    /**
     * DataType Unknown.
     */
    public static class ThemisAnalysisDataTypeUnknown
            implements ThemisAnalysisDataType {
        /**
         * The Name.
         */
        private final String theName;

        /**
         * Constructor.
         *
         * @param pName the name
         */
        ThemisAnalysisDataTypeUnknown(final String pName) {
            theName = pName;
        }

        @Override
        public String toString() {
            return theName;
        }
    }

    /**
     * DataType Child.
     */
    public static class ThemisAnalysisDataTypeChild
            implements ThemisAnalysisDataType {
        /**
         * The Name.
         */
        private final ThemisAnalysisDataType theParent;

        /**
         * The Child.
         */
        private final String theChild;

        /**
         * Constructor.
         *
         * @param pParent the parent
         * @param pChild  the child
         */
        ThemisAnalysisDataTypeChild(final ThemisAnalysisDataType pParent,
                                    final String pChild) {
            theParent = pParent;
            theChild = pChild;
        }

        @Override
        public String toString() {
            return theParent.toString() + ThemisAnalysisChar.PERIOD + theChild;
        }
    }
}