ResolvedContainerFlowSearchPlan.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.ContainerFlowSearchPlan.SearchKind;
/**
* Exact Java-model targets produced by resolving a container flow search plan.
*
* <p>The original search intention is retained for every exact field or method so a
* later parser pass can distinguish declarations, callers and override-family
* members without repeating workspace model resolution.</p>
*/
public record ResolvedContainerFlowSearchPlan(List<ResolvedSearchTarget> targets) {
public ResolvedContainerFlowSearchPlan {
targets= List.copyOf(Objects.requireNonNull(targets, "targets")); //$NON-NLS-1$
Set<String> keys= new HashSet<>();
for (ResolvedSearchTarget target : targets) {
if (!keys.add(target.stableKey())) {
throw new IllegalArgumentException(
"Duplicate resolved container flow target: " + target.stableKey()); //$NON-NLS-1$
}
}
}
/** Returns an empty, immutable resolved plan. */
public static ResolvedContainerFlowSearchPlan empty() {
return new ResolvedContainerFlowSearchPlan(List.of());
}
/** Returns whether no exact continuation target was resolved. */
public boolean isEmpty() {
return targets.isEmpty();
}
/** One exact field or method related to one original graph boundary. */
public record ResolvedSearchTarget(
String sourceNodeId,
SearchKind searchKind,
TargetKind targetKind,
String bindingKey,
String ownerKey,
String javaElementHandle,
int signatureIndex,
String reason) {
public ResolvedSearchTarget {
sourceNodeId= requiredText(sourceNodeId, "sourceNodeId"); //$NON-NLS-1$
Objects.requireNonNull(searchKind, "searchKind"); //$NON-NLS-1$
Objects.requireNonNull(targetKind, "targetKind"); //$NON-NLS-1$
bindingKey= optionalText(bindingKey);
ownerKey= optionalText(ownerKey);
javaElementHandle= requiredText(javaElementHandle, "javaElementHandle"); //$NON-NLS-1$
if (signatureIndex < -1) {
throw new IllegalArgumentException(
"signatureIndex must be -1 or a parameter index"); //$NON-NLS-1$
}
reason= requiredText(reason, "reason"); //$NON-NLS-1$
if (targetKind == TargetKind.FIELD && searchKind != SearchKind.FIELD_REFERENCES) {
throw new IllegalArgumentException(
"A resolved field must originate from a field-reference search"); //$NON-NLS-1$
}
if (targetKind == TargetKind.FIELD && signatureIndex != -1) {
throw new IllegalArgumentException(
"A resolved field cannot have a signature index"); //$NON-NLS-1$
}
}
/** Stable deterministic de-duplication key. */
public String stableKey() {
return sourceNodeId + '|' + searchKind + '|' + targetKind + '|'
+ javaElementHandle + '|' + signatureIndex;
}
}
public enum TargetKind {
FIELD,
METHOD
}
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;
}
private static String optionalText(String value) {
return value == null ? "" : value.strip(); //$NON-NLS-1$
}
}