ThemisMapperProjectState.java

/*
 * Themis: Java Project Framework
 * Copyright 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.solver.mapper;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisClassInstance;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisInstance.ThemisNodeInstance;
import io.github.tonywasher.joceanus.themis.parser.node.ThemisNodeImport;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverClass;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverFile;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverModule;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverPackage;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverProject;
import io.github.tonywasher.joceanus.themis.solver.reflect.ThemisReflectExternal;
import io.github.tonywasher.joceanus.themis.solver.reflect.ThemisReflectJar;

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

/**
 * Project State.
 */
public class ThemisMapperProjectState
        implements AutoCloseable {
    /**
     * Map of all classes defined in the project.
     */
    private final Map<String, ThemisSolverClass> theProjectClasses;

    /**
     * Map of all external classes referenced in the project.
     */
    private final Map<String, ThemisReflectExternal> theExternalClasses;

    /**
     * The Jar parser.
     */
    private final ThemisReflectJar theJar;

    /**
     * Constructor.
     *
     * @param pProject the project
     * @throws OceanusException on error
     */
    ThemisMapperProjectState(final ThemisSolverProject pProject) throws OceanusException {
        /* build the project classMap */
        theProjectClasses = new LinkedHashMap<>();
        buildProjectClassMap(pProject);

        /* build the external classMap */
        theExternalClasses = new LinkedHashMap<>();
        buildExternalClassMap(pProject);

        /* Process external classes */
        theJar = new ThemisReflectJar(pProject.getProjectParser(), theExternalClasses);
        theJar.processExternalClasses();
    }

    /**
     * Obtain the project classes.
     *
     * @return the project classes.
     */
    Map<String, ThemisSolverClass> getProjectClassMap() {
        return theProjectClasses;
    }

    /**
     * Obtain the external classes.
     *
     * @return the external classes.
     */
    Map<String, ThemisReflectExternal> getExternalClassMap() {
        return theExternalClasses;
    }

    /**
     * Build project classMap.
     *
     * @param pProject the project
     */
    private void buildProjectClassMap(final ThemisSolverProject pProject) {
        /* Loop through all modules */
        for (ThemisSolverModule myModule : pProject.getModules()) {
            /* Loop through all packages */
            for (ThemisSolverPackage myPackage : myModule.getPackages().values()) {
                buildProjectClassMap(myPackage);
            }
        }
    }

    /**
     * Build project classMap.
     *
     * @param pPackage the package
     */
    private void buildProjectClassMap(final ThemisSolverPackage pPackage) {
        /* Loop through all files */
        for (ThemisSolverFile myFile : pPackage.getFiles()) {
            /* Loop through all classes */
            for (ThemisSolverClass myClass : myFile.getClasses()) {
                theProjectClasses.put(myClass.getFullName(), myClass);
            }
        }
    }

    /**
     * Build external classMap.
     *
     * @param pProject the project
     */
    private void buildExternalClassMap(final ThemisSolverProject pProject) {
        /* Loop through all modules */
        for (ThemisSolverModule myModule : pProject.getModules()) {
            /* Loop through all packages */
            for (ThemisSolverPackage myPackage : myModule.getPackages().values()) {
                buildExternalClassMap(myPackage);
            }
        }
    }

    /**
     * Build external classMap.
     *
     * @param pPackage the package to process.
     */
    private void buildExternalClassMap(final ThemisSolverPackage pPackage) {
        /* Loop through all files */
        for (ThemisSolverFile myFile : pPackage.getFiles()) {
            /* Process the imports */
            for (ThemisNodeInstance myInstance : myFile.getUnderlyingFile().getContents().getImports()) {
                /* Determine full name */
                final ThemisNodeImport myImport = (ThemisNodeImport) myInstance;
                final String myFullName = myImport.getFullName();

                /* If this is a previously unseen class */
                if (!theProjectClasses.containsKey(myFullName)) {
                    /* Ensure the external class map */
                    theExternalClasses.computeIfAbsent(myFullName, n -> new ThemisReflectExternal(myImport));
                }
            }
        }
    }

    /**
     * Obtain a list of all children of a project class.
     *
     * @param pClass the class
     * @return the children
     */
    public List<ThemisClassInstance> listAllInherited(final String pClass) {
        /* Create list of all ancestors */
        final List<String> myAncestors = listAllAncestors(pClass);

        /* Build list of all children of the ancestors */
        final List<ThemisClassInstance> myResult = new ArrayList<>();
        for (String myAncestor : myAncestors) {
            listAllInherited(myResult, myAncestor);
        }
        return myResult;
    }

    /**
     * Obtain a list of all inherited children of a class.
     *
     * @param pResult the list to populate
     * @param pClass  the class
     */
    private void listAllInherited(final List<ThemisClassInstance> pResult,
                                  final String pClass) {
        final ThemisSolverClass myProjectClass = theProjectClasses.get(pClass);
        if (myProjectClass != null) {
            listAllProjectInherited(pResult, myProjectClass);
        } else {
            listAllExternalInherited(pResult, theExternalClasses.get(pClass));
        }
    }

    /**
     * Obtain a list of all inherited children of a project class.
     *
     * @param pResult the list to populate
     * @param pClass  the class
     */
    private void listAllProjectInherited(final List<ThemisClassInstance> pResult,
                                         final ThemisSolverClass pClass) {
        final String myParent = pClass.getFullName();
        for (ThemisSolverClass myChild : theProjectClasses.values()) {
            final String myCheckName = myParent + ThemisChar.PERIOD + myChild.getName();
            if (myCheckName.equals(myChild.getFullName())) {
                pResult.add(myChild.getUnderlyingClass());
            }
        }
    }

    /**
     * Obtain a list of all inherited children of an external class.
     *
     * @param pResult the list to populate
     * @param pClass  the class
     */
    private void listAllExternalInherited(final List<ThemisClassInstance> pResult,
                                          final ThemisReflectExternal pClass) {
        final String myParent = pClass.getFullName();
        for (ThemisReflectExternal myChild : theExternalClasses.values()) {
            final String myCheckName = myParent + ThemisChar.PERIOD + myChild.getName();
            if (myCheckName.equals(myChild.getFullName())) {
                pResult.add(myChild.getClassInstance());
            }
        }
    }

    /**
     * Obtain a list of all ancestors of a class.
     *
     * @param pClass the class
     * @return the ancestors
     */
    private List<String> listAllAncestors(final String pClass) {
        final List<String> myResult = new ArrayList<>();
        final ThemisSolverClass myClass = theProjectClasses.get(pClass);
        if (myClass != null) {
            listAllProjectAncestors(myResult, myClass);
        } else {
            listAllExternalAncestors(myResult, theExternalClasses.get(pClass));
        }
        return myResult;
    }

    /**
     * Populate a list of all ancestors of a project class.
     *
     * @param pExisting the list of existing ancestors
     * @param pClass    the class
     */
    private void listAllProjectAncestors(final List<String> pExisting,
                                         final ThemisSolverClass pClass) {
        for (String myAncestor : pClass.getAncestors()) {
            /* If the ancestor is unknown */
            if (!pExisting.contains(myAncestor)) {
                /* Add the ancestor */
                pExisting.add(myAncestor);

                /* If the ancestor is a project file */
                final ThemisSolverClass myClass = theProjectClasses.get(myAncestor);
                if (myClass != null) {
                    /* Process all ancestors */
                    listAllProjectAncestors(pExisting, myClass);

                    /* else must be external so add all ancestors */
                } else {
                    listAllExternalAncestors(pExisting, theExternalClasses.get(myAncestor));
                }
            }
        }
    }

    /**
     * Populate a list of all ancestors of an external class.
     *
     * @param pExisting the list of existing ancestors
     * @param pClass    the class
     */
    private void listAllExternalAncestors(final List<String> pExisting,
                                          final ThemisReflectExternal pClass) {
        for (String myAncestor : pClass.getAncestors()) {
            /* If the ancestor is unknown */
            if (!pExisting.contains(myAncestor)) {
                /* Add the ancestor and all of its ancestors */
                pExisting.add(myAncestor);
                listAllExternalAncestors(pExisting, theExternalClasses.get(myAncestor));
            }
        }
    }

    /**
     * Try a class as a java.lang class.
     *
     * @param pName the class name
     * @return the loaded class or null if it did not exist
     */
    public ThemisReflectExternal tryJavaLang(final String pName) {
        return theJar.tryJavaLang(pName);
    }

    /**
     * Try a class as a java.lang class.
     *
     * @param pName the class name
     * @return the loaded class or null if it did not exist
     */
    public ThemisReflectExternal tryNamedClass(final String pName) {
        return theJar.tryNamedClass(pName);
    }

    @Override
    public void close() {
        theJar.close();
    }
}