HintFile.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 - initial API and implementation
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.api;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Represents a parsed {@code .sandbox-hint} file containing transformation rules.
*
* <p>A hint file contains metadata and a list of {@link TransformationRule} objects.
* Each rule defines a source pattern, optional guard, and rewrite alternatives.</p>
*
* <p>Example {@code .sandbox-hint} file:</p>
* <pre>
* <!id: encoding.utf8>
* <!description: Replace String encoding literals with StandardCharsets>
* <!severity: warning>
* <!minJavaVersion: 7>
*
* new String($bytes, "UTF-8")
* => new String($bytes, java.nio.charset.StandardCharsets.UTF_8)
* ;;
* </pre>
*
* @since 1.3.2
*/
public final class HintFile {
private String id;
private String description;
private Severity severity;
private int minJavaVersion;
private List<String> tags;
private final List<TransformationRule> rules;
private final List<String> includes;
private final List<EmbeddedJavaBlock> embeddedJavaBlocks;
private boolean caseInsensitive;
private List<String> suppressWarnings;
private List<Integer> treeKindNodeTypes;
/**
* Creates a new empty hint file.
*/
public HintFile() {
this.tags = new ArrayList<>();
this.rules = new ArrayList<>();
this.includes = new ArrayList<>();
this.embeddedJavaBlocks = new ArrayList<>();
this.severity = Severity.INFO;
this.suppressWarnings = new ArrayList<>();
this.treeKindNodeTypes = new ArrayList<>();
}
/**
* Returns the hint file ID.
*
* @return the ID, or {@code null} if not set
*/
public String getId() {
return id;
}
/**
* Sets the hint file ID.
*
* @param id the ID
*/
public void setId(String id) {
this.id = id;
}
/**
* Returns the hint file description.
*
* @return the description, or {@code null} if not set
*/
public String getDescription() {
return description;
}
/**
* Sets the hint file description.
*
* @param description the description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Returns the severity level.
*
* @return the severity enum value
*/
public Severity getSeverity() {
return severity;
}
/**
* Returns the severity level as a lowercase string.
*
* @return the severity name in lowercase (e.g. {@code "info"}, {@code "warning"})
*/
public String getSeverityAsString() {
return severity.name().toLowerCase();
}
/**
* Sets the severity level from a string value.
* Parsing is case-insensitive; unrecognized values fall back to {@link Severity#INFO}.
*
* @param severity the severity string ({@code "info"}, {@code "warning"}, {@code "error"}, or {@code "hint"})
*/
public void setSeverity(String severity) {
if (severity == null) {
this.severity = Severity.INFO;
return;
}
try {
this.severity = Severity.valueOf(severity.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
this.severity = Severity.INFO;
}
}
/**
* Sets the severity level.
*
* @param severity the severity enum value
*/
public void setSeverity(Severity severity) {
this.severity = severity;
}
/**
* Returns the minimum Java version required for this hint file.
*
* @return the minimum Java version, or 0 if not set
*/
public int getMinJavaVersion() {
return minJavaVersion;
}
/**
* Sets the minimum Java version.
*
* @param minJavaVersion the minimum Java version
*/
public void setMinJavaVersion(int minJavaVersion) {
this.minJavaVersion = minJavaVersion;
}
/**
* Returns the tags for this hint file.
*
* @return unmodifiable list of tags
*/
public List<String> getTags() {
return Collections.unmodifiableList(tags);
}
/**
* Sets the tags.
*
* @param tags the tags
*/
public void setTags(List<String> tags) {
this.tags = tags != null ? new ArrayList<>(tags) : new ArrayList<>();
}
/**
* Returns the transformation rules.
*
* @return unmodifiable list of rules
*/
public List<TransformationRule> getRules() {
return Collections.unmodifiableList(rules);
}
/**
* Adds a transformation rule.
*
* @param rule the rule to add
*/
public void addRule(TransformationRule rule) {
rules.add(rule);
}
/**
* Returns the list of included hint file IDs for pattern composition.
*
* <p>When a hint file includes another hint file by ID, all rules from
* the included file are also applied when this hint file is processed.</p>
*
* @return unmodifiable list of included hint file IDs
* @since 1.3.4
*/
public List<String> getIncludes() {
return Collections.unmodifiableList(includes);
}
/**
* Adds an include reference to another hint file by ID.
*
* @param hintFileId the ID of the hint file to include
* @since 1.3.4
*/
public void addInclude(String hintFileId) {
if (hintFileId != null && !hintFileId.isBlank()) {
includes.add(hintFileId.trim());
}
}
/**
* Returns the list of embedded Java code blocks.
*
* <p>These blocks were extracted from {@code <? ?>} regions in the hint file.
* They may contain custom guard functions, fix functions, imports, or helper
* methods.</p>
*
* @return unmodifiable list of embedded Java blocks
* @since 1.5.0
*/
public List<EmbeddedJavaBlock> getEmbeddedJavaBlocks() {
return Collections.unmodifiableList(embeddedJavaBlocks);
}
/**
* Adds an embedded Java block.
*
* @param block the embedded Java block to add
* @since 1.5.0
*/
public void addEmbeddedJavaBlock(EmbeddedJavaBlock block) {
if (block != null) {
embeddedJavaBlocks.add(block);
}
}
/**
* Returns whether string literal matching should be case-insensitive.
*
* <p>When enabled, pattern strings like {@code "UTF-8"} will also match
* {@code "utf-8"}, {@code "Utf-8"}, etc.</p>
*
* @return {@code true} if case-insensitive matching is enabled
* @since 1.3.8
*/
public boolean isCaseInsensitive() {
return caseInsensitive;
}
/**
* Sets whether string literal matching should be case-insensitive.
*
* @param caseInsensitive {@code true} to enable case-insensitive matching
* @since 1.3.8
*/
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
/**
* Returns the suppress warnings keys for this hint file.
*
* <p>When a key is listed here, hints from this file will not be reported
* for code that has a {@code @SuppressWarnings} annotation containing
* that key.</p>
*
* @return unmodifiable list of suppress warnings keys
* @since 1.4.0
*/
public List<String> getSuppressWarnings() {
return Collections.unmodifiableList(suppressWarnings);
}
/**
* Sets the suppress warnings keys.
*
* @param suppressWarnings the suppress warnings keys
* @since 1.4.0
*/
public void setSuppressWarnings(List<String> suppressWarnings) {
this.suppressWarnings = suppressWarnings != null ? new ArrayList<>(suppressWarnings) : new ArrayList<>();
}
/**
* Adds a suppress warnings key.
*
* @param key the suppress warnings key
* @since 1.4.0
*/
public void addSuppressWarnings(String key) {
if (key != null && !key.isBlank()) {
this.suppressWarnings.add(key.trim());
}
}
/**
* Returns the list of AST node type constants that this hint file targets.
*
* <p>When non-empty, the pattern engine should only attempt matching on
* AST nodes whose {@code getNodeType()} is contained in this list.
* This enables early filtering for performance.</p>
*
* @return unmodifiable list of {@link org.eclipse.jdt.core.dom.ASTNode} type constants
* @since 1.4.1
*/
public List<Integer> getTreeKindNodeTypes() {
return Collections.unmodifiableList(treeKindNodeTypes);
}
/**
* Sets the list of AST node type constants.
*
* @param treeKindNodeTypes the node type constants
* @since 1.4.1
*/
public void setTreeKindNodeTypes(List<Integer> treeKindNodeTypes) {
this.treeKindNodeTypes = treeKindNodeTypes != null ? new ArrayList<>(treeKindNodeTypes) : new ArrayList<>();
}
}