ConcurrencyProtocolAssessment.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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import org.sandbox.jdt.container.api.ContainerRecommendation.Confidence;
import org.sandbox.jdt.container.api.ContainerUsageProfile.AnalysisCompleteness;
import org.sandbox.jdt.container.api.ContainerUsageProfile.SynchronizationKind;

/**
 * Report-only outcome of concurrent-container protocol analysis.
 *
 * <p>All current outcomes deliberately deny source rewriting. They separate a possible
 * modernization strategy from the equally important conclusions that existing locking
 * should be retained, that only a diagnostic is justified, or that analysis was
 * incomplete. A later migration planner must establish its own stronger proof.</p>
 */
public sealed interface ConcurrencyProtocolAssessment
		permits ConcurrencyProtocolAssessment.RecommendedMigration,
				ConcurrencyProtocolAssessment.RetainExistingLocking,
				ConcurrencyProtocolAssessment.DiagnosticOnly,
				ConcurrencyProtocolAssessment.Rejected {

	/** Returns the immutable source protocol supporting this outcome. */
	ConcurrencyProtocol protocol();

	/** Returns concise, user-facing reasons for the outcome. */
	List<String> explanations();

	/** Concurrent-container analysis is report-only in the initial delivery stage. */
	default boolean permitsSourceRewrite() {
		return false;
	}

	/** A possible modernization strategy whose implementation still requires a planner proof. */
	record RecommendedMigration(
			ConcurrencyProtocol protocol,
			TargetContainerContract targetContract,
			Strategy strategy,
			Confidence confidence,
			List<String> explanations) implements ConcurrencyProtocolAssessment {

		public RecommendedMigration {
			Objects.requireNonNull(protocol, "protocol"); //$NON-NLS-1$
			Objects.requireNonNull(targetContract, "targetContract"); //$NON-NLS-1$
			Objects.requireNonNull(strategy, "strategy"); //$NON-NLS-1$
			Objects.requireNonNull(confidence, "confidence"); //$NON-NLS-1$
			explanations= copyExplanations(explanations);
			if (!hasCompleteUsageProof(protocol)) {
				throw new IllegalArgumentException(
						"A migration recommendation requires complete local usage without unresolved boundaries"); //$NON-NLS-1$
			}
		}

		@Override
		public List<String> explanations() {
			return defensiveCopy(explanations);
		}
	}

	/** Positive conclusion that the observed lock-based protocol should not be replaced. */
	record RetainExistingLocking(
			ConcurrencyProtocol protocol,
			List<String> explanations) implements ConcurrencyProtocolAssessment {

		public RetainExistingLocking {
			Objects.requireNonNull(protocol, "protocol"); //$NON-NLS-1$
			explanations= copyExplanations(explanations);
			SynchronizationKind synchronization= protocol.summary().synchronization();
			if (!hasCompleteUsageProof(protocol)
					|| !isLockBased(synchronization)
					|| !protocol.hasSingleProtectingLock()) {
				throw new IllegalArgumentException(
						"Retaining existing locking requires complete usage proof and one lock-based protecting protocol"); //$NON-NLS-1$
			}
		}

		@Override
		public List<String> explanations() {
			return defensiveCopy(explanations);
		}
	}

	/** Actionable warning or observation that does not imply one replacement strategy. */
	record DiagnosticOnly(
			ConcurrencyProtocol protocol,
			Severity severity,
			List<String> explanations) implements ConcurrencyProtocolAssessment {

		public DiagnosticOnly {
			Objects.requireNonNull(protocol, "protocol"); //$NON-NLS-1$
			Objects.requireNonNull(severity, "severity"); //$NON-NLS-1$
			explanations= copyExplanations(explanations);
			if (protocol.completeness() == AnalysisCompleteness.REJECTED) {
				throw new IllegalArgumentException(
						"Rejected analysis must use the Rejected outcome"); //$NON-NLS-1$
			}
		}

		@Override
		public List<String> explanations() {
			return defensiveCopy(explanations);
		}
	}

	/** Analysis result for unresolved, binary, stale or otherwise unsupported protocols. */
	record Rejected(
			ConcurrencyProtocol protocol,
			List<String> explanations) implements ConcurrencyProtocolAssessment {

		public Rejected {
			Objects.requireNonNull(protocol, "protocol"); //$NON-NLS-1$
			explanations= copyExplanations(explanations);
			if (protocol.completeness() != AnalysisCompleteness.REJECTED) {
				throw new IllegalArgumentException(
						"Rejected outcome requires a rejected protocol"); //$NON-NLS-1$
			}
		}

		@Override
		public List<String> explanations() {
			return defensiveCopy(explanations);
		}
	}

	private static boolean hasCompleteUsageProof(ConcurrencyProtocol protocol) {
		AnalysisCompleteness completeness= protocol.completeness();
		return (completeness == AnalysisCompleteness.LOCAL_USAGE_COMPLETE
				|| completeness == AnalysisCompleteness.FLOW_COMPLETE)
				&& !protocol.hasUnresolvedBoundary();
	}

	private static boolean isLockBased(SynchronizationKind synchronization) {
		return synchronization == SynchronizationKind.INTRINSIC_LOCK
				|| synchronization == SynchronizationKind.EXPLICIT_LOCK
				|| synchronization == SynchronizationKind.SYNCHRONIZED_WRAPPER;
	}

	private static List<String> copyExplanations(List<String> source) {
		List<String> result= Objects.requireNonNull(source, "explanations").stream() //$NON-NLS-1$
				.map(item -> Objects.requireNonNull(item, "explanation").strip()) //$NON-NLS-1$
				.toList();
		if (result.isEmpty() || result.stream().anyMatch(String::isEmpty)) {
			throw new IllegalArgumentException("At least one non-empty explanation is required"); //$NON-NLS-1$
		}
		return result;
	}

	private static List<String> defensiveCopy(List<String> source) {
		return Collections.unmodifiableList(new ArrayList<>(source));
	}

	/** Concurrency implementation family suggested by the report-only analyzer. */
	enum Strategy {
		COPY_ON_WRITE_SEQUENCE,
		COPY_ON_WRITE_SET,
		CONCURRENT_MEMBERSHIP_SET,
		CONCURRENT_QUEUE,
		CONCURRENT_DEQUE,
		IMMUTABLE_SNAPSHOT,
		PURPOSE_SPECIFIC_ABSTRACTION
	}

	/** Diagnostic importance independent of whether a rewrite exists. */
	enum Severity {
		INFO,
		WARNING,
		ERROR
	}
}