ContainerParameterRewritePlanner.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.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.sandbox.jdt.container.api.ContainerFlowComponent;
import org.sandbox.jdt.container.api.ContainerFlowGraph.ClosureStatus;
import org.sandbox.jdt.container.api.ContainerFlowGraph.FlowNode;
import org.sandbox.jdt.container.api.ContainerFlowGraph.NodeKind;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.ExecutionStatus;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan.DiagnosticKind;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan.EditKind;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan.ParameterEdit;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan.PlanningDiagnostic;
import org.sandbox.jdt.container.api.ContainerParameterRewritePlan.PlanningResult;
import org.sandbox.jdt.container.api.ContainerShape;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.PlanningStatus;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.PositionKind;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.SignatureAtomicityGroup;
import org.sandbox.jdt.container.api.ContainerSignatureMigrationPlan.SignatureMember;
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.TargetContainerContract.Mutability;
import org.sandbox.jdt.container.api.UsageEvidence;
import org.sandbox.jdt.container.api.UsageEvidence.Kind;

/** Plans the first executable closed-source array-parameter to list rewrite. */
public final class ContainerParameterRewritePlanner {

	private static final String LIST_TYPE= "java.util.List"; //$NON-NLS-1$

	private static final Set<Kind> SUPPORTED_EVIDENCE= EnumSet.of(
			Kind.REFERENCE_COMPONENT,
			Kind.ARRAY_LENGTH_READ,
			Kind.ENCOUNTER_ITERATION,
			Kind.LOCAL_USAGE_COMPLETE,
			Kind.FLOW_CONTINUATION_ROOT);

	/**
	 * Plans one member of a single-member closed-source parameter group.
	 *
	 * <p>Override families remain rejected until every member can be emitted through
	 * one aggregate multi-file rewrite plan.</p>
	 */
	public PlanningResult plan(
			ContainerFlowComponent component,
			ContainerSignatureMigrationPlan signaturePlan,
			SignatureAtomicityGroup group,
			SignatureMember member,
			ContainerUsageProfile parameterProfile,
			ContainerMigrationReadiness readiness) {
		Objects.requireNonNull(component, "component"); //$NON-NLS-1$
		Objects.requireNonNull(signaturePlan, "signaturePlan"); //$NON-NLS-1$
		Objects.requireNonNull(group, "group"); //$NON-NLS-1$
		Objects.requireNonNull(member, "member"); //$NON-NLS-1$
		Objects.requireNonNull(parameterProfile, "parameterProfile"); //$NON-NLS-1$
		Objects.requireNonNull(readiness, "readiness"); //$NON-NLS-1$

		List<PlanningDiagnostic> diagnostics= new ArrayList<>();
		if (readiness.status() != ExecutionStatus.AUTOMATIC) {
			diagnostics.add(diagnostic(
					DiagnosticKind.NOT_AUTOMATIC,
					"The semantic execution gate has not approved automatic rewriting.")); //$NON-NLS-1$
		}
		validateSignaturePlan(component, signaturePlan, group, member, diagnostics);
		validateTarget(signaturePlan, readiness, diagnostics);
		FlowNode parameterNode= validateFlowNode(
				component, group, member, parameterProfile, diagnostics);
		validateProfile(parameterProfile, diagnostics);
		validateEvidence(parameterProfile, diagnostics);
		if (!diagnostics.isEmpty() || parameterNode == null) {
			return PlanningResult.rejected(diagnostics);
		}

		return PlanningResult.accepted(new ContainerParameterRewritePlan(
				parameterNode.compilationUnitHandle(),
				parameterNode.javaElementHandle(),
				parameterNode.bindingKey(),
				group.signatureIndex(),
				LIST_TYPE,
				signaturePlan.targetContract(),
				edits(parameterProfile)));
	}

	private static void validateSignaturePlan(
			ContainerFlowComponent component,
			ContainerSignatureMigrationPlan signaturePlan,
			SignatureAtomicityGroup group,
			SignatureMember member,
			List<PlanningDiagnostic> diagnostics) {
		if (component.closureStatus() != ClosureStatus.LOCAL_CLOSED
				|| signaturePlan.status() != PlanningStatus.CLOSED_SOURCE_AUTOMATIC) {
			diagnostics.add(diagnostic(
					DiagnosticKind.SIGNATURE_PLAN_NOT_CLOSED_SOURCE,
					"Parameter rewriting requires an explicitly automatic closed-source signature plan.")); //$NON-NLS-1$
		}
		Optional<SignatureAtomicityGroup> plannedGroup= signaturePlan.groups().stream()
				.filter(candidate -> candidate.groupId().equals(group.groupId()))
				.findFirst();
		if (plannedGroup.isEmpty() || !plannedGroup.get().equals(group)
				|| group.positionKind() != PositionKind.PARAMETER
				|| group.members().size() != 1) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_SIGNATURE_GROUP,
					"The first executable slice requires one exact, single-member parameter group.")); //$NON-NLS-1$
		}
		if (!group.members().contains(member)) {
			diagnostics.add(diagnostic(
					DiagnosticKind.SIGNATURE_MEMBER_MISMATCH,
					"The requested signature member is not part of the planned atomic group.")); //$NON-NLS-1$
		}
	}

	private static void validateTarget(
			ContainerSignatureMigrationPlan signaturePlan,
			ContainerMigrationReadiness readiness,
			List<PlanningDiagnostic> diagnostics) {
		if (!signaturePlan.targetContract().equals(readiness.targetContract())
				|| signaturePlan.targetContract().shape() != ContainerShape.LIST
				|| signaturePlan.targetContract().mutability() != Mutability.MUTABLE
				|| signaturePlan.targetContract().orderRequirement() == OrderRequirement.SORTED) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_TARGET,
					"The first parameter rewrite requires a mutable, unsorted list target.")); //$NON-NLS-1$
		}
	}

	private static FlowNode validateFlowNode(
			ContainerFlowComponent component,
			SignatureAtomicityGroup group,
			SignatureMember member,
			ContainerUsageProfile profile,
			List<PlanningDiagnostic> diagnostics) {
		Optional<FlowNode> resolved= component.node(member.flowNodeId());
		if (resolved.isEmpty()) {
			diagnostics.add(diagnostic(
					DiagnosticKind.FLOW_NODE_MISMATCH,
					"The signature member has no exact node in the closed flow component.")); //$NON-NLS-1$
			return null;
		}
		FlowNode node= resolved.get();
		if (node.kind() != NodeKind.PARAMETER
				|| !node.sourceResolved()
				|| node.signatureIndex() != group.signatureIndex()
				|| !node.javaElementHandle().equals(member.javaElementHandle())
				|| !node.compilationUnitHandle().equals(member.compilationUnitHandle())
				|| !node.bindingKey().equals(profile.identity().bindingKey())) {
			diagnostics.add(diagnostic(
					DiagnosticKind.FLOW_NODE_MISMATCH,
					"The parameter profile, signature member and closed flow node do not match.")); //$NON-NLS-1$
			return null;
		}
		return node;
	}

	private static void validateProfile(
			ContainerUsageProfile profile,
			List<PlanningDiagnostic> diagnostics) {
		if (profile.completeness() != AnalysisCompleteness.LOCAL_USAGE_COMPLETE
				&& profile.completeness() != AnalysisCompleteness.FLOW_COMPLETE) {
			diagnostics.add(diagnostic(
					DiagnosticKind.INCOMPLETE_PARAMETER_PROFILE,
					"Every use of the parameter binding must be classified before rewriting.")); //$NON-NLS-1$
		}
		if (profile.currentShape() != ContainerShape.ARRAY
				|| profile.elementDomain() != ElementDomain.REFERENCE
						&& profile.elementDomain() != ElementDomain.ENUM
				|| profile.escapeLevel() != EscapeLevel.METHOD_BOUNDARY
				|| profile.aliasingContract() != AliasingContract.NO_OBSERVED_ALIAS) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_PARAMETER_USAGE,
					"The first slice requires an alias-free reference array parameter.")); //$NON-NLS-1$
		}
		if (profile.access().indexedRead()
				|| profile.access().indexedWrite()
				|| profile.access().append()
				|| profile.access().positionalInsert()
				|| profile.access().positionalRemove()
				|| profile.access().membershipQuery()
				|| profile.access().keyLookup()) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_PARAMETER_USAGE,
					"Only length reads and enhanced-for iteration are supported for the first parameter rewrite.")); //$NON-NLS-1$
		}
	}

	private static void validateEvidence(
			ContainerUsageProfile profile,
			List<PlanningDiagnostic> diagnostics) {
		for (UsageEvidence evidence : profile.evidence()) {
			if (!SUPPORTED_EVIDENCE.contains(evidence.kind())) {
				diagnostics.add(diagnostic(
						DiagnosticKind.UNSUPPORTED_EVIDENCE,
						"The parameter profile contains unsupported evidence: "
								+ evidence.kind() + '.')); //$NON-NLS-1$
			}
		}
	}

	private static List<ParameterEdit> edits(ContainerUsageProfile profile) {
		List<ParameterEdit> edits= new ArrayList<>();
		edits.add(new ParameterEdit(
				EditKind.CHANGE_PARAMETER_DECLARATION,
				profile.identity().sourceStart(),
				profile.identity().sourceLength()));
		for (UsageEvidence evidence : profile.evidence()) {
			EditKind kind= switch (evidence.kind()) {
				case ARRAY_LENGTH_READ -> EditKind.REPLACE_LENGTH_WITH_SIZE;
				case ENCOUNTER_ITERATION -> EditKind.VERIFY_ENCOUNTER_ITERATION;
				default -> null;
			};
			if (kind != null) {
				edits.add(new ParameterEdit(
						kind, evidence.sourceStart(), evidence.sourceLength()));
			}
		}
		edits.sort(Comparator
				.comparingInt(ParameterEdit::sourceStart)
				.thenComparing(edit -> edit.kind().ordinal()));
		return List.copyOf(edits);
	}

	private static PlanningDiagnostic diagnostic(
			DiagnosticKind kind,
			String message) {
		return new PlanningDiagnostic(kind, message);
	}
}