IntEnumMigrationPlan.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.internal.corext.fix.multifile;

import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperationWithSourceRange;

import org.sandbox.jdt.cleanup.multifile.GeneratedNameAllocator;
import org.sandbox.jdt.cleanup.multifile.GeneratedNameAllocator.Allocation;
import org.sandbox.jdt.cleanup.multifile.GeneratedNameAllocator.NestedTypeRequest;
import org.sandbox.jdt.cleanup.multifile.SelectedCompilationUnitPlan;

/** Immutable plan containing all conservative package-scoped enum candidates. */
public record IntEnumMigrationPlan(SelectedCompilationUnitPlan selectedScope, List<IntEnumCandidate> candidates) {

	/** Defensively copies plan data. */
	public IntEnumMigrationPlan {
		candidates= List.copyOf(candidates);
	}

	/** Returns whether the unit participates in the cleanup run. */
	public boolean contains(ICompilationUnit unit) {
		return selectedScope.contains(unit);
	}

	/** Adds the local rewrite operation after resolving all expected plan targets. */
	public void addOperationsFor(ICompilationUnit unit, CompilationUnit root,
			Set<CompilationUnitRewriteOperationWithSourceRange> operations, Set<ASTNode> nodesProcessed)
			throws CoreException {
		String handle= unit.getPrimary().getHandleIdentifier();
		List<IntEnumCandidate> relevant= candidates.stream()
				.filter(candidate -> handle.equals(candidate.ownerCompilationUnitHandle())
						|| candidate.expectedReferenceCountsByUnit().containsKey(handle)
						|| candidate.expectedCallCountsByUnit().containsKey(handle))
				.toList();
		if (relevant.isEmpty()) {
			return;
		}
		validateGeneratedNames(unit.getPrimary(), root, relevant.stream()
				.filter(candidate -> handle.equals(candidate.ownerCompilationUnitHandle())).toList());
		IntEnumMultiFileRewriteOperation.ResolvedPlan resolved= IntEnumMultiFileRewriteOperation.resolve(
				unit.getPrimary(), root, relevant);
		nodesProcessed.addAll(resolved.processedNodes());
		operations.add(new IntEnumMultiFileRewriteOperation(resolved));
	}

	private static void validateGeneratedNames(ICompilationUnit unit, CompilationUnit root,
			List<IntEnumCandidate> ownerCandidates) throws CoreException {
		if (ownerCandidates.isEmpty()) {
			return;
		}
		List<NestedTypeRequest> requests= ownerCandidates.stream()
				.map(candidate -> new NestedTypeRequest(requestId(candidate), candidate.ownerCompilationUnitHandle(),
						candidate.ownerTypeBindingKey(), candidate.ownerTypeQualifiedName(), candidate.enumTypeName()))
				.toList();
		Map<String, Allocation> allocations= GeneratedNameAllocator.allocateNestedTypes(List.of(root), requests);
		for (NestedTypeRequest request : requests) {
			Allocation allocation= allocations.get(request.requestId());
			if (allocation == null || allocation.available()) {
				continue;
			}
			String message= "The project-wide int-to-enum plan is stale for " + unit.getElementName() //$NON-NLS-1$
					+ ": generated name " + request.requestedName() + " is not available: " //$NON-NLS-1$ //$NON-NLS-2$
					+ allocation.diagnosticMessage();
			throw new CoreException(new Status(IStatus.ERROR, "sandbox_int_to_enum", message)); //$NON-NLS-1$
		}
	}

	private static String requestId(IntEnumCandidate candidate) {
		return candidate.ownerTypeBindingKey() + '#' + candidate.methodBindingKey() + ':'
				+ candidate.parameterIndex() + "->" + candidate.enumTypeName(); //$NON-NLS-1$
	}
}