ThemisMapperReference.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.themis.parser.base.ThemisChar;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverClass;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverDef.ThemisSolverPackageDef;
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.proj.ThemisSolverReference;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverReference.ThemisRefType;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverReference.ThemisSolverRefClass;
import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverReference.ThemisSolverRefPackage;

import java.util.ArrayList;
import java.util.List;

/**
 * Class to analyse references.
 */
public class ThemisMapperReference {
    /**
     * Process references for a project.
     *
     * @param pProject the project
     */
    public void processReferences(final ThemisSolverProject pProject) {
        /* Process references for all modules */
        for (ThemisSolverModule myModule : pProject.getModules()) {
            /* Process references for root */
            processReferences(myModule.getRoot());
        }
    }

    /**
     * Process references for a package and it's children.
     *
     * @param pPackage the package
     */
    private void processReferences(final ThemisSolverPackage pPackage) {
        /* Process references for all children */
        for (ThemisSolverPackage myChild : pPackage.getChildren()) {
            /* Process references for all siblings */
            for (ThemisSolverPackage mySibling : pPackage.getChildren()) {
                /* Avoid self references */
                if (!myChild.equals(mySibling)) {
                    findReferences(myChild, ThemisRefType.SIBLING, mySibling);
                }
            }

            /* Process sibling references */
            determineLocalReferences(myChild);
            pPackage.processLocalReferences();

            /* find references from the child to the parent */
            findReferences(myChild, ThemisRefType.PARENT, pPackage);

            /* find references from the parent to the child */
            findReferences(pPackage, ThemisRefType.CHILD, myChild);

            /* Process child */
            processReferences(myChild);
        }

        /* Process references for all children */
        for (ThemisSolverPackage myChild : pPackage.getChildren()) {
            /* Process sibling references */
            myChild.processLocalReferences();

            /* Check for incest */
            check4Incest(pPackage, myChild);
        }
    }

    /**
     * Find references from the source package plus children to the target package and it's children.
     *
     * @param pSource  the sourcePackage
     * @param pRefType the referenceType
     * @param pTarget  the targetPackage
     */
    private void findReferences(final ThemisSolverPackage pSource,
                                final ThemisRefType pRefType,
                                final ThemisSolverPackage pTarget) {
        /* Obtain the map for the package */
        final ThemisSolverReference myMap = pSource.getReferenceMap();

        /* Create result map */
        final ThemisSolverRefPackage myReferences = new ThemisSolverRefPackage(pTarget, pRefType);

        /* Look for references */
        findReferences(myReferences, pSource);

        /* If we have any references */
        if (!myReferences.getReferences().isEmpty()) {
            myMap.addReferences(myReferences);
        }
    }

    /**
     * Find references to the target package and it's children.
     *
     * @param pReferences the referenceSet
     * @param pSource     the source package
     */
    private void findReferences(final ThemisSolverRefPackage pReferences,
                                final ThemisSolverPackage pSource) {
        /* Loop through the files */
        for (ThemisSolverFile myFile : pSource.getFiles()) {
            findReferences(pReferences, myFile);
        }

        /* Loop through the children to find further references */
        if (!ThemisRefType.CHILD.equals(pReferences.getReferenceType())) {
            for (ThemisSolverPackage myChild : pSource.getChildren()) {
                findReferences(pReferences, myChild);
            }
        }
    }

    /**
     * Find references from the file to the target package and it's children.
     *
     * @param pReferences the referenceSet
     * @param pFile       the source file
     */
    private void findReferences(final ThemisSolverRefPackage pReferences,
                                final ThemisSolverFile pFile) {
        /* Create list and prefix */
        final List<ThemisSolverClass> myReferences = new ArrayList<>();
        final String myPackage = pReferences.getPackage().getPackageName();
        final String myPrefix = myPackage + ThemisChar.PERIOD;
        final boolean isParent = ThemisRefType.PARENT.equals(pReferences.getReferenceType());

        /* Loop through the references */
        for (ThemisSolverClass myReference : pFile.getReferenced()) {
            final String myName = myReference.getFullName();
            if (isParent
                    ? myName.equals(myPrefix + myReference.getName())
                    : myName.startsWith(myPrefix)) {
                myReferences.add(myReference);
            }
        }

        /* If we have references, add to map */
        if (!myReferences.isEmpty()) {
            /* Create the reference map and add to references */
            final ThemisSolverRefClass myClass = new ThemisSolverRefClass(pFile.getTopLevel(), myReferences);
            pReferences.addReferences(myClass);
        }
    }

    /**
     * Determine local references.
     *
     * @param pPackage the package
     */
    private void determineLocalReferences(final ThemisSolverPackage pPackage) {
        /* Create list */
        final List<ThemisSolverPackageDef> myReferences = new ArrayList<>();
        final ThemisSolverReference myReferenceMap = pPackage.getReferenceMap();

        /* Add all Sibling references to the list */
        for (ThemisSolverRefPackage myPackage : myReferenceMap.getReferences(ThemisRefType.SIBLING)) {
            myReferences.add(myPackage.getPackage());
        }

        /* Register the references */
        pPackage.setReferenced(myReferences);
    }

    /**
     * Check for incest.
     *
     * @param pParent the parent
     * @param pChild  the child
     */
    private void check4Incest(final ThemisSolverPackage pParent,
                              final ThemisSolverPackage pChild) {
        /* Obtain the map for the package */
        final ThemisSolverReference myParentMap = pParent.getReferenceMap();
        final ThemisSolverReference myChildMap = pChild.getReferenceMap();

        /* If we have two-way links parent to/from child */
        final ThemisSolverRefPackage myParentRefs = myChildMap.getReferredPackage(pParent);
        final ThemisSolverRefPackage myChildRefs = myParentMap.getReferredPackage(pChild);
        if (myParentRefs != null && myChildRefs != null) {
            /* Mark child as incestuous */
            pChild.markIncestuous();
        }
    }
}