ContainerMigrationReadinessPlanner.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.analysis;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.BridgeDecision;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.BridgePolicyGroup;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.BridgeProperty;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.RequirementStatus;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.SemanticRequirement;
import org.sandbox.jdt.container.api.ContainerFlowComponent;
import org.sandbox.jdt.container.api.ContainerFlowGraph.ClosureStatus;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.BlockerProperty;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.BlockerSeverity;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.ExecutionBlocker;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.ExecutionStatus;
import org.sandbox.jdt.container.api.ContainerRecommendation;
import org.sandbox.jdt.container.api.ContainerRecommendation.ContractAssessment;
import org.sandbox.jdt.container.api.ContainerRecommendation.ContractProperty;
import org.sandbox.jdt.container.api.ContainerRecommendation.Preservation;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan;
/** Combines all semantic planning layers into one monotonic execution decision. */
public final class ContainerMigrationReadinessPlanner {
private static final List<ContractProperty> EXECUTION_PROPERTIES= List.of(
ContractProperty.ORDER,
ContractProperty.UNIQUENESS,
ContractProperty.MUTABILITY,
ContractProperty.NULLS,
ContractProperty.ALIASING,
ContractProperty.CONCURRENCY);
/** Builds the final execution gate for one semantic container recommendation. */
public ContainerMigrationReadiness plan(
ContainerFlowComponent component,
ContainerRecommendation recommendation,
ContainerSignatureMigrationPlan signaturePlan,
ContainerBridgePolicyPlan bridgePlan) {
Objects.requireNonNull(component, "component"); //$NON-NLS-1$
Objects.requireNonNull(recommendation, "recommendation"); //$NON-NLS-1$
Objects.requireNonNull(signaturePlan, "signaturePlan"); //$NON-NLS-1$
Objects.requireNonNull(bridgePlan, "bridgePlan"); //$NON-NLS-1$
List<ExecutionBlocker> blockers= new ArrayList<>();
validateTargetContracts(recommendation, signaturePlan, bridgePlan, blockers);
addFlowBlocker(component, blockers);
addRejectedPlanBlockers(signaturePlan, bridgePlan, blockers);
addContractAssessmentBlockers(recommendation, blockers);
addSignatureAndBridgeBlockers(signaturePlan, bridgePlan, blockers);
ExecutionStatus status= status(blockers);
return new ContainerMigrationReadiness(
recommendation.targetContract(), status, blockers);
}
private static void validateTargetContracts(
ContainerRecommendation recommendation,
ContainerSignatureMigrationPlan signaturePlan,
ContainerBridgePolicyPlan bridgePlan,
List<ExecutionBlocker> blockers) {
if (!recommendation.targetContract().equals(signaturePlan.targetContract())
|| !recommendation.targetContract().equals(bridgePlan.targetContract())) {
blockers.add(blocker(
BlockerProperty.TARGET_CONTRACT,
BlockerSeverity.FATAL,
"target-contract", //$NON-NLS-1$
"Recommendation, signature plan and bridge plan do not describe the same target contract.")); //$NON-NLS-1$
}
}
private static void addFlowBlocker(
ContainerFlowComponent component,
List<ExecutionBlocker> blockers) {
switch (component.closureStatus()) {
case LOCAL_CLOSED -> {
// Flow is closed.
}
case REQUIRES_SCOPE_EXPANSION -> blockers.add(blocker(
BlockerProperty.FLOW,
BlockerSeverity.PROOF_REQUIRED,
component.rootNodeId(),
"The source flow still requires additional compilation units or continuation analysis.")); //$NON-NLS-1$
case EXTERNAL_BOUNDARY -> blockers.add(blocker(
BlockerProperty.FLOW,
BlockerSeverity.FATAL,
component.rootNodeId(),
"The container value reaches an external or binary boundary that cannot be migrated atomically.")); //$NON-NLS-1$
case REJECTED -> blockers.add(blocker(
BlockerProperty.FLOW,
BlockerSeverity.FATAL,
component.rootNodeId(),
"The semantic flow component contains a rejected or contradictory path.")); //$NON-NLS-1$
}
}
private static void addRejectedPlanBlockers(
ContainerSignatureMigrationPlan signaturePlan,
ContainerBridgePolicyPlan bridgePlan,
List<ExecutionBlocker> blockers) {
if (signaturePlan.status()
== ContainerSignatureMigrationPlan.PlanningStatus.REJECTED) {
blockers.add(blocker(
BlockerProperty.SIGNATURES,
BlockerSeverity.FATAL,
"signature-plan", //$NON-NLS-1$
"Atomic signature planning was rejected.")); //$NON-NLS-1$
}
if (bridgePlan.status() == ContainerBridgePolicyPlan.PlanningStatus.REJECTED) {
blockers.add(blocker(
BlockerProperty.SIGNATURES,
BlockerSeverity.FATAL,
"bridge-plan", //$NON-NLS-1$
"Semantic bridge-policy planning was rejected.")); //$NON-NLS-1$
}
}
private static void addContractAssessmentBlockers(
ContainerRecommendation recommendation,
List<ExecutionBlocker> blockers) {
Map<ContractProperty, ContractAssessment> assessments=
new EnumMap<>(ContractProperty.class);
for (ContractAssessment assessment : recommendation.assessments()) {
ContractAssessment previous= assessments.putIfAbsent(
assessment.property(), assessment);
if (previous != null && !previous.equals(assessment)) {
blockers.add(blocker(
blockerProperty(assessment.property()),
BlockerSeverity.FATAL,
"recommendation", //$NON-NLS-1$
"Conflicting contract assessments exist for "
+ assessment.property() + '.')); //$NON-NLS-1$
}
}
for (ContractProperty property : EXECUTION_PROPERTIES) {
ContractAssessment assessment= assessments.get(property);
if (assessment == null) {
blockers.add(blocker(
blockerProperty(property),
BlockerSeverity.PROOF_REQUIRED,
"recommendation", //$NON-NLS-1$
"No semantic assessment is available for " + property + '.')); //$NON-NLS-1$
continue;
}
if (assessment.preservation() == Preservation.REQUIRES_PROOF
|| assessment.preservation() == Preservation.UNKNOWN) {
blockers.add(blocker(
blockerProperty(property),
BlockerSeverity.PROOF_REQUIRED,
"recommendation", //$NON-NLS-1$
assessment.explanation()));
} else if (assessment.preservation() == Preservation.CHANGED) {
blockers.add(blocker(
blockerProperty(property),
BlockerSeverity.BREAKING_CHANGE,
"recommendation", //$NON-NLS-1$
assessment.explanation()));
}
}
}
private static void addSignatureAndBridgeBlockers(
ContainerSignatureMigrationPlan signaturePlan,
ContainerBridgePolicyPlan bridgePlan,
List<ExecutionBlocker> blockers) {
if (signaturePlan.status()
== ContainerSignatureMigrationPlan.PlanningStatus.NO_SIGNATURE_CHANGE
|| signaturePlan.status()
== ContainerSignatureMigrationPlan.PlanningStatus.CLOSED_SOURCE_AUTOMATIC) {
if (bridgePlan.status()
!= ContainerBridgePolicyPlan.PlanningStatus.NO_BRIDGE_NEEDED) {
blockers.add(blocker(
BlockerProperty.SIGNATURES,
BlockerSeverity.FATAL,
"signature-plan", //$NON-NLS-1$
"The bridge plan requests compatibility work although the signature plan retains no old API.")); //$NON-NLS-1$
}
return;
}
for (BridgePolicyGroup group : bridgePlan.groups()) {
if (group.decision() == BridgeDecision.IMPOSSIBLE) {
blockers.add(blocker(
BlockerProperty.SIGNATURES,
BlockerSeverity.BREAKING_CHANGE,
group.signatureGroupId(),
group.explanation()));
continue;
}
for (SemanticRequirement requirement : group.requirements()) {
if (requirement.status() == RequirementStatus.CONFIRMED) {
continue;
}
BlockerSeverity severity= requirement.status()
== RequirementStatus.PROOF_REQUIRED
? BlockerSeverity.PROOF_REQUIRED
: BlockerSeverity.POLICY_REQUIRED;
blockers.add(blocker(
blockerProperty(requirement.property()),
severity,
group.signatureGroupId(),
requirement.explanation()));
}
}
}
private static ExecutionStatus status(List<ExecutionBlocker> blockers) {
if (contains(blockers, BlockerSeverity.FATAL)) {
return ExecutionStatus.REJECTED;
}
if (contains(blockers, BlockerSeverity.PROOF_REQUIRED)) {
return ExecutionStatus.REPORT_ONLY;
}
if (contains(blockers, BlockerSeverity.BREAKING_CHANGE)) {
return ExecutionStatus.INTERACTIVE_BREAKING;
}
if (contains(blockers, BlockerSeverity.POLICY_REQUIRED)) {
return ExecutionStatus.INTERACTIVE_POLICY;
}
return ExecutionStatus.AUTOMATIC;
}
private static boolean contains(
List<ExecutionBlocker> blockers,
BlockerSeverity severity) {
return blockers.stream().anyMatch(blocker -> blocker.severity() == severity);
}
private static BlockerProperty blockerProperty(ContractProperty property) {
return switch (property) {
case ORDER -> BlockerProperty.ORDER;
case UNIQUENESS -> BlockerProperty.UNIQUENESS;
case MUTABILITY -> BlockerProperty.MUTABILITY;
case NULLS -> BlockerProperty.NULLS;
case ALIASING -> BlockerProperty.ALIASING;
case CONCURRENCY -> BlockerProperty.CONCURRENCY;
case SIGNATURES -> BlockerProperty.SIGNATURES;
};
}
private static BlockerProperty blockerProperty(BridgeProperty property) {
return switch (property) {
case ORDER -> BlockerProperty.ORDER;
case UNIQUENESS -> BlockerProperty.UNIQUENESS;
case MUTABILITY -> BlockerProperty.MUTABILITY;
case NULLS -> BlockerProperty.NULLS;
case ALIASING -> BlockerProperty.ALIASING;
case CONCURRENCY -> BlockerProperty.CONCURRENCY;
case ADAPTER_FORM -> BlockerProperty.ADAPTER_FORM;
};
}
private static ExecutionBlocker blocker(
BlockerProperty property,
BlockerSeverity severity,
String sourceId,
String explanation) {
return new ExecutionBlocker(property, severity, sourceId, explanation);
}
}