RuleExampleRefiner.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License 2.0: https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.api;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.IntStream;
import org.sandbox.jdt.triggerpattern.internal.ChangeVariableGeneralizer;
import org.sandbox.jdt.triggerpattern.internal.RuleAuthoringDocument.ChangeExample;
/** Bounded, deterministic proposals; names are generalized only after replay. */
public final class RuleExampleRefiner {
public static final int MAX_VARIABLES = 6;
private RuleExampleRefiner() { }
public record Proposal(Set<String> generalizedVariables, ChangeExample example, RuleExampleVerifier.Report report) {
public Proposal {
generalizedVariables = Set.copyOf(generalizedVariables);
}
}
/**
* Tests most-general candidates first (at most 64). The caller builds the
* complete document, preserving guards and metadata. Nothing is applied to
* the rule or source files; the returned proposal still needs review.
*/
public static Optional<Proposal> refine(ChangeExample seed, RuleExampleSuite suite,
RuleExampleVerifier verifier, Function<ChangeExample, String> documentFactory) {
var names = ChangeVariableGeneralizer.candidates(seed);
if (names.size() > MAX_VARIABLES) {
throw new IllegalArgumentException("At most " + MAX_VARIABLES
+ " variable names can be refined together; narrow the change first");
}
var masks = IntStream.range(0, 1 << names.size()).boxed()
.sorted(Comparator.<Integer>comparingInt(Integer::bitCount).reversed().thenComparingInt(Integer::intValue))
.toList();
for (int mask : masks) {
RuleExampleVerifier.checkCanceled();
Set<String> selected = new LinkedHashSet<>();
for (int index = 0; index < names.size(); index++) {
if ((mask & (1 << index)) != 0) {
selected.add(names.get(index));
}
}
var candidate = ChangeVariableGeneralizer.generalize(seed, selected);
var report = verifier.verify(documentFactory.apply(candidate), suite);
if (report.passed()) {
return Optional.of(new Proposal(selected, candidate, report));
}
}
return Optional.empty();
}
}