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

import org.sandbox.jdt.container.api.ContainerRecommendation;
import org.sandbox.jdt.container.api.ContainerRecommendation.AutomationLevel;
import org.sandbox.jdt.container.api.ContainerRecommendation.Confidence;
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.ContainerShape;
import org.sandbox.jdt.container.api.ContainerUsageProfile;
import org.sandbox.jdt.container.api.ContainerUsageProfile.AliasingContract;
import org.sandbox.jdt.container.api.ContainerUsageProfile.AnalysisCompleteness;
import org.sandbox.jdt.container.api.ContainerUsageProfile.ElementDomain;
import org.sandbox.jdt.container.api.ContainerUsageProfile.EscapeLevel;
import org.sandbox.jdt.container.api.ContainerUsageProfile.OrderRequirement;
import org.sandbox.jdt.container.api.ContainerUsageProfile.ThreadExposure;
import org.sandbox.jdt.container.api.TargetContainerContract;
import org.sandbox.jdt.container.api.TargetContainerContract.Mutability;

/** Infers explainable, report-only target contracts from complete profiles. */
public final class ContainerContractInferrer {

	private final UniqueSequenceContractInferrer uniqueSequenceInferrer=
			new UniqueSequenceContractInferrer();

	/**
	 * Selects the applicable semantic container strategy and returns one explainable
	 * target contract. Specialized strategies remain separate, readable classes; callers
	 * use this common entry point.
	 */
	public Optional<ContainerRecommendation> infer(ContainerUsageProfile profile) {
		Objects.requireNonNull(profile, "profile"); //$NON-NLS-1$
		Optional<ContainerRecommendation> uniqueSequence=
				uniqueSequenceInferrer.infer(profile);
		if (uniqueSequence.isPresent()) {
			return uniqueSequence;
		}
		return inferAppendArray(profile);
	}

	private static Optional<ContainerRecommendation> inferAppendArray(
			ContainerUsageProfile profile) {
		if (!eligibleAppendArray(profile)) {
			return Optional.empty();
		}

		TargetContainerContract target= new TargetContainerContract(
				ContainerShape.LIST,
				profile.orderRequirement(),
				profile.uniquenessRequirement(),
				Mutability.MUTABLE,
				profile.nullContract(),
				"Use a dynamically growing mutable sequence instead of repeatedly copying an array."); //$NON-NLS-1$

		List<ContractAssessment> assessments= new ArrayList<>();
		assessments.add(orderAssessment(profile));
		assessments.add(new ContractAssessment(
				ContractProperty.UNIQUENESS,
				Preservation.PRESERVED,
				"An array and a list both retain duplicate elements; any existing uniqueness guard remains in the surrounding code.")); //$NON-NLS-1$
		assessments.add(new ContractAssessment(
				ContractProperty.MUTABILITY,
				Preservation.PRESERVED,
				"The proposed list remains mutable during the same local construction and use phase.")); //$NON-NLS-1$
		assessments.add(new ContractAssessment(
				ContractProperty.NULLS,
				Preservation.PRESERVED,
				"Reference arrays and ArrayList have the same null capability; an unknown application-level null policy remains unknown.")); //$NON-NLS-1$
		assessments.add(aliasingAssessment(profile));
		assessments.add(concurrencyAssessment(profile));
		assessments.add(signatureAssessment(profile));

		Confidence confidence= completeProof(profile)
				? Confidence.HIGH : Confidence.MEDIUM;
		return Optional.of(new ContainerRecommendation(
				profile,
				target,
				ContainerRuleRegistry.arrayAppendSequence(),
				confidence,
				AutomationLevel.REPORT_ONLY,
				assessments));
	}

	private static boolean eligibleAppendArray(ContainerUsageProfile profile) {
		if (!completeProfile(profile)
				|| profile.currentShape() != ContainerShape.ARRAY
				|| !referenceDomain(profile.elementDomain())
				|| !profile.access().append()
				|| profile.access().positionalInsert()
				|| profile.access().positionalRemove()) {
			return false;
		}
		return profile.orderRequirement() == OrderRequirement.ENCOUNTER
				|| profile.orderRequirement() == OrderRequirement.POSITIONAL;
	}

	private static boolean completeProfile(ContainerUsageProfile profile) {
		return profile.completeness() == AnalysisCompleteness.LOCAL_USAGE_COMPLETE
				|| profile.completeness() == AnalysisCompleteness.FLOW_COMPLETE;
	}

	private static boolean referenceDomain(ElementDomain domain) {
		return domain == ElementDomain.REFERENCE || domain == ElementDomain.ENUM;
	}

	private static ContractAssessment orderAssessment(ContainerUsageProfile profile) {
		if (profile.orderRequirement() == OrderRequirement.POSITIONAL) {
			return new ContractAssessment(
					ContractProperty.ORDER,
					Preservation.PRESERVED,
					"A list retains the observed positional and encounter-order contract."); //$NON-NLS-1$
		}
		return new ContractAssessment(
				ContractProperty.ORDER,
				Preservation.PRESERVED,
				"A list preserves the observed encounter order of appended elements."); //$NON-NLS-1$
	}

	private static ContractAssessment aliasingAssessment(ContainerUsageProfile profile) {
		if (profile.aliasingContract() == AliasingContract.NO_OBSERVED_ALIAS
				&& (profile.escapeLevel() == EscapeLevel.LOCAL
						|| profile.completeness() == AnalysisCompleteness.FLOW_COMPLETE)) {
			return new ContractAssessment(
					ContractProperty.ALIASING,
					Preservation.PRESERVED,
					"Every local use and closed-flow transfer was classified; no identity observation, external publication or unmatched alias remains."); //$NON-NLS-1$
		}
		return new ContractAssessment(
				ContractProperty.ALIASING,
				Preservation.REQUIRES_PROOF,
				"Project-wide flow must prove that replacing the array object does not change observable alias or identity behavior."); //$NON-NLS-1$
	}

	private static ContractAssessment concurrencyAssessment(ContainerUsageProfile profile) {
		if (profile.concurrency().exposure() == ThreadExposure.THREAD_CONFINED
				&& (profile.escapeLevel() == EscapeLevel.LOCAL
						|| profile.completeness() == AnalysisCompleteness.FLOW_COMPLETE)) {
			return new ContractAssessment(
					ContractProperty.CONCURRENCY,
					Preservation.PRESERVED,
					"The complete source flow contains only synchronous local and parameter transfers and no capture or publication path."); //$NON-NLS-1$
		}
		return new ContractAssessment(
				ContractProperty.CONCURRENCY,
				Preservation.REQUIRES_PROOF,
				"Thread exposure, synchronization and publication semantics must be closed before selecting a concrete collection implementation."); //$NON-NLS-1$
	}

	private static ContractAssessment signatureAssessment(ContainerUsageProfile profile) {
		if (profile.escapeLevel() == EscapeLevel.LOCAL) {
			return new ContractAssessment(
					ContractProperty.SIGNATURES,
					Preservation.PRESERVED,
					"The represented value is a local variable and no method, constructor, field, or override signature changes are required."); //$NON-NLS-1$
		}
		if (profile.completeness() == AnalysisCompleteness.FLOW_COMPLETE) {
			return new ContractAssessment(
					ContractProperty.SIGNATURES,
					Preservation.PRESERVED,
					"The closed flow identifies every participating source signature; the atomic signature planner must still approve the exact executable group."); //$NON-NLS-1$
		}
		return new ContractAssessment(
				ContractProperty.SIGNATURES,
				Preservation.REQUIRES_PROOF,
				"Fields, parameters, return values, callers and override families must be migrated atomically before execution."); //$NON-NLS-1$
	}

	private static boolean completeProof(ContainerUsageProfile profile) {
		return completeProfile(profile)
				&& profile.aliasingContract() == AliasingContract.NO_OBSERVED_ALIAS
				&& profile.concurrency().exposure() == ThreadExposure.THREAD_CONFINED;
	}
}