ContainerFlowContinuationPlan.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
*******************************************************************************/
package org.sandbox.jdt.container.api;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.sandbox.jdt.container.api.ContainerFlowGraph.EdgeKind;
/**
* Binding-resolved continuation roots found after project scope expansion.
*
* <p>Each root is a normal {@link ContainerUsageProfile} that can be passed to the
* existing local flow builder. Its relationship records how the resulting local graph
* connects to the boundary node that caused the workspace search.</p>
*/
public record ContainerFlowContinuationPlan(
List<ContinuationRoot> roots,
List<ContinuationDiagnostic> diagnostics) {
public ContainerFlowContinuationPlan {
roots= List.copyOf(Objects.requireNonNull(roots, "roots")); //$NON-NLS-1$
diagnostics= List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); //$NON-NLS-1$
validateUniqueRoots(roots);
}
/** Returns whether every matched search target had a supported continuation shape. */
public boolean complete() {
return diagnostics.isEmpty();
}
/** One local variable, field or parameter from which flow analysis can continue. */
public record ContinuationRoot(
String boundaryNodeId,
ContinuationKind kind,
Relationship relationship,
EdgeKind transferKind,
String compilationUnitHandle,
String exactTargetHandle,
ContainerUsageProfile profile) {
public ContinuationRoot {
boundaryNodeId= requiredText(boundaryNodeId, "boundaryNodeId"); //$NON-NLS-1$
Objects.requireNonNull(kind, "kind"); //$NON-NLS-1$
Objects.requireNonNull(relationship, "relationship"); //$NON-NLS-1$
compilationUnitHandle= requiredText(
compilationUnitHandle, "compilationUnitHandle"); //$NON-NLS-1$
exactTargetHandle= requiredText(exactTargetHandle, "exactTargetHandle"); //$NON-NLS-1$
Objects.requireNonNull(profile, "profile"); //$NON-NLS-1$
if (!profile.identity().hasResolvedBinding()) {
throw new IllegalArgumentException(
"A continuation root requires a resolved variable binding"); //$NON-NLS-1$
}
if (relationship == Relationship.SAME_NODE && transferKind != null) {
throw new IllegalArgumentException(
"A same-node continuation cannot declare a transfer edge"); //$NON-NLS-1$
}
if (relationship != Relationship.SAME_NODE && transferKind == null) {
throw new IllegalArgumentException(
"A transferred continuation requires an edge kind"); //$NON-NLS-1$
}
}
/** Stable deterministic key used for result de-duplication. */
public String stableKey() {
return boundaryNodeId + '|' + kind + '|' + relationship + '|'
+ compilationUnitHandle + '|' + profile.identity().bindingKey();
}
}
/** One unsupported or unresolved continuation site. */
public record ContinuationDiagnostic(
DiagnosticKind kind,
String compilationUnitHandle,
String exactTargetHandle,
String message,
int sourceStart,
int sourceLength) {
public ContinuationDiagnostic {
Objects.requireNonNull(kind, "kind"); //$NON-NLS-1$
compilationUnitHandle= requiredText(
compilationUnitHandle, "compilationUnitHandle"); //$NON-NLS-1$
exactTargetHandle= requiredText(exactTargetHandle, "exactTargetHandle"); //$NON-NLS-1$
message= requiredText(message, "message"); //$NON-NLS-1$
if (sourceStart < 0 || sourceLength < 0) {
throw new IllegalArgumentException("Source range must not be negative"); //$NON-NLS-1$
}
}
}
public enum ContinuationKind {
FIELD,
PARAMETER_DECLARATION,
CALL_ARGUMENT,
RETURN_EXPRESSION,
RETURN_CONSUMER
}
public enum Relationship {
SAME_NODE,
ROOT_TO_BOUNDARY,
BOUNDARY_TO_ROOT
}
public enum DiagnosticKind {
INVALID_SIGNATURE_INDEX,
NON_ARRAY_VALUE,
UNRESOLVED_BINDING,
UNSUPPORTED_ARGUMENT,
UNSUPPORTED_RETURN_EXPRESSION,
UNSUPPORTED_RETURN_CONSUMER,
METHOD_REFERENCE,
TARGET_NOT_FOUND
}
private static void validateUniqueRoots(List<ContinuationRoot> roots) {
Set<String> keys= new HashSet<>();
for (ContinuationRoot root : roots) {
if (!keys.add(root.stableKey())) {
throw new IllegalArgumentException(
"Duplicate container flow continuation root: " + root.stableKey()); //$NON-NLS-1$
}
}
}
private static String requiredText(String value, String fieldName) {
String text= Objects.requireNonNull(value, fieldName).strip();
if (text.isEmpty()) {
throw new IllegalArgumentException(fieldName + " must not be empty"); //$NON-NLS-1$
}
return text;
}
}