SemanticPlanValue.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer and others.
*
* 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
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.api;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.sandbox.jdt.triggerpattern.api.SemanticRewritePlan.NodeKey;
/**
* Closed typed value model for semantic-plan facts and relation attributes.
*
* <p>The model deliberately contains only immutable transport values. Project
* planners may define contract-specific fact names, while the shared hint
* engine can compare values without depending on JUnit, JDT Core or JDT UI
* concepts.</p>
*/
public sealed interface SemanticPlanValue permits SemanticPlanValue.StringValue,
SemanticPlanValue.BooleanValue, SemanticPlanValue.IntegerValue,
SemanticPlanValue.NodeValue, SemanticPlanValue.ListValue {
/** Stable value kinds used for contract validation and diagnostics. */
enum Kind {
STRING,
BOOLEAN,
INTEGER,
NODE,
LIST
}
/** Returns the stable kind of this value. */
Kind kind();
/** Creates an exact text value. */
static StringValue string(String value) {
return new StringValue(value);
}
/** Creates a boolean value. */
static BooleanValue bool(boolean value) {
return new BooleanValue(value);
}
/** Creates a signed integer value. */
static IntegerValue integer(long value) {
return new IntegerValue(value);
}
/** Creates a reference to another stable semantic-plan node. */
static NodeValue node(NodeKey value) {
return new NodeValue(value);
}
/** Creates a homogeneous immutable list value. */
static ListValue list(SemanticPlanValue... values) {
return new ListValue(values == null ? List.of() : Arrays.asList(values));
}
/**
* Parses the literal forms accepted by typed plan guards.
*
* <p>Quoted values are strings, {@code true}/{@code false} are booleans and
* signed decimal values are integers. Remaining unquoted values are exact
* strings, which keeps enum-like contract values concise.</p>
*/
static SemanticPlanValue fromGuardLiteral(String literal) {
String value= literal == null ? "" : literal.trim(); //$NON-NLS-1$
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
return string(value.substring(1, value.length() - 1));
}
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { //$NON-NLS-1$ //$NON-NLS-2$
return bool(Boolean.parseBoolean(value));
}
try {
return integer(Long.parseLong(value));
} catch (NumberFormatException e) {
return string(value);
}
}
/** Exact text fact. */
record StringValue(String value) implements SemanticPlanValue {
public StringValue {
Objects.requireNonNull(value);
}
@Override
public Kind kind() {
return Kind.STRING;
}
}
/** Boolean fact. */
record BooleanValue(boolean value) implements SemanticPlanValue {
@Override
public Kind kind() {
return Kind.BOOLEAN;
}
}
/** Signed integer fact. */
record IntegerValue(long value) implements SemanticPlanValue {
@Override
public Kind kind() {
return Kind.INTEGER;
}
}
/** Reference to another stable semantic-plan node. */
record NodeValue(NodeKey value) implements SemanticPlanValue {
public NodeValue {
Objects.requireNonNull(value);
}
@Override
public Kind kind() {
return Kind.NODE;
}
}
/** Homogeneous immutable list fact. */
record ListValue(List<SemanticPlanValue> values) implements SemanticPlanValue {
public ListValue {
values= List.copyOf(values == null ? List.of() : values);
Kind elementKind= null;
for (SemanticPlanValue value : values) {
Objects.requireNonNull(value);
if (elementKind == null) {
elementKind= value.kind();
} else if (elementKind != value.kind()) {
throw new IllegalArgumentException("Semantic plan lists must be homogeneous"); //$NON-NLS-1$
}
}
}
@Override
public Kind kind() {
return Kind.LIST;
}
/** Returns the element kind, or {@code null} for an empty list. */
public Kind elementKind() {
return values.isEmpty() ? null : values.get(0).kind();
}
}
}