InferredRuleValidator.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
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.mining.analysis;
import org.sandbox.jdt.triggerpattern.api.Pattern;
import org.sandbox.jdt.triggerpattern.internal.PatternParser;
/**
* Validates an {@link InferredRule} by checking that its source and replacement
* patterns are parseable and that all placeholders are consistent.
*
* @since 1.2.6
*/
public class InferredRuleValidator {
/**
* Validation outcome.
*/
public enum ValidationStatus {
/** The rule is valid. */
VALID,
/** The source pattern cannot be parsed. */
SOURCE_UNPARSEABLE,
/** The replacement pattern cannot be parsed. */
REPLACEMENT_UNPARSEABLE,
/** A placeholder in the replacement is not present in the source. */
PLACEHOLDER_MISMATCH,
/** The confidence is below the minimum threshold. */
LOW_CONFIDENCE
}
/**
* Result of validating an inferred rule.
*
* @param status the validation status
* @param message a human-readable description
*/
public record ValidationResult(ValidationStatus status, String message) {
}
/**
* Minimum confidence required for an inferred rule to be considered reliable.
* <p>
* This value was chosen empirically to filter out very low-confidence rules
* that are typically noisy while still allowing potentially useful patterns.
*/
private static final double MIN_CONFIDENCE = 0.3;
private final PatternParser parser = new PatternParser();
/**
* Validates the given inferred rule.
*
* @param rule the rule to validate
* @return the validation result
*/
public ValidationResult validate(InferredRule rule) {
if (rule.confidence() < MIN_CONFIDENCE) {
return new ValidationResult(ValidationStatus.LOW_CONFIDENCE,
"Confidence " + rule.confidence() + " is below threshold " + MIN_CONFIDENCE); //$NON-NLS-1$ //$NON-NLS-2$
}
// Check that source pattern is parseable
try {
Pattern sourcePattern = Pattern.of(rule.sourcePattern(), rule.kind());
parser.parse(sourcePattern);
} catch (Exception e) {
return new ValidationResult(ValidationStatus.SOURCE_UNPARSEABLE,
"Source pattern is not parseable: " + e.getMessage()); //$NON-NLS-1$
}
// Check that replacement pattern is parseable
try {
Pattern replacementPattern = Pattern.of(rule.replacementPattern(), rule.kind());
parser.parse(replacementPattern);
} catch (Exception e) {
return new ValidationResult(ValidationStatus.REPLACEMENT_UNPARSEABLE,
"Replacement pattern is not parseable: " + e.getMessage()); //$NON-NLS-1$
}
// Check placeholder consistency: all placeholders used in replacement
// must also appear in the source
for (String placeholder : rule.placeholderNames()) {
if (rule.replacementPattern().contains(placeholder)
&& !rule.sourcePattern().contains(placeholder)) {
return new ValidationResult(ValidationStatus.PLACEHOLDER_MISMATCH,
"Placeholder " + placeholder //$NON-NLS-1$
+ " used in replacement but not in source"); //$NON-NLS-1$
}
}
return new ValidationResult(ValidationStatus.VALID, "Rule is valid"); //$NON-NLS-1$
}
}