ContainerBridgePolicyPlanner.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.BridgePolicyDiagnostic;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.BridgePolicyGroup;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.BridgeProperty;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.DiagnosticKind;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.PlanningStatus;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.RequirementStatus;
import org.sandbox.jdt.container.api.ContainerBridgePolicyPlan.SemanticRequirement;
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;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.BridgeFeasibility;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.PositionKind;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.SignatureAtomicityGroup;

/**
 * Converts Java-level signature feasibility into an explicit semantic bridge-policy
 * matrix.
 *
 * <p>A parameter overload is never labelled compatible merely because it compiles.
 * The bridge must still decide or prove order, uniqueness, mutability, null behavior,
 * aliasing, concurrency and whether it passes a view, wrapper or independent copy.
 * A closed-source direct migration needs no compatibility bridge because the old
 * signature is not retained.</p>
 */
public final class ContainerBridgePolicyPlanner {

	private static final List<ContractProperty> BRIDGE_PROPERTIES= List.of(
			ContractProperty.ORDER,
			ContractProperty.UNIQUENESS,
			ContractProperty.MUTABILITY,
			ContractProperty.NULLS,
			ContractProperty.ALIASING,
			ContractProperty.CONCURRENCY);

	/** Builds one immutable bridge policy plan. */
	public ContainerBridgePolicyPlan plan(
			ContainerSignatureMigrationPlan signaturePlan,
			ContainerRecommendation recommendation) {
		Objects.requireNonNull(signaturePlan, "signaturePlan"); //$NON-NLS-1$
		Objects.requireNonNull(recommendation, "recommendation"); //$NON-NLS-1$

		List<BridgePolicyDiagnostic> diagnostics= new ArrayList<>();
		if (!signaturePlan.targetContract().equals(recommendation.targetContract())) {
			diagnostics.add(new BridgePolicyDiagnostic(
					DiagnosticKind.TARGET_CONTRACT_MISMATCH,
					"signature-plan", //$NON-NLS-1$
					"The signature plan and recommendation describe different target contracts.")); //$NON-NLS-1$
			return rejected(signaturePlan, diagnostics);
		}
		if (signaturePlan.status()
				== ContainerSignatureMigrationPlan.PlanningStatus.REJECTED) {
			diagnostics.add(new BridgePolicyDiagnostic(
					DiagnosticKind.SIGNATURE_PLAN_REJECTED,
					"signature-plan", //$NON-NLS-1$
					"Semantic bridge planning requires a valid atomic signature plan.")); //$NON-NLS-1$
			return rejected(signaturePlan, diagnostics);
		}
		if (signaturePlan.status()
				== ContainerSignatureMigrationPlan.PlanningStatus.NO_SIGNATURE_CHANGE
				|| signaturePlan.status()
						== ContainerSignatureMigrationPlan.PlanningStatus.CLOSED_SOURCE_AUTOMATIC
				|| signaturePlan.groups().isEmpty()) {
			return new ContainerBridgePolicyPlan(
					signaturePlan.targetContract(),
					List.of(),
					PlanningStatus.NO_BRIDGE_NEEDED,
					List.of());
		}

		AssessmentIndex assessments= AssessmentIndex.create(
				recommendation.assessments(), diagnostics);
		List<BridgePolicyGroup> groups= new ArrayList<>();
		for (SignatureAtomicityGroup signatureGroup : signaturePlan.groups()) {
			BridgePolicyGroup policyGroup= policyGroup(
					signatureGroup, assessments, diagnostics);
			if (policyGroup != null) {
				groups.add(policyGroup);
			}
		}
		return new ContainerBridgePolicyPlan(
				signaturePlan.targetContract(),
				groups,
				diagnostics.isEmpty() ? PlanningStatus.REPORT_ONLY : PlanningStatus.REJECTED,
				diagnostics);
	}

	private static BridgePolicyGroup policyGroup(
			SignatureAtomicityGroup signatureGroup,
			AssessmentIndex assessments,
			List<BridgePolicyDiagnostic> diagnostics) {
		if (signatureGroup.positionKind() == PositionKind.RETURN
				&& signatureGroup.bridgeFeasibility()
						== BridgeFeasibility.SAME_NAME_RETURN_BRIDGE_IMPOSSIBLE) {
			return new BridgePolicyGroup(
					signatureGroup.groupId(),
					PositionKind.RETURN,
					BridgeDecision.IMPOSSIBLE,
					List.of(),
					"The old and new return contracts cannot coexist under the same method name; a compatibility path requires a different method name or an explicitly breaking migration."); //$NON-NLS-1$
		}
		if (signatureGroup.positionKind() == PositionKind.PARAMETER
				&& signatureGroup.bridgeFeasibility()
						== BridgeFeasibility.OVERLOAD_POSSIBLE_POLICY_REQUIRED) {
			List<SemanticRequirement> requirements= new ArrayList<>();
			for (ContractProperty property : BRIDGE_PROPERTIES) {
				requirements.add(requirement(property, assessments.assessment(property)));
			}
			requirements.add(new SemanticRequirement(
					BridgeProperty.ADAPTER_FORM,
					RequirementStatus.EXPLICIT_POLICY_REQUIRED,
					Preservation.UNKNOWN,
					"Choose whether the deprecated overload passes a backed view, a wrapper, or an independent copy. These choices change structural mutation, element replacement and alias visibility.")); //$NON-NLS-1$
			return new BridgePolicyGroup(
					signatureGroup.groupId(),
					PositionKind.PARAMETER,
					BridgeDecision.POLICY_REQUIRED,
					requirements,
					"The old and new parameter signatures can coexist, but a deprecated overload is not semantically safe until every listed property has an explicit proof or policy."); //$NON-NLS-1$
		}

		diagnostics.add(new BridgePolicyDiagnostic(
				DiagnosticKind.UNSUPPORTED_BRIDGE_CLASSIFICATION,
				signatureGroup.groupId(),
				"The signature group's position and Java-level bridge classification are inconsistent.")); //$NON-NLS-1$
		return null;
	}

	private static SemanticRequirement requirement(
			ContractProperty property,
			ContractAssessment assessment) {
		BridgeProperty bridgeProperty= bridgeProperty(property);
		if (assessment == null) {
			return new SemanticRequirement(
					bridgeProperty,
					RequirementStatus.PROOF_REQUIRED,
					Preservation.UNKNOWN,
					"No contract assessment is available for this property; the bridge must remain disabled until the behavior is proven."); //$NON-NLS-1$
		}
		RequirementStatus status= switch (assessment.preservation()) {
			case PRESERVED -> RequirementStatus.CONFIRMED;
			case REQUIRES_PROOF, UNKNOWN -> RequirementStatus.PROOF_REQUIRED;
			case CHANGED -> RequirementStatus.EXPLICIT_POLICY_REQUIRED;
		};
		return new SemanticRequirement(
				bridgeProperty,
				status,
				assessment.preservation(),
				assessment.explanation());
	}

	private static BridgeProperty bridgeProperty(ContractProperty property) {
		return switch (property) {
			case ORDER -> BridgeProperty.ORDER;
			case UNIQUENESS -> BridgeProperty.UNIQUENESS;
			case MUTABILITY -> BridgeProperty.MUTABILITY;
			case NULLS -> BridgeProperty.NULLS;
			case ALIASING -> BridgeProperty.ALIASING;
			case CONCURRENCY -> BridgeProperty.CONCURRENCY;
			case SIGNATURES -> throw new IllegalArgumentException(
					"Signature compatibility is represented by the atomic signature plan"); //$NON-NLS-1$
		};
	}

	private static ContainerBridgePolicyPlan rejected(
			ContainerSignatureMigrationPlan signaturePlan,
			List<BridgePolicyDiagnostic> diagnostics) {
		return new ContainerBridgePolicyPlan(
				signaturePlan.targetContract(),
				List.of(),
				PlanningStatus.REJECTED,
				diagnostics);
	}

	private static final class AssessmentIndex {

		private final Map<ContractProperty, ContractAssessment> assessments;

		private AssessmentIndex(Map<ContractProperty, ContractAssessment> assessments) {
			this.assessments= assessments;
		}

		static AssessmentIndex create(
				List<ContractAssessment> input,
				List<BridgePolicyDiagnostic> diagnostics) {
			Map<ContractProperty, ContractAssessment> result=
					new EnumMap<>(ContractProperty.class);
			for (ContractAssessment assessment : input) {
				ContractAssessment existing= result.putIfAbsent(
						assessment.property(), assessment);
				if (existing != null && !existing.equals(assessment)) {
					diagnostics.add(new BridgePolicyDiagnostic(
							DiagnosticKind.AMBIGUOUS_CONTRACT_ASSESSMENT,
							"recommendation", //$NON-NLS-1$
							"Conflicting assessments exist for "
									+ assessment.property() + '.')); //$NON-NLS-1$
				}
			}
			return new AssessmentIndex(Map.copyOf(result));
		}

		ContractAssessment assessment(ContractProperty property) {
			return assessments.get(property);
		}
	}
}