RewriteActionValue.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;
/**
* Immutable value expression used by a {@link StructuredRewriteAction}.
*
* <p>Action values remain declarative. They can reference the primary match,
* one pattern binding, a typed semantic-plan fact, or Java annotation values
* without embedding arbitrary Java code.</p>
*/
public sealed interface RewriteActionValue permits RewriteActionValue.Literal,
RewriteActionValue.MatchedNode, RewriteActionValue.Binding,
RewriteActionValue.PlanValue, RewriteActionValue.ClassLiteral,
RewriteActionValue.Name, RewriteActionValue.ListValue {
/** Creates a typed literal value. */
static Literal literal(SemanticPlanValue value) {
return new Literal(value);
}
/** Refers to the rule's primary matched AST node. */
static MatchedNode matchedNode() {
return MatchedNode.INSTANCE;
}
/** Creates a reference to an exact pattern binding such as {@code $method}. */
static Binding binding(String placeholder) {
return new Binding(placeholder);
}
/** Reads one plan fact from the matched node. */
static PlanValue planValue(String factName) {
return new PlanValue(null, factName);
}
/** Reads one plan fact from an exact pattern binding. */
static PlanValue planValue(String placeholder, String factName) {
return new PlanValue(placeholder, factName);
}
/** Interprets the resolved nested value as a Java class literal. */
static ClassLiteral classLiteral(RewriteActionValue typeName) {
return new ClassLiteral(typeName);
}
/** Interprets the resolved nested value as a qualified Java name expression. */
static Name name(RewriteActionValue qualifiedName) {
return new Name(qualifiedName);
}
/** Creates an ordered immutable list expression. */
static ListValue list(RewriteActionValue... values) {
return new ListValue(values == null ? List.of() : Arrays.asList(values));
}
/** Typed literal transported directly into an action handler. */
record Literal(SemanticPlanValue value) implements RewriteActionValue {
public Literal {
Objects.requireNonNull(value);
}
}
/** Internal value denoting the rule's primary matched node. */
record MatchedNode() implements RewriteActionValue {
private static final MatchedNode INSTANCE= new MatchedNode();
}
/** Exact pattern-binding reference. */
record Binding(String placeholder) implements RewriteActionValue {
public Binding {
placeholder= requirePlaceholder(placeholder);
}
}
/** Typed semantic-plan fact reference. */
record PlanValue(String placeholder, String factName) implements RewriteActionValue {
public PlanValue {
if (placeholder != null) {
placeholder= requirePlaceholder(placeholder);
}
factName= requireName(factName, "Plan fact"); //$NON-NLS-1$
}
}
/** Java class-literal expression such as {@code SomeType.class}. */
record ClassLiteral(RewriteActionValue typeName) implements RewriteActionValue {
public ClassLiteral {
Objects.requireNonNull(typeName);
}
}
/** Qualified Java name expression such as one enum constant. */
record Name(RewriteActionValue qualifiedName) implements RewriteActionValue {
public Name {
Objects.requireNonNull(qualifiedName);
}
}
/** Ordered list expression, primarily for annotation array values. */
record ListValue(List<RewriteActionValue> values) implements RewriteActionValue {
public ListValue {
values= List.copyOf(values == null ? List.of() : values);
values.forEach(Objects::requireNonNull);
}
}
private static String requirePlaceholder(String value) {
String placeholder= requireName(value, "Binding placeholder"); //$NON-NLS-1$
if (!placeholder.startsWith("$") || placeholder.length() == 1) { //$NON-NLS-1$
throw new IllegalArgumentException("Binding placeholder must start with '$': " + placeholder); //$NON-NLS-1$
}
return placeholder;
}
private static String requireName(String value, String label) {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException(label + " must not be blank"); //$NON-NLS-1$
}
return value.trim();
}
}