AiSuggestionEngineImpl.java

package org.egothor.methodatlas.ai;

import java.io.IOException;
import java.nio.file.Files;
import java.util.List;

/**
 * Default implementation of {@link AiSuggestionEngine} that coordinates
 * provider selection and taxonomy loading for AI-based security classification.
 *
 * <p>
 * This implementation acts as the primary orchestration layer between the
 * command-line application and the provider-specific AI client subsystem. It
 * resolves the effective {@link AiProviderClient} through
 * {@link AiProviderFactory}, loads the taxonomy text used to guide
 * classification, and delegates class-level analysis requests to the selected
 * provider client.
 * </p>
 *
 * <h2>Responsibilities</h2>
 *
 * <ul>
 * <li>creating the effective provider client from {@link AiOptions}</li>
 * <li>loading taxonomy text from a configured file or from the selected
 * built-in taxonomy mode</li>
 * <li>delegating class analysis requests to the provider client</li>
 * <li>presenting a provider-independent {@link AiSuggestionEngine} contract to
 * higher-level callers</li>
 * </ul>
 *
 * <p>
 * Instances of this class are immutable after construction and are intended to
 * be created once per application run.
 * </p>
 *
 * @see AiSuggestionEngine
 * @see AiProviderFactory
 * @see AiProviderClient
 * @see AiOptions.TaxonomyMode
 */
public final class AiSuggestionEngineImpl implements AiSuggestionEngine {

    private final AiProviderClient client;
    private final String taxonomyText;

    /**
     * Creates a new AI suggestion engine using the supplied runtime options.
     *
     * <p>
     * During construction, the implementation resolves the effective provider
     * client and loads the taxonomy text that will be supplied to the AI provider
     * for subsequent classification requests. The taxonomy is taken from an
     * external file when configured; otherwise, the built-in taxonomy selected by
     * {@link AiOptions#taxonomyMode()} is used.
     * </p>
     *
     * @param options AI runtime configuration controlling provider selection,
     *                taxonomy loading, and request behavior
     *
     * @throws AiSuggestionException if provider initialization fails or if the
     *                               configured taxonomy cannot be loaded
     */
    public AiSuggestionEngineImpl(AiOptions options) throws AiSuggestionException {
        this.client = AiProviderFactory.create(options);
        this.taxonomyText = loadTaxonomy(options);
    }

    /**
     * Requests AI-generated security classification for a single parsed test class.
     *
     * <p>
     * The method delegates directly to the configured {@link AiProviderClient},
     * supplying the fully qualified class name, the complete class source, and the
     * taxonomy text loaded at engine initialization time.
     * </p>
     *
     * @param fqcn          fully qualified class name of the analyzed test class
     * @param classSource   complete source code of the class to analyze
     * @param targetMethods deterministically extracted JUnit test methods that must
     *                      be classified
     * @return normalized AI classification result for the class and its methods
     *
     * @throws AiSuggestionException if the provider fails to analyze the class or
     *                               returns an invalid response
     *
     * @see AiClassSuggestion
     * @see AiProviderClient#suggestForClass(String, String, String, List)
     */
    @Override
    public AiClassSuggestion suggestForClass(String fileStem, String fqcn, String classSource,
            List<PromptBuilder.TargetMethod> targetMethods) throws AiSuggestionException {
        return client.suggestForClass(fqcn, classSource, taxonomyText, targetMethods);
    }

    /**
     * Loads the taxonomy text used to guide AI classification.
     *
     * <p>
     * Resolution order:
     * </p>
     * <ol>
     * <li>If an external taxonomy file is configured, its contents are used.</li>
     * <li>Otherwise, the built-in taxonomy selected by
     * {@link AiOptions#taxonomyMode()} is used.</li>
     * </ol>
     *
     * @param options AI runtime configuration
     * @return taxonomy text to be supplied to the AI provider
     *
     * @throws AiSuggestionException if an external taxonomy file is configured but
     *                               cannot be read successfully
     *
     * @see DefaultSecurityTaxonomy#text()
     * @see OptimizedSecurityTaxonomy#text()
     */
    private static String loadTaxonomy(AiOptions options) throws AiSuggestionException {
        if (options.taxonomyFile() != null) {
            try {
                return Files.readString(options.taxonomyFile());
            } catch (IOException e) {
                throw new AiSuggestionException("Failed to read taxonomy file: " + options.taxonomyFile(), e);
            }
        }

        return switch (options.taxonomyMode()) {
            case DEFAULT -> DefaultSecurityTaxonomy.text();
            case OPTIMIZED -> OptimizedSecurityTaxonomy.text();
        };
    }
}