AiSuggestionEngineImpl.java

1
package org.egothor.methodatlas.ai;
2
3
import java.io.IOException;
4
import java.nio.file.Files;
5
import java.util.List;
6
7
/**
8
 * Default implementation of {@link AiSuggestionEngine} that coordinates
9
 * provider selection and taxonomy loading for AI-based security classification.
10
 *
11
 * <p>
12
 * This implementation acts as the primary orchestration layer between the
13
 * command-line application and the provider-specific AI client subsystem. It
14
 * resolves the effective {@link AiProviderClient} through
15
 * {@link AiProviderFactory}, loads the taxonomy text used to guide
16
 * classification, and delegates class-level analysis requests to the selected
17
 * provider client.
18
 * </p>
19
 *
20
 * <h2>Responsibilities</h2>
21
 *
22
 * <ul>
23
 * <li>creating the effective provider client from {@link AiOptions}</li>
24
 * <li>loading taxonomy text from a configured file or from the selected
25
 * built-in taxonomy mode</li>
26
 * <li>delegating class analysis requests to the provider client</li>
27
 * <li>presenting a provider-independent {@link AiSuggestionEngine} contract to
28
 * higher-level callers</li>
29
 * </ul>
30
 *
31
 * <p>
32
 * Instances of this class are immutable after construction and are intended to
33
 * be created once per application run.
34
 * </p>
35
 *
36
 * @see AiSuggestionEngine
37
 * @see AiProviderFactory
38
 * @see AiProviderClient
39
 * @see AiOptions.TaxonomyMode
40
 */
41
public final class AiSuggestionEngineImpl implements AiSuggestionEngine {
42
43
    private final AiProviderClient client;
44
    private final String taxonomyText;
45
46
    /**
47
     * Creates a new AI suggestion engine using the supplied runtime options.
48
     *
49
     * <p>Rate-limit events are silently discarded by this constructor.  Use
50
     * {@link #AiSuggestionEngineImpl(AiOptions, RateLimitListener)} when the
51
     * caller needs to be informed of HTTP&nbsp;429 pauses.</p>
52
     *
53
     * <p>
54
     * During construction, the implementation resolves the effective provider
55
     * client and loads the taxonomy text that will be supplied to the AI provider
56
     * for subsequent classification requests. The taxonomy is taken from an
57
     * external file when configured; otherwise, the built-in taxonomy selected by
58
     * {@link AiOptions#taxonomyMode()} is used.
59
     * </p>
60
     *
61
     * @param options AI runtime configuration controlling provider selection,
62
     *                taxonomy loading, and request behavior
63
     *
64
     * @throws AiSuggestionException if provider initialization fails or if the
65
     *                               configured taxonomy cannot be loaded
66
     * @see #AiSuggestionEngineImpl(AiOptions, RateLimitListener)
67
     */
68
    public AiSuggestionEngineImpl(AiOptions options) throws AiSuggestionException {
69
        this.client = AiProviderFactory.create(options);
70
        this.taxonomyText = loadTaxonomy(options);
71
    }
72
73
    /**
74
     * Creates a new AI suggestion engine that notifies
75
     * {@code rateLimitListener} before each rate-limit sleep.
76
     *
77
     * <p>
78
     * During construction, the implementation resolves the effective provider
79
     * client and loads the taxonomy text that will be supplied to the AI provider
80
     * for subsequent classification requests. The taxonomy is taken from an
81
     * external file when configured; otherwise, the built-in taxonomy selected by
82
     * {@link AiOptions#taxonomyMode()} is used.
83
     * </p>
84
     *
85
     * @param options             AI runtime configuration controlling provider
86
     *                            selection, taxonomy loading, and request behavior
87
     * @param rateLimitListener   callback invoked before each HTTP&nbsp;429
88
     *                            pause; must not be {@code null}
89
     *
90
     * @throws AiSuggestionException if provider initialization fails or if the
91
     *                               configured taxonomy cannot be loaded
92
     * @see RateLimitListener
93
     */
94
    public AiSuggestionEngineImpl(AiOptions options, RateLimitListener rateLimitListener)
95
            throws AiSuggestionException {
96
        this.client = AiProviderFactory.create(options, rateLimitListener);
97
        this.taxonomyText = loadTaxonomy(options);
98
    }
99
100
    /**
101
     * Requests AI-generated security classification for a single parsed test class.
102
     *
103
     * <p>
104
     * The method delegates directly to the configured {@link AiProviderClient},
105
     * supplying the fully qualified class name, the complete class source, and the
106
     * taxonomy text loaded at engine initialization time.
107
     * </p>
108
     *
109
     * @param fqcn          fully qualified class name of the analyzed test class
110
     * @param classSource   complete source code of the class to analyze
111
     * @param targetMethods deterministically extracted JUnit test methods that must
112
     *                      be classified
113
     * @return normalized AI classification result for the class and its methods
114
     *
115
     * @throws AiSuggestionException if the provider fails to analyze the class or
116
     *                               returns an invalid response
117
     *
118
     * @see AiClassSuggestion
119
     * @see AiProviderClient#suggestForClass(String, String, String, List)
120
     */
121
    @Override
122
    public AiClassSuggestion suggestForClass(String fileStem, String fqcn, String classSource,
123
            List<PromptBuilder.TargetMethod> targetMethods) throws AiSuggestionException {
124 1 1. suggestForClass : replaced return value with null for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::suggestForClass → KILLED
        return client.suggestForClass(fqcn, classSource, taxonomyText, targetMethods);
125
    }
126
127
    /**
128
     * Loads the taxonomy text used to guide AI classification.
129
     *
130
     * <p>
131
     * Resolution order:
132
     * </p>
133
     * <ol>
134
     * <li>If an external taxonomy file is configured, its contents are used.</li>
135
     * <li>Otherwise, the built-in taxonomy selected by
136
     * {@link AiOptions#taxonomyMode()} is used.</li>
137
     * </ol>
138
     *
139
     * @param options AI runtime configuration
140
     * @return taxonomy text to be supplied to the AI provider
141
     *
142
     * @throws AiSuggestionException if an external taxonomy file is configured but
143
     *                               cannot be read successfully
144
     *
145
     * @see DefaultSecurityTaxonomy#text()
146
     * @see OptimizedSecurityTaxonomy#text()
147
     */
148
    private static String loadTaxonomy(AiOptions options) throws AiSuggestionException {
149 2 1. loadTaxonomy : removed conditional - replaced equality check with true → KILLED
2. loadTaxonomy : removed conditional - replaced equality check with false → KILLED
        if (options.taxonomyFile() != null) {
150
            try {
151 1 1. loadTaxonomy : replaced return value with "" for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::loadTaxonomy → KILLED
                return Files.readString(options.taxonomyFile());
152
            } catch (IOException e) {
153
                throw new AiSuggestionException("Failed to read taxonomy file: " + options.taxonomyFile(), e);
154
            }
155
        }
156
157 2 1. loadTaxonomy : replaced return value with "" for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::loadTaxonomy → KILLED
2. loadTaxonomy : Changed switch default to be first case → KILLED
        return switch (options.taxonomyMode()) {
158
            case DEFAULT -> DefaultSecurityTaxonomy.text();
159
            case OPTIMIZED -> OptimizedSecurityTaxonomy.text();
160
        };
161
    }
162
}

Mutations

124

1.1
Location : suggestForClass
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:suggestForClass_delegatesToProviderClient_usingOptimizedTaxonomy()]
replaced return value with null for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::suggestForClass → KILLED

149

1.1
Location : loadTaxonomy
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:suggestForClass_delegatesToProviderClient_usingOptimizedTaxonomy()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : loadTaxonomy
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:constructor_throwsWhenTaxonomyFileCannotBeRead()]
removed conditional - replaced equality check with false → KILLED

151

1.1
Location : loadTaxonomy
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:suggestForClass_usesExternalTaxonomyFile_whenConfigured()]
replaced return value with "" for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::loadTaxonomy → KILLED

157

1.1
Location : loadTaxonomy
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:suggestForClass_delegatesToProviderClient_usingOptimizedTaxonomy()]
replaced return value with "" for org/egothor/methodatlas/ai/AiSuggestionEngineImpl::loadTaxonomy → KILLED

2.2
Location : loadTaxonomy
Killed by : org.egothor.methodatlas.ai.AiSuggestionEngineImplTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.ai.AiSuggestionEngineImplTest]/[method:suggestForClass_delegatesToProviderClient_usingOptimizedTaxonomy()]
Changed switch default to be first case → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1