ContainerMigrationReadiness.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.List;
import java.util.Objects;

/**
 * Final execution gate assembled from semantic flow, signature and bridge-policy
 * planning.
 *
 * <p>The status is deliberately monotonic: missing proof is more restrictive than a
 * user-selectable policy, and a fatal inconsistency rejects the migration entirely.</p>
 */
public record ContainerMigrationReadiness(
		TargetContainerContract targetContract,
		ExecutionStatus status,
		List<ExecutionBlocker> blockers) {

	public ContainerMigrationReadiness {
		Objects.requireNonNull(targetContract, "targetContract"); //$NON-NLS-1$
		Objects.requireNonNull(status, "status"); //$NON-NLS-1$
		blockers= List.copyOf(Objects.requireNonNull(blockers, "blockers")); //$NON-NLS-1$
		if (status == ExecutionStatus.AUTOMATIC && !blockers.isEmpty()) {
			throw new IllegalArgumentException(
					"An automatic migration cannot retain execution blockers"); //$NON-NLS-1$
		}
		if (status == ExecutionStatus.REJECTED
				&& blockers.stream().noneMatch(blocker -> blocker.severity() == BlockerSeverity.FATAL)) {
			throw new IllegalArgumentException(
					"A rejected migration requires at least one fatal blocker"); //$NON-NLS-1$
		}
	}

	/** Returns whether any form of source rewrite may currently be offered. */
	public boolean isExecutable() {
		return status == ExecutionStatus.AUTOMATIC
				|| status == ExecutionStatus.INTERACTIVE_POLICY
				|| status == ExecutionStatus.INTERACTIVE_BREAKING;
	}

	/** One explicit reason that limits or prevents execution. */
	public record ExecutionBlocker(
			BlockerProperty property,
			BlockerSeverity severity,
			String sourceId,
			String explanation) {

		public ExecutionBlocker {
			Objects.requireNonNull(property, "property"); //$NON-NLS-1$
			Objects.requireNonNull(severity, "severity"); //$NON-NLS-1$
			sourceId= requiredText(sourceId, "sourceId"); //$NON-NLS-1$
			explanation= requiredText(explanation, "explanation"); //$NON-NLS-1$
		}
	}

	public enum ExecutionStatus {
		REJECTED,
		REPORT_ONLY,
		INTERACTIVE_POLICY,
		INTERACTIVE_BREAKING,
		AUTOMATIC
	}

	public enum BlockerSeverity {
		FATAL,
		PROOF_REQUIRED,
		POLICY_REQUIRED,
		BREAKING_CHANGE
	}

	public enum BlockerProperty {
		FLOW,
		TARGET_CONTRACT,
		ORDER,
		UNIQUENESS,
		MUTABILITY,
		NULLS,
		ALIASING,
		CONCURRENCY,
		SIGNATURES,
		ADAPTER_FORM
	}

	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;
	}
}