ContainerBridgePolicyPlan.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.ContainerRecommendation.Preservation;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.PositionKind;
/**
* Report-only semantic compatibility assessment for deprecated bridge methods.
*
* <p>Java-level overloadability is not treated as semantic compatibility. A parameter
* bridge remains policy-dependent until every observable container property and the
* adapter form have an explicit decision. Return-type changes are represented as
* impossible under the same method name.</p>
*/
public record ContainerBridgePolicyPlan(
TargetContainerContract targetContract,
List<BridgePolicyGroup> groups,
PlanningStatus status,
List<BridgePolicyDiagnostic> diagnostics) {
public ContainerBridgePolicyPlan {
Objects.requireNonNull(targetContract, "targetContract"); //$NON-NLS-1$
groups= List.copyOf(Objects.requireNonNull(groups, "groups")); //$NON-NLS-1$
Objects.requireNonNull(status, "status"); //$NON-NLS-1$
diagnostics= List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); //$NON-NLS-1$
validateUniqueGroups(groups);
if (status == PlanningStatus.REJECTED && diagnostics.isEmpty()) {
throw new IllegalArgumentException("A rejected bridge policy requires diagnostics"); //$NON-NLS-1$
}
}
/** One atomic signature group's bridge decision and semantic requirements. */
public record BridgePolicyGroup(
String signatureGroupId,
PositionKind positionKind,
BridgeDecision decision,
List<SemanticRequirement> requirements,
String explanation) {
public BridgePolicyGroup {
signatureGroupId= requiredText(signatureGroupId, "signatureGroupId"); //$NON-NLS-1$
Objects.requireNonNull(positionKind, "positionKind"); //$NON-NLS-1$
Objects.requireNonNull(decision, "decision"); //$NON-NLS-1$
requirements= List.copyOf(Objects.requireNonNull(requirements, "requirements")); //$NON-NLS-1$
validateUniqueProperties(requirements);
explanation= requiredText(explanation, "explanation"); //$NON-NLS-1$
if (decision == BridgeDecision.POLICY_REQUIRED && requirements.isEmpty()) {
throw new IllegalArgumentException(
"A policy-dependent bridge requires semantic requirements"); //$NON-NLS-1$
}
if (decision == BridgeDecision.IMPOSSIBLE && !requirements.isEmpty()) {
throw new IllegalArgumentException(
"An impossible bridge cannot have adapter requirements"); //$NON-NLS-1$
}
}
/** Returns whether every listed property is already confirmed. */
public boolean allRequirementsConfirmed() {
return requirements.stream()
.allMatch(requirement -> requirement.status() == RequirementStatus.CONFIRMED);
}
}
/** One observable semantic property that a bridge must preserve or decide. */
public record SemanticRequirement(
BridgeProperty property,
RequirementStatus status,
Preservation sourceAssessment,
String explanation) {
public SemanticRequirement {
Objects.requireNonNull(property, "property"); //$NON-NLS-1$
Objects.requireNonNull(status, "status"); //$NON-NLS-1$
Objects.requireNonNull(sourceAssessment, "sourceAssessment"); //$NON-NLS-1$
explanation= requiredText(explanation, "explanation"); //$NON-NLS-1$
}
}
/** One reason why semantic bridge assessment could not be produced. */
public record BridgePolicyDiagnostic(
DiagnosticKind kind,
String signatureGroupId,
String message) {
public BridgePolicyDiagnostic {
Objects.requireNonNull(kind, "kind"); //$NON-NLS-1$
signatureGroupId= requiredText(signatureGroupId, "signatureGroupId"); //$NON-NLS-1$
message= requiredText(message, "message"); //$NON-NLS-1$
}
}
public enum BridgeDecision {
/** A Java overload is possible, but the adapter semantics need explicit policy. */
POLICY_REQUIRED,
/** The old and new signatures cannot coexist under the same method name. */
IMPOSSIBLE
}
public enum BridgeProperty {
ORDER,
UNIQUENESS,
MUTABILITY,
NULLS,
ALIASING,
CONCURRENCY,
/** Whether the bridge passes a view, wrapper or independent copy. */
ADAPTER_FORM
}
public enum RequirementStatus {
CONFIRMED,
PROOF_REQUIRED,
EXPLICIT_POLICY_REQUIRED
}
public enum PlanningStatus {
NO_BRIDGE_NEEDED,
REPORT_ONLY,
REJECTED
}
public enum DiagnosticKind {
SIGNATURE_PLAN_REJECTED,
TARGET_CONTRACT_MISMATCH,
AMBIGUOUS_CONTRACT_ASSESSMENT,
UNSUPPORTED_BRIDGE_CLASSIFICATION
}
private static void validateUniqueGroups(List<BridgePolicyGroup> groups) {
Set<String> ids= new HashSet<>();
for (BridgePolicyGroup group : groups) {
if (!ids.add(group.signatureGroupId())) {
throw new IllegalArgumentException(
"Duplicate bridge policy group: " + group.signatureGroupId()); //$NON-NLS-1$
}
}
}
private static void validateUniqueProperties(List<SemanticRequirement> requirements) {
Set<BridgeProperty> properties= new HashSet<>();
for (SemanticRequirement requirement : requirements) {
if (!properties.add(requirement.property())) {
throw new IllegalArgumentException(
"Duplicate bridge property: " + requirement.property()); //$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;
}
}