ThemisDSMModule.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.dsm;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisIOException;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisMaven;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * DSM Module.
 */
public class ThemisDSMModule {
    /**
     * The java directory.
     */
    private static final String DIR_JAVA = "src/main/java";

    /**
     * The location of the module.
     */
    private final File theLocation;

    /**
     * The name of the module.
     */
    private final String theModule;

    /**
     * The list of subModules.
     */
    private final List<ThemisDSMModule> theSubModules;

    /**
     * The list of packages.
     */
    private final List<ThemisDSMPackage> thePackages;

    /**
     * The error.
     */
    private OceanusException theError;

    /**
     * Constructor.
     *
     * @param pLocation the location of the project
     */
    ThemisDSMModule(final File pLocation) {
        theLocation = pLocation;
        theModule = theLocation.getName();
        theSubModules = new ArrayList<>();
        thePackages = new ArrayList<>();
    }

    /**
     * Return the location of the module.
     *
     * @return the location
     */
    File getLocation() {
        return theLocation;
    }

    /**
     * Return the module name.
     *
     * @return the name
     */
    String getModuleName() {
        return theModule;
    }

    /**
     * Add a package to the list.
     *
     * @param pPackage the package
     */
    void registerPackage(final ThemisDSMPackage pPackage) {
        if (thePackages.contains(pPackage)) {
            throw new IllegalArgumentException("Already included");
        }

        /* Add the package */
        thePackages.add(pPackage);
    }

    /**
     * Obtain an iterator of the imports.
     *
     * @return the iterator
     */
    Iterator<ThemisDSMModule> moduleIterator() {
        return theSubModules.iterator();
    }

    /**
     * Does the module have subModules?
     *
     * @return true/false
     */
    boolean hasSubModules() {
        return !theSubModules.isEmpty();
    }

    /**
     * Does the module have packages?
     *
     * @return true/false
     */
    boolean hasPackages() {
        return !thePackages.isEmpty();
    }

    /**
     * Obtain the count of packages.
     *
     * @return the count
     */
    int getPackageCount() {
        return thePackages.size();
    }

    /**
     * Obtain the error.
     *
     * @return error
     */
    public OceanusException getError() {
        return theError;
    }

    /**
     * Obtain an iterator of the imports.
     *
     * @return the iterator
     */
    Iterator<ThemisDSMPackage> packageIterator() {
        return thePackages.iterator();
    }

    /**
     * Obtain an iterator of the imports.
     *
     * @return the iterator
     */
    public ThemisDSMPackage getDefaultPackage() {
        final List<ThemisDSMPackage> myList = listPackages();
        return myList.isEmpty() ? null : myList.get(0);
    }

    /**
     * Obtain an indexed package.
     *
     * @param pIndex the index of the package
     * @return the package
     */
    public ThemisDSMPackage getIndexedPackage(final int pIndex) {
        final List<ThemisDSMPackage> myList = listPackages();
        return myList.size() <= pIndex || pIndex < 0 ? null : myList.get(pIndex);
    }

    /**
     * Obtain a list of all modules and submodules that contain packages.
     *
     * @return the list
     */
    List<ThemisDSMModule> listModules() {
        /* Create result list and loop through the modules */
        final List<ThemisDSMModule> myList = new ArrayList<>();
        for (ThemisDSMModule myModule : theSubModules) {
            /* Only add the module if it has packages */
            if (myModule.hasPackages()) {
                myList.add(myModule);
            }

            /* Add any subModules */
            myList.addAll(myModule.listModules());
        }

        /* Return the list */
        return myList;
    }

    @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 a module */
        if (pThat.getClass() != this.getClass()) {
            return false;
        }

        /* Access the target Class */
        final ThemisDSMModule myThat = (ThemisDSMModule) pThat;

        /* Check name */
        return theModule.equals(myThat.getModuleName());
    }

    @Override
    public int hashCode() {
        return theModule.hashCode();
    }

    /**
     * process modules and packages.
     */
    void processModulesAndPackages() {
        /* Process the project */
        parseProjectFile(new File(theLocation, ThemisAnalysisMaven.POM));

        /* Look for the source directory */
        final File mySrc = new File(theLocation, DIR_JAVA);
        if (mySrc.isDirectory()) {
            /* Process any packages */
            processPackages(mySrc);
        }

        /* Process the classes */
        processClasses();

        /* Process the dependencies */
        processDependencies();
    }

    /**
     * process packages.
     *
     * @param pPath the location of the package
     */
    void processPackages(final File pPath) {
        /* Loop through the entries in the directory */
        for (File myFile : Objects.requireNonNull(pPath.listFiles())) {
            /* Ignore files */
            if (!myFile.isDirectory()) {
                continue;
            }

            /* Process the package */
            final ThemisDSMPackage myPackage = new ThemisDSMPackage(this, myFile);
            myPackage.processPackages();
        }
    }

    /**
     * process classes.
     */
    private void processClasses() {
        /* Loop through the packages */
        for (ThemisDSMPackage myPackage : thePackages) {
            /* Process the classes */
            myPackage.processClasses();
        }
    }

    /**
     * process dependencies.
     */
    private void processDependencies() {
        /* Loop through the packages */
        for (ThemisDSMPackage myPackage : thePackages) {
            /* Process the classes */
            myPackage.processDependencies();
        }

        /* Sort the packages */
        thePackages.sort(ThemisDSMPackage::compareTo);
    }

    /**
     * Find a class reference.
     *
     * @param pReference the class name
     * @return the found class or null
     */
    ThemisDSMClass findClass(final String pReference) {
        /* Loop through the packages */
        for (ThemisDSMPackage myPackage : thePackages) {
            /* Ignore if the package is not a possibility */
            final String myPrefix = myPackage.getPackageName() + ThemisDSMPackage.SEP_PACKAGE;
            if (!pReference.startsWith(myPrefix)) {
                continue;
            }

            /* Loop through the classes */
            final ThemisDSMClass myClass = myPackage.findClass(pReference);
            if (myClass != null) {
                return myClass;
            }
        }

        /* No match */
        return null;
    }

    /**
     * Does this module contain circular package references?
     *
     * @return true/false
     */
    public boolean isCircular() {
        /* Loop through the dependencies */
        for (ThemisDSMPackage myPackage : thePackages) {
            if (myPackage.isCircular()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Obtain a list of all packages that have dependencies.
     *
     * @return the list
     */
    public List<ThemisDSMPackage> listPackages() {
        /* Create result list and loop through the modules */
        final List<ThemisDSMPackage> myList = new ArrayList<>();
        for (ThemisDSMPackage myPackage : thePackages) {
            myList.add(myPackage);
        }

        /* Return the list */
        return myList;
    }

    /**
     * Compare this package to another module for sort order.
     *
     * @param pThat the other module to compare to
     * @return true/false
     */
    public int compareTo(final ThemisDSMModule pThat) {
        /* Handle circular status */
        if (isCircular() != pThat.isCircular()) {
            return isCircular() ? -1 : 1;
        }

        /* If all else fails rely on alphabetical */
        return theModule.compareTo(pThat.getModuleName());
    }

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

    /**
     * Parse the maven project file.
     *
     * @param pPom the project file
     */
    private void parseProjectFile(final File pPom) {
        /* If the pom file does not exist, just return */
        if (!pPom.exists()) {
            return;
        }

        /* Protect against exceptions */
        try (InputStream myInStream = new FileInputStream(pPom)) {
            /* Parse the Project definition file */
            final ThemisAnalysisMaven myPom = new ThemisAnalysisMaven(myInStream);

            /* Loop through the modules */
            for (final String myModuleName : myPom.getModules()) {
                final File myModuleDir = new File(pPom.getParentFile(), myModuleName);

                final ThemisDSMModule myModule = new ThemisDSMModule(myModuleDir);
                myModule.processModulesAndPackages();
                if (myModule.getPackageCount() > 1
                        || myModule.hasSubModules()) {
                    theSubModules.add(myModule);
                }
            }

            /* Sort the modules */
            theSubModules.sort(Comparator.comparing(ThemisDSMModule::getModuleName));

            /* Catch exceptions */
        } catch (IOException
                 | OceanusException e) {
            /* Save Exception */
            theSubModules.clear();
            theError = new ThemisIOException("Failed to parse Project file", e);
        }
    }
}