UniqueSequenceLocalRewritePlanner.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.Set;

import org.sandbox.jdt.container.api.ContainerMigrationReadiness;
import org.sandbox.jdt.container.api.ContainerMigrationReadiness.ExecutionStatus;
import org.sandbox.jdt.container.api.ContainerRecommendation;
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.ContainerUsageProfile.UniquenessRequirement;
import org.sandbox.jdt.container.api.TargetContainerContract.Mutability;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan.DiagnosticKind;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan.EditKind;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan.LocalEdit;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan.PlanningDiagnostic;
import org.sandbox.jdt.container.api.UniqueSequenceLocalRewritePlan.PlanningResult;
import org.sandbox.jdt.container.api.UsageEvidence;
import org.sandbox.jdt.container.api.UsageEvidence.Kind;

/** Plans a strictly local, manually unique sequence to ordered-set rewrite. */
public final class UniqueSequenceLocalRewritePlanner {

	private static final String SET_TYPE= "java.util.Set"; //$NON-NLS-1$
	private static final String LINKED_HASH_SET_TYPE= "java.util.LinkedHashSet"; //$NON-NLS-1$

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

	/** Builds one local rewrite plan or complete rejection diagnostics. */
	public PlanningResult plan(
			String compilationUnitHandle,
			ContainerRecommendation recommendation,
			ContainerMigrationReadiness readiness) {
		Objects.requireNonNull(recommendation, "recommendation"); //$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$
		}
		validateTarget(recommendation, readiness, diagnostics);
		ContainerUsageProfile profile= recommendation.sourceProfile();
		validateSource(profile, diagnostics);
		validateEvidence(profile, diagnostics);
		if (!diagnostics.isEmpty()) {
			return PlanningResult.rejected(diagnostics);
		}

		return PlanningResult.accepted(new UniqueSequenceLocalRewritePlan(
				compilationUnitHandle,
				profile.identity().bindingKey(),
				SET_TYPE,
				LINKED_HASH_SET_TYPE,
				recommendation.targetContract(),
				edits(profile)));
	}

	private static void validateTarget(
			ContainerRecommendation recommendation,
			ContainerMigrationReadiness readiness,
			List<PlanningDiagnostic> diagnostics) {
		if (!recommendation.targetContract().equals(readiness.targetContract())
				|| recommendation.targetContract().shape() != ContainerShape.SET
				|| recommendation.targetContract().mutability() != Mutability.MUTABLE
				|| recommendation.targetContract().orderRequirement()
						!= OrderRequirement.ENCOUNTER
				|| recommendation.targetContract().uniquenessRequirement()
						!= UniquenessRequirement.REQUIRED) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_TARGET,
					"The first unique-sequence rewrite requires a mutable encounter-ordered set.")); //$NON-NLS-1$
		}
	}

	private static void validateSource(
			ContainerUsageProfile profile,
			List<PlanningDiagnostic> diagnostics) {
		if (profile.completeness() != AnalysisCompleteness.LOCAL_USAGE_COMPLETE
				|| profile.escapeLevel() != EscapeLevel.LOCAL
				|| profile.aliasingContract() != AliasingContract.NO_OBSERVED_ALIAS
				|| profile.concurrency().exposure() != ThreadExposure.THREAD_CONFINED
				|| !profile.identity().hasResolvedBinding()) {
			diagnostics.add(diagnostic(
					DiagnosticKind.SOURCE_NOT_STRICTLY_LOCAL,
					"The first set rewrite requires one complete, alias-free, thread-confined local value.")); //$NON-NLS-1$
		}
		if (profile.currentShape() != ContainerShape.LIST
				|| profile.elementDomain() != ElementDomain.REFERENCE
						&& profile.elementDomain() != ElementDomain.ENUM
				|| !profile.access().append()
				|| !profile.access().membershipQuery()
				|| profile.uniquenessRequirement() != UniquenessRequirement.REQUIRED) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_SOURCE,
					"The source must be a reference sequence whose every insertion suppresses duplicates.")); //$NON-NLS-1$
		}
		if (profile.access().hasPositionalSemantics()
				|| profile.orderRequirement() == OrderRequirement.POSITIONAL) {
			diagnostics.add(diagnostic(
					DiagnosticKind.POSITIONAL_SEMANTICS,
					"Indexed and positional sequence semantics cannot be represented by the ordered set rewrite.")); //$NON-NLS-1$
		}
	}

	private static void validateEvidence(
			ContainerUsageProfile profile,
			List<PlanningDiagnostic> diagnostics) {
		if (count(profile, Kind.DUPLICATE_SUPPRESSION) == 0) {
			diagnostics.add(diagnostic(
					DiagnosticKind.MISSING_DUPLICATE_GUARD,
					"The profile does not contain a proven contains-before-add insertion.")); //$NON-NLS-1$
		}
		if (count(profile, Kind.HASH_STABLE_COMPONENT) == 0) {
			diagnostics.add(diagnostic(
					DiagnosticKind.UNSUPPORTED_SOURCE,
					"The profile does not prove stable equality and hash semantics.")); //$NON-NLS-1$
		}
		for (UsageEvidence evidence : profile.evidence()) {
			if (!SUPPORTED_EVIDENCE.contains(evidence.kind())) {
				diagnostics.add(diagnostic(
						DiagnosticKind.UNSUPPORTED_EVIDENCE,
						"The local profile contains unsupported evidence: "
								+ evidence.kind() + '.')); //$NON-NLS-1$
			}
		}
	}

	private static long count(ContainerUsageProfile profile, Kind kind) {
		return profile.evidence().stream()
				.filter(evidence -> evidence.kind() == kind)
				.count();
	}

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

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