ThemisMavenParser.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.parser.maven;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.exc.ThemisLogicException;
import io.github.tonywasher.joceanus.themis.parser.base.ThemisDataResource;
import io.github.tonywasher.joceanus.themis.parser.maven.ThemisMavenPom.ThemisXMavenControl;

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

/**
 * Maven Parser.
 */
public class ThemisMavenParser
        implements ThemisXMavenControl {
    /**
     * The POM file name.
     */
    private static final String POM_NAME = "pom.xml";

    /**
     * The map of maven artifacts.
     */
    private final Map<ThemisMavenId, ThemisMavenPom> thePoms;

    /**
     * The list of local modules.
     */
    private final List<ThemisMavenPom> theModules;

    /**
     * The dependencies.
     */
    private final List<ThemisMavenId> theDependencies;

    /**
     * The pom.
     */
    private final ThemisMavenPom thePom;

    /**
     * Have we processed local definitions?
     */
    private boolean processedLocal;

    /**
     * Constructor.
     *
     * @param pReport   the reporter
     * @param pLocation the location of the project
     * @throws OceanusException on error
     */
    public ThemisMavenParser(final TethysUIThreadStatusReport pReport,
                             final File pLocation) throws OceanusException {
        /* Obtain the active profile */
        OceanusProfile myTask = pReport.getActiveTask();
        myTask = myTask.startTask(ThemisDataResource.TASK_DISCOVER);
        pReport.initTask(ThemisDataResource.TASK_DISCOVER);
        pReport.setNumStages(2);

        /* Create the map */
        thePoms = new LinkedHashMap<>();
        processedLocal = false;

        /* Parse the local project */
        thePom = loadPomAtLocation(pLocation);
        processedLocal = true;

        /* Loop through the local modules */
        OceanusProfile mySubTask = myTask.startTask(ThemisDataResource.TASK_DISCOVERLOCAL);
        final List<ThemisMavenPom> myModules = new ArrayList<>(thePoms.values());
        pReport.setNewStage(ThemisDataResource.TASK_DISCOVERLOCAL);
        pReport.setNumSteps(myModules.size());
        for (ThemisMavenPom myModule : myModules) {
            /* Process the module */
            final String myName = myModule.getId().toString();
            mySubTask.startTask(myName);
            pReport.setNextStep();
            myModule.getVersions();
            myModule.getDirectDependencies();
        }

        /* Loop through the local modules */
        mySubTask = myTask.startTask(ThemisDataResource.TASK_DISCOVERDEPENDENCY);
        pReport.setNewStage(ThemisDataResource.TASK_DISCOVERDEPENDENCY);
        pReport.setNumSteps(myModules.size());
        for (ThemisMavenPom myModule : myModules) {
            /* Process the module */
            final String myName = myModule.getId().toString();
            mySubTask.startTask(myName);
            pReport.setNextStep();
            myModule.getDependencies();
        }

        /* Create list of local modules */
        theModules = getModuleList(myModules);
        theDependencies = thePom.getDependencies();
        myTask.end();
    }

    /**
     * Obtain the name.
     *
     * @return the name
     */
    public String getName() {
        return thePom.getArtifactId();
    }

    /**
     * Obtain the local modules.
     *
     * @return the local modules
     */
    public List<ThemisMavenId> getModules() {
        return theModules.stream().map(ThemisMavenPom::getId).toList();
    }

    /**
     * Obtain the parsed modules.
     *
     * @return the parsed modules
     */
    public List<ThemisMavenPom> getParsedModules() {
        return theModules;
    }

    /**
     * Obtain the dependencies.
     *
     * @return the dependencies
     */
    public List<ThemisMavenId> getDependencies() {
        return theDependencies;
    }

    @Override
    public ThemisMavenPom loadPomViaId(final ThemisMavenId pId) throws OceanusException {
        /* Look for already known Pom */
        final ThemisMavenPom myExisting = thePoms.get(pId);
        if (myExisting != null) {
            return myExisting;
        }

        /* Reject if we have not fully processed local poms */
        if (!processedLocal) {
            throw new ThemisLogicException("Processed pom via id before finished local processing");
        }

        /* Load the pom file and store into cache */
        final ThemisMavenPom myPom = doLoadPomViaId(pId);
        thePoms.put(pId, myPom);

        /* If the pom has an associated jar */
        if (myPom.isJarPackaging()) {
            /* Make sure that the pom has been downloaded */
            ThemisMavenLocation.ensureJarArtifact(pId);
        }

        /* Process dependencies */
        myPom.getVersions();
        myPom.getDirectDependencies();
        myPom.getDependencies();

        /* Return the pom */
        return myPom;
    }

    /**
     * Do load pom via Id.
     *
     * @param pId the id of the pom
     * @return the loaded pom
     * @throws OceanusException on error
     */
    private ThemisMavenPom doLoadPomViaId(final ThemisMavenId pId) throws OceanusException {
        /* If the id has a classifier */
        if (pId.getClassifier() != null) {
            /* There is no pom , so just fake it */
            return new ThemisMavenPom(pId);
        }

        /* Make sure that the pom has been downloaded */
        ThemisMavenLocation.ensurePomArtifact(pId);

        /* Just do a normal load */
        return new ThemisMavenPom(this, new File(ThemisMavenLocation.getLocalPomFileName(pId)));
    }

    @Override
    public ThemisMavenPom loadPomAtLocation(final File pLocation) throws OceanusException {
        /* Parse the pom and obtain the id */
        final ThemisMavenPom myPom = new ThemisMavenPom(this, new File(pLocation, POM_NAME));
        final ThemisMavenId myId = myPom.getId();

        /* Make sure that the pom has not been seen before */
        final ThemisMavenPom myExisting = thePoms.get(myId);
        if (myExisting != null) {
            throw new ThemisDataException("Duplicate POM - " + myId);
        }

        /* Store into map */
        thePoms.put(myId, myPom);

        /* Process local details */
        myPom.processLocalDetails();

        /* Return the Pom */
        return myPom;
    }

    /**
     * Create list of local jar modules.
     *
     * @param pLocal the list of local modules
     * @return the list.
     * @throws OceanusException on error
     */
    private List<ThemisMavenPom> getModuleList(final List<ThemisMavenPom> pLocal) throws OceanusException {
        /* Loop through the local modules */
        final List<ThemisMavenPom> myModules = new ArrayList<>();
        for (ThemisMavenPom myModule : pLocal) {
            if (myModule.isJarPackaging()) {
                myModules.add(myModule);
            }
        }
        return myModules;
    }

    /**
     * Combine module dependencies.
     *
     * @return the dependencies
     * @throws OceanusException on error
     */
    public List<ThemisMavenId> getProjectDependencies() throws OceanusException {
        /* Allocate the dependencies */
        final List<ThemisMavenId> myIds = new ArrayList<>();

        /* Loop through the modules */
        for (ThemisMavenPom myModule : theModules) {
            /* Loop through direct dependencies */
            for (ThemisMavenId myDependency : myModule.getDependencies()) {
                /* Check the dependency and add if required */
                ThemisMavenPom.checkDependencyId(myIds, myDependency);
            }
        }

        /* Return the id */
        return myIds;
    }

}